C#でTypeInitializationExceptionが発生する

ある日突然プログラムを実行したら「TypeInitializationException」が発生した場合の対処法です。

自分のコード内であれば、大体半分くらい確率で以下の状況です。残りの半分は外部の環境(リンクしてるDLLとか…)で発生しているので環境を再インストールするとか設定をちゃんとするとかになります。偶にどうしようもないバグなのでその時は開発元に問い合わせましょう。

// 発生する例外のメッセージ

// .NET Core
System.TypeInitializationException: The type initializer for 'StaticClassSample' threw an exception. 

// .NET Framework (ja-jp)
System.TypeInitializationException: 'StaticClassSample' のタイプ初期化子が例外をスローしました。

概ね以下の状態になると発生します。つまり、staticフィールドの初期化で例外が発生しています。

以下コード例のように安全であるはずのフィールドを呼び出したにも関わらず例外が発生する場合もあります。

using System;

namespace ConsoleApp26
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                int a = StaticClassSample.B;
                //
                // アクセスした瞬間以下の例外が発生する
                //
                // System.TypeInitializationException: 
                //   The type initializer for 'StaticClassSample' threw an exception. 
                //    ---> System.ArgumentException: Value does not fall within the expected range.
                //    at Sample.Int() in ****\Program.cs:line 36
                //    at StaticClassSample..cctor() in ****\Program.cs:line 31
                //    --- End of inner exception stack trace ---
                //    at Program.Main(String[] args) in ****\Program.cs:line 11
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }
    }

    public static class StaticClassSample
    {
        public static int A = BadMethod(); // フィールドの初期化に失敗するとよくわからないエラーになる

        public static int B = NormalMethod();

        public static int C => BadMethod(); // プロパティは問題ない

        // 問題が発生する処理の呼び出し
        public static int BadMethod() => throw new ArgumentException();

        // 普通の呼び出し
        public static int NormalMethod() => 10;
    }
}

発生原理は以下の通り。

(1) staticな要素への初回呼び出しが発生 ↓ (2) ランタイム側で自動的にフィールドを初期化しようとして暗黙のstaticコンストラクタが呼び出される ↓ (3) staticコンストラクタ実行中に例外で操作が失敗する ↓ (4) TypeInitializationExceptionが発生する

の順序で発生します。

この時、staticクラスの型の生成に失敗したのでTypeInitializationException(=型の初期化の失敗)という例外が発生しますが厄介なことに自分が触った要素以外のstaticなフィールドとかで例外が起きてもこの状態になるので、「誰かが問題の発生する変更を行った場合」予期せず上記の例外が発生します。

メンバーを最初に呼び出したスレッド上で上記の例外が発生するので検出自体は可能です。しかし、通常この例外をハンドルすることを想定しないですし、実行時にしか分からないので意味不明だたいていの場合しある日突然発生します。

但し、補足した例外のスタックトレースに問題のある型の名前は出るので、手が出せるクラスの場合はメンバーをひとつづつ確認することになります。(ここで自分のコード以外だとどうしようもないケースも割とありますが…

特に、「定義ファイルを外部リソースから読んでstaticフィールドに格納する」処理とか、「事前に計算した値をstaticなフィールドに入れておく(実は事前に計算されていない)」とかで発生しがちです。前者の場合環境の問題なので自分の環境をもう一度確認しましょう。後者はただのバグです。

従って、事故を避けるために、staticフィールドは例外を起こさない実装をするよう注意しましょう。

そういう実装は誰も(主にVisualStudioとかインスペクションツールは)サジェストしてくれないので注意が必要です。

以上です。