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

# 1. 概要

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

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

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

:::note info
**[2025年6月22日追記]**
この問題は、.NETランタイムの既知の問題であるようです。
詳細については[7. 結論](#7-結論)の追記を参照してください。
:::

# 2. 実行環境
- OS
  - Windows 10 64bit
- .Net Runtime
  - .Net 8.0.17
  - .Net 9.0.6

# 3. 発生現象

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

```c#
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` の原因というと、ぱっと思いつくのは以下のものです。
- 静的コンストラクタでの何らかの例外
- 静的フィールドの初期化コードでの何らかの例外

```c#
// インスタンスを作成しようとすると例外が発生する構造体の例
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 静的フィールドではなく静的プロパティにする

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

修正前:
```c#
public static readonly ReadOnlyMemory<StructureWithComplexStaticField> GrayScales =
    Enumerable.Repeat(232, 24).Select(code => new StructureWithComplexStaticField(code)).ToArray();
```

修正後:
```c#
public static ReadOnlyMemory<StructureWithComplexStaticField> GrayScales =>
    Enumerable.Repeat(232, 24).Select(code => new StructureWithComplexStaticField(code)).ToArray();
```

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

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

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

**[修正前]**
```c#
public static readonly ReadOnlyMemory<StructureWithComplexStaticField> GrayScales =
    Enumerable.Repeat(232, 24).Select(code => new StructureWithComplexStaticField(code)).ToArray();
```

**[修正後(1)]**
```c#
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)]**
```c#
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)]**
```c#
public static readonly ReadOnlyCollection<StructureWithComplexStaticField> GrayScales =
    new([.. Enumerable.Repeat(232, 24).Select(code => new StructureWithComplexStaticField(code))]);
```

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

:::note info
**[2026年6月22日追記]**
https://github.com/dotnet/runtime/issues/104511 にて、今回の問題とそっくりそのままの問題が議論されていました。
~~私の乏しい読解能力をフル回転させて読んだところ、~~ 問題の発生条件は以下の通りです。

1. `readonly` なジェネリック構造体 `A` が定義されており、かつ
1. `readonly` な構造体 `B` が定義されており、かつ
1. 構造体 `B` に、型が `A<B>` である `readonly` な静的フィールドが存在する場合。

本稿で紹介した再現用コードでは、構造体 `StructureWithComplexStaticField` に型が `ReadOnlyMemory<StructureWithComplexStaticField>` である `readonly` な静的フィールド が存在するので、まさにそれに該当しています。

原因はどうやら JIT にあるらしく、.NET 10 で修正予定のようです。
:::

# 8. 参考資料

- [未使用ジェネリック型引数で TypeLoadException が起こる問題 | ++C++; // 未確認飛行 C ブログ](https://ufcpp.net/blog/2022/12/unused-generic-type-parameter/)
- [C#でTypeLoadExceptionを吐かれて困った出来事](https://qiita.com/YuHima03/items/23fb4a2bc0b554723240)