50 Stimmen

Ist die Reihenfolge der initialisierung statischer Klassen in C# deterministisch?

Ich habe ein paar Recherchen angestellt und ich denke, der folgende Code ist garantiert, um Ausgaben zu produzieren:

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);
    }
}

Ich habe dies zahlreiche Male ausgeführt und bekomme immer die Ausgabe über dem Codebereich; Ich wollte nur überprüfen, wird sich das ändern? Auch wenn textuell Klasse A und Klasse B neu angeordnet sind?

Wird garantiert, dass die erste Verwendung eines statischen Objekts die Initialisierung seiner statischen Member auslösen wird, gefolgt von der Instanziierung seines statischen Konstruktors? Für dieses Programm wird durch die Verwendung von A.X in main die Initialisierung von A.X ausgelöst, was wiederum B.X initialisiert, dann B() und nach Abschluss der Initialisierung von A.X wird mit A() fortgefahren. Schließlich wird Main() A.X und B.X ausgeben.

1 Stimmen

Wenn Main() zuerst B.X verwendet hätte, wären die Ausgabewerte 7, 8, 8, A = 8, B = 8.

2 Stimmen

Für den spezifischen Code aus Ihrer Frage ist die Antwort von Porges genau richtig. Für die allgemeinere Frage aus Ihrem Titel - "Ist die Reihenfolge der statischen Klasseninitialisierung in C# deterministisch?" - lautet die Antwort, dass es darauf ankommt: Wenn die beteiligten statischen Typen alle statische Konstruktoren haben, ist die Initialisierungsreihenfolge bestimmt; wenn nicht, dann nicht.

0 Stimmen

60voto

porges Punkte 29409

Direkt aus ECMA-334:

17.4.5.1: "Existiert ein statischer Konstruktor (§17.11) in der Klasse, werden die statischen Feldinitialisierer unmittelbar vor der Ausführung dieses statischen Konstruktors ausgeführt. Andernfalls werden die statischen Feldinitialisierer zu einem implementationsabhängigen Zeitpunkt vor dem ersten Gebrauch eines statischen Feldes dieser Klasse ausgeführt."

Und:

17.11: Die Ausführung eines statischen Konstruktors wird durch das erste der folgenden Ereignisse in einer Anwendungsdomäne ausgelöst:

  • Es wird eine Instanz der Klasse erstellt.
  • Eine der statischen Member der Klasse wird referenziert.

Enthält eine Klasse die Main-Methode (§10.1), mit der die Ausführung beginnt, wird der statische Konstruktor für diese Klasse vor dem Aufruf der Main-Methode ausgeführt. Wenn eine Klasse statische Felder mit Initialisierungen enthält, werden diese Initialisierungen in der textuellen Reihenfolge unmittelbar vor der Ausführung des statischen Konstruktors (§17.4.5) ausgeführt.

Also ist die Reihenfolge:

  • A.X verwendet, also wird static A() aufgerufen.
  • A.X muss initialisiert werden, verwendet aber B.X, daher wird static B() aufgerufen.
  • B.X muss initialisiert werden und wird auf 7 initialisiert. B.X = 7
  • Alle statischen Felder von B werden initialisiert, also wird static B() aufgerufen. X wird ausgegeben ("7"), dann auf A.X gesetzt. A hat bereits mit der Initialisierung begonnen, also erhalten wir den Wert von A.X, der der Standardwert ist ("wenn eine Klasse initialisiert wird, werden zuerst alle statischen Felder in dieser Klasse auf ihren Standardwert initialisiert"); B.X = 0, und wird ausgegeben ("0").
  • Die Initialisierung von B ist abgeschlossen und der Wert von A.X wird auf B.X+1 gesetzt. A.X = 1.
  • Alle statischen Felder von A werden initialisiert, also wird static A() aufgerufen. A.X wird ausgegeben ("1").
  • Zurück in Main werden die Werte von A.X und B.X ausgegeben ("1", "0").

Im Standard wird tatsächlich darauf hingewiesen:

17.4.5: Es ist möglich, dass statische Felder mit variablen Initialisierern im Zustand ihres Standardwerts beobachtet werden. Dies wird jedoch als stilistisch nicht empfohlen.

13voto

Ben Voigt Punkte 268424

Bei der Garantie sind etwa vier verschiedene Regeln der C#-Spezifikation beteiligt, und sie ist spezifisch für C#. Die einzige Garantie, die von der .NET-Runtime gegeben wird, ist, dass die Typinitialisierung vor der Verwendung des Typs beginnt.

  • Dass statische Felder bis zum Ausführen des Typinitialisierers auf Null initialisiert sind.
  • Dass statische Feldinitialisierer unmittelbar vor dem statischen Konstruktor ausgeführt werden.
  • Dass statische Konstruktoren beim ersten Instanzkonstruktoraufruf oder ersten statischen Elementaufruf aufgerufen werden.
  • Dass Funktionsargumente in der Reihenfolge von links nach rechts ausgewertet werden.

Sich darauf zu verlassen, ist eine sehr schlechte Idee, da es wahrscheinlich ist, dass es jeden verwirrt, der Ihren Code liest, insbesondere wenn sie mit Sprachen mit einer ähnlichen Syntax vertraut sind, die nicht alle vier der oben genannten Garantien bieten.

Bitte beachten Sie, dass Porges' Kommentar sich auf meine erste Aussage (basierend auf dem .NET-Verhalten) bezog, dass die Garantien zu schwach sind, um das beobachtete Verhalten zu gewährleisten. Porges hat recht, dass die Garantien stark genug sind, aber tatsächlich ist eine weit komplexere Kette beteiligt, als er vorschlägt.

6voto

Kyle Delaney Punkte 10603

Sie könnten interessiert sein zu erfahren, dass es sogar möglich ist, Werte einem Feld zwischen seiner Standardinitialisierung und Variableninitialisierung zuzuweisen.

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

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

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

ergibt

0 - Standardinitialisierung
3 - Zuweisung
4 - Variableninitialisierung

-2voto

LBushkin Punkte 124894

Die deterministische Initialisierung von statischen Elementen ist tatsächlich garantiert ... aber nicht in "textueller Reihenfolge". Darüber hinaus wird sie möglicherweise nicht in vollständig verzögerter Weise durchgeführt (was bedeutet, dass dies nur geschieht, wenn die statische Variable zum ersten Mal referenziert wird). In Ihrem Beispiel mit Ganzzahlen würde es jedoch keinen Unterschied machen.

In einigen Fällen ist es wünschenswert, eine verzögerte Initialisierung zu erhalten (insbesondere bei teuren Singletons) - in diesem Fall müssen Sie manchmal einige Hürden überwinden, um es richtig zu machen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X