web-dev-qa-db-ja.com

C#での静的クラスの初期化の順序は決定論的ですか?

私はいくつかの検索を行いましたが、次のコードが出力を生成することが保証されていると思います。

B.X = 7

B.X = 0

A.X = 1

A = 1, B = 0
static class B
{
    public static int X = 7;
    static B() {
        Console.WriteLine("B.X = " + X);
        X = A.X;
        Console.WriteLine("B.X = " + X);
    }
}

static class A
{
    public static int X = B.X + 1;
    static A() {
        Console.WriteLine("A.X = " + X);
    }
}

static class Program
{
    static void Main() {
        Console.WriteLine("A = {0}, B = {1}", A.X, B.X);
    }
}

私はこれを何度も実行し、常にコードセクションの上に出力を取得しました。変更されるかどうかを確認したかっただけですか?テキストであったとしても、クラスAとクラスBは再配置されますか?

静的オブジェクトを最初に使用すると、静的メンバーの初期化がトリガーされ、続いて静的コンストラクターがインスタンス化されることが保証されていますか?このプログラムでは、mainでA.Xを使用すると、A.Xの初期化がトリガーされ、次にB.Xが初期化され、次にB()がトリガーされ、A.Xの初期化が完了した後、A()に進みます。最後に、Main()A.XとB.X`を出力します。

38
Jess

ECMA-334から直接:

17.4.5.1: "静的コンストラクター(§17.11)がクラスに存在する場合、静的フィールド初期化子の実行は、その静的コンストラクターを実行する直前に行われます。それ以外の場合、静的フィールド初期化子は、そのクラスの静的フィールドを最初に使用する前に、実装に依存する時間に実行されます。」

そして:

17.11:静的コンストラクターの実行は、アプリケーションドメイン内で発生する次のイベントの最初のものによってトリガーされます。

  • クラスのインスタンスが作成されます。
  • クラスの静的メンバーのいずれかが参照されます。

クラスに実行が開始されるMainメソッド(§10.1)が含まれている場合、そのクラスの静的コンストラクターは、Mainメソッドが呼び出される前に実行されます。 クラスに初期化子を持つ静的フィールドが含まれている場合、それらの初期化子は静的コンストラクターを実行する直前にテキスト順に実行されます(§17.4.5)。

したがって、順序は次のとおりです。

  • _A.X_が使用されたため、static A()が呼び出されました。
  • _A.X_を初期化する必要がありますが、_B.X_を使用するため、static B()が呼び出されます。
  • _B.X_を初期化する必要があり、7に初期化されます。_B.X = 7_
  • Bのすべての静的フィールドが初期化されるため、static B()が呼び出されます。 Xが出力され( "7")、_A.X_に設定されます。 Aはすでに初期化されているため、デフォルト値である_A.X_の値を取得します(「クラスが初期化されると、そのクラスのすべての静的フィールドは最初にデフォルト値に初期化されます"); _B.X = 0_であり、印刷されます( "0")。
  • Bの初期化が完了し、_A.X_の値が_B.X+1_に設定されます。 _A.X = 1_。
  • Aのすべての静的フィールドが初期化されるため、static A()が呼び出されます。 _A.X_が出力されます( "1")。
  • Mainに戻ると、_A.X_と_B.X_の値が出力されます( "1"、 "0")。

それは実際に標準でこれについてコメントしています:

17.4.5:変数初期化子を持つ静的フィールドがデフォルト値の状態で監視される可能性があります。ただし、これはスタイルの問題として強くお勧めしません。

44
porges

この保証には、C#仕様の約4つの異なるルールが関係しており、C#に固有です。 .NETランタイムによって行われる唯一の保証は、型が使用される前に型の初期化が開始されることです。

  • その静的フィールドは、型初期化子が実行されるまでゼロで初期化されます。
  • その静的フィールド初期化子は、静的コンストラクターの直前で実行されます。
  • その静的コンストラクターは、最初のインスタンスコンストラクター呼び出しまたは最初の静的メンバー参照で呼び出されます。
  • その関数の引数は、左から右の順序で評価されます。

これに依存することは非常に悪い考えです。特に、上記の4つすべてを保証しない同様の構文の言語に精通している場合は、コードを読む人を混乱させる可能性があります。

Porgesのコメントは、保証が弱すぎて観察された動作を保証できないという私の最初のステートメント(.NETの動作に基づく)に関連していることに注意してください。ポルゲスは保証が十分に強力であることは正しいですが、実際には彼が示唆するよりもはるかに複雑なチェーンが含まれています。

6
Ben Voigt

デフォルトの初期化と変数の初期化の間でフィールドに値を割り当てることさえ可能であることを知りたいと思うかもしれません。

private static int b = Foo();
private static int a = 4;

private static int Foo()
{
    Console.WriteLine("{0} - Default initialization", a);
    a = 3;
    Console.WriteLine("{0} - Assignment", a);
    return 0;
}

public static void Main()
{
    Console.WriteLine("{0} - Variable initialization", a);
}

出力

0 - Default initialization
3 - Assignment
4 - Variable initialization
5
Kyle Delaney