1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

c# 構造体の静的フィールドの初期化で TypeLoadException 例外

Posted at

1. 概要

筆者が開発中の c# のアプリで TypeLoadException 例外が発生したのですが、発生条件がやや特殊(?) で調査に結構手間取りました。

原因は不明のままですが、回避方法らしきものはわかったので、投稿することにしました。

同じような問題でお悩みの方の手助けとなれば幸いです。

2. 実行環境

  • OS
    • Windows 10 64bit
  • .Net Runtime
    • .Net 8.0.17
    • .Net 9.0.6

3. 発生現象

以下のコンソールアプリケーションのソースコードをビルドして実行すると、Mainメソッド実行が始まる前に TypeLoadException 例外が発生します。

using System;
using System.Globalization;
using System.Linq;

namespace Experiment.CSharp
{
    internal sealed class Program
    {
        private static void Main(String[] args)
        {
            Console.WriteLine(typeof(StructureWithComplexStaticField).FullName);

            Console.Beep();
            Console.WriteLine("Complete");
            _ = Console.ReadLine();
        }
    }

    public readonly struct StructureWithComplexStaticField
    {
        private readonly Byte _value;

        private StructureWithComplexStaticField(Int32 value)
        {
            _value = value;
        }

        public static readonly ReadOnlyMemory<StructureWithComplexStaticField> GrayScales =
             Enumerable.Repeat(232, 24).Select(code => new StructureWithComplexStaticField(code)).ToArray();

        public override String ToString() => _value.ToString(CultureInfo.InvariantCulture);
    }
}

4. 調査内容

最初に問題が発生したコードから再現条件を絞り込み、前述の単純なサンプルソースコードに仕上げるのに 2 日ほど拘束されました (泣) がそれはさておき。

TypeLoadException の原因というと、ぱっと思いつくのは以下のものです。

  • 静的コンストラクタでの何らかの例外
  • 静的フィールドの初期化コードでの何らかの例外
// インスタンスを作成しようとすると例外が発生する構造体の例
public readonly struct StructureWithTypeLoadException1
{
    static StructureWithTypeLoadException1()
    {
        throw new Exception();
    }

    private readonly Int32 _value;

    public StructureWithTypeLoadException1(Int32 value)
    {
        _value = value;
    }

    public override String ToString() => _value.ToString(null, null);
}

// 静的フィールドにアクセスすると例外が発生する構造体の例
public readonly struct StructureWithTypeLoadException2
{
    public static readonly Int32 StaticValue = EnsureStaticValue();

    private readonly Int32 _value;

    public StructureWithTypeLoadException2(Int32 value)
    {
        _value = value;
    }

    public override String ToString() => _value.ToString(null, null);

    private static Int32 EnsureStaticValue() => throw new Exception();
}

ただ、これらの場合は、構造体のインスタンスを作成したり静的フィールドにアクセスしたりすると例外が発生しますが、今回の場合のように typeof() で型情報を取得しようとしただけで例外が発生するといった問題はこれまで見たことがありませんでした。

struct ではなく、class にしてみたところ例外は発生しなくなりましたので、構造体固有の問題のようです。

もちろん、ビルド時にはエラーは何も発生していませんし、静的フィールドが一つしかないのでフィールドの初期化順序にまつわる諸々の問題も発生しようがありません。

5. 考察

例によって Google 先生にお伺いを立てたところ、構造体の静的フィールドというのは結構取り扱いが難しいらしく、コンパイルは正常に行えても実行時に JIT がエラーとする、といったケースがあるようです。

6. 回避方法

原因はよくわからないものの、発生条件はかなり絞り込めたので、以下のような回避方法を採ってみました。

6.1 静的フィールドではなく静的プロパティにする

例えば、以下のように変更します。

修正前:

public static readonly ReadOnlyMemory<StructureWithComplexStaticField> GrayScales =
    Enumerable.Repeat(232, 24).Select(code => new StructureWithComplexStaticField(code)).ToArray();

修正後:

public static ReadOnlyMemory<StructureWithComplexStaticField> GrayScales =>
    Enumerable.Repeat(232, 24).Select(code => new StructureWithComplexStaticField(code)).ToArray();

多分これが一番単純な方法で、これはこれできちんと動作するのですが、プロパティの値を取得する度に値を作成するコードが走ってしまうのは問題かなぁと思います。

6.2 初期化コードを単純化する

今回のコードの場合は静的フィールドでは大きさが 24 の配列を返しているのですが、Linq とか頼らずに最初から配列で書いた方がいいのではないか、という方法です。

[修正前]

public static readonly ReadOnlyMemory<StructureWithComplexStaticField> GrayScales =
    Enumerable.Repeat(232, 24).Select(code => new StructureWithComplexStaticField(code)).ToArray();

[修正後(1)]

public static readonly ReadOnlyMemory<StructureWithComplexStaticField> GrayScales =
    new StructureWithComplexStaticField[]
    {
        new (232),
        new (233),
        new (234),
        new (235),
        new (236),
        new (237),
        new (238),
        new (239),
        new (240),
        new (241),
        new (242),
        new (243),
        new (244),
        new (245),
        new (246),
        new (247),
        new (248),
        new (249),
        new (240),
        new (241),
        new (242),
        new (243),
        new (244),
        new (245),
    };

Linqで配列を作っていたのを、最初から配列を定義してみました。しかし、これでも元の問題が発生します。

色々試行錯誤した結果、以下のコードなら例外が発生しないことが分かりました。解せない…

[修正後(2)]

public static readonly ReadOnlyCollection<StructureWithComplexStaticField> GrayScales =
    new(new StructureWithComplexStaticField[]
    {
        new (232),
        new (233),
        new (234),
        new (235),
        new (236),
        new (237),
        new (238),
        new (239),
        new (240),
        new (241),
        new (242),
        new (243),
        new (244),
        new (245),
        new (246),
        new (247),
        new (248),
        new (249),
        new (240),
        new (241),
        new (242),
        new (243),
        new (244),
        new (245),
    });

なお、以下のコードでも例外は発生しませんでした。ReadOnlyMemory<T> に関する問題なのでしょうか…?
[修正後(3)]

public static readonly ReadOnlyCollection<StructureWithComplexStaticField> GrayScales =
    new([.. Enumerable.Repeat(232, 24).Select(code => new StructureWithComplexStaticField(code))]);

7. 結論

  • 構造体の静的フィールドの初期化で複雑なコードを記述すると、その構造体の型情報を参照しようとしただけでも (つまり、ソースコードにその構造体の名前を書いただけでも) TypeLoadException が発生することがある。例外の発生個所は、当該構造体を使用している関数の開始直前である。 (例: Main メソッドでその構造体を使用していれば、Main メソッドの開始直前で例外が発生する)
  • 正確な発生条件は不明である。
  • 静的フィールドを静的プロパティに変更することにより回避できる。
  • または、静的フィールドの型を変更したり、初期化コードをより単純にすることにより問題を回避できることがある。

8. 参考資料

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?