29 Stimmen

Reihenfolge von statischen Konstruktoren/Initialisierern in C#

Beim Arbeiten an einer C#-App habe ich gerade bemerkt, dass in mehreren Stellen statische Initialisierer Abhängigkeiten voneinander haben, wie hier:

static private List a = new List() { 0 };
static private List b = new List() { a[0] };

Ohne etwas Besonderes zu tun hat das funktioniert. Ist das nur Glück? Hat C# Regeln, um dies aufzulösen?

Bearbeiten: (siehe Panos) In einer Datei scheint die lexikalische Reihenfolge entscheidend zu sein? Wie sieht es über Dateien hinweg aus?

Beim Versuch einer zyklischen Abhängigkeit wie dieser habe ich festgestellt:

static private List a = new List() { b[0] };
static private List b = new List() { a[0] };

und das Programm lief nicht gleich (der Testlauf schlug komplett fehl und ich habe nicht weiter gesucht).

0 Stimmen

Ich denke, dass dies auch bei Dateien (d. h. bei verschiedenen Klassen) passieren wird. Während der Typinitialisierung von Klasse A wird Klasse B aufgefordert, initialisiert zu werden, und Klasse B wird eine Nullreferenz zu Klasse A finden.

0 Stimmen

Jetzt, über Dateien derselben Klasse (Teilklasse), liegt es wahrscheinlich am Pre-Prozessor zu bestimmen, ob es fehlschlägt oder nicht.

0 Stimmen

So, wenn A auf B.b verweist, wird A.a, wenn es initialisiert wird, B.b erhöhen?

23voto

Cowan Punkte 36327

Siehe Abschnitt 10.4 der C#-Spezifikation für die Regeln hier:

Wenn eine Klasse initialisiert wird, werden zuerst alle statischen Felder in dieser Klasse auf ihre Standardwerte initialisiert, und dann werden die statischen Feld-Initialisierer in textueller Reihenfolge ausgeführt. Ebenso werden bei der Erstellung einer Instanz einer Klasse alle Instanzfelder in dieser Instanz zuerst auf ihre Standardwerte initialisiert, und dann werden die Instanzfeld-Initialisierer in textueller Reihenfolge ausgeführt. Es ist möglich, dass statische Felder mit variablen Initialisierern in ihrem Standardwertzustand beobachtet werden können. Dies ist jedoch als Stilfrage sehr abzuraten.

Mit anderen Worten wird in Ihrem Beispiel 'b' in seinen Standardzustand (null) initialisiert, und daher ist die Referenz darauf im Initialisierer von 'a' legal, würde jedoch zu einer NullReferenceException führen.

Diese Regeln unterscheiden sich von denen von Java (siehe Abschnitt 8.3.2.3 der JLS für Javas Regeln über Vorwärtsreferenzen, die restriktiver sind).

0 Stimmen

Tolle Antwort auf die Frage, die ich gestellt habe. Es scheint jedoch, dass ich nicht die Frage gestellt habe, die ich stellen wollte: Wie sieht es mit Dateiübergreifendem aus?

1 Stimmen

Ich bin kein C#-Profi (wusste nur, wo ich suchen sollte), aber schau dir msdn.microsoft.com/en-us/library/aa645612(VS.71).aspx an - "Es ist möglich, zirkuläre Abhängigkeiten zu konstruieren, die es erlauben, statische Felder mit variablen Initialisierern in ihrem Standardwertzustand zu beobachten". Hilft dir das weiter?

0 Stimmen

@Cowan nicht wirklich. Es geht nicht auf das Problem mit den Querdateien ein.

15voto

Panos Punkte 18703

Es scheint von der Reihenfolge der Zeilen abhängig zu sein. Dieser Code funktioniert:

static private List a = new List() { 1 };
static private List b = new List() { a[0] };

während dieser Code nicht funktioniert (er wirft eine NullReferenceException)

static private List a = new List() { b[0] };
static private List b = new List() { 1 };

Es gibt also offensichtlich keine Regeln für zyklische Abhängigkeiten. Es ist jedoch merkwürdig, dass der Compiler nicht meckert...


EDIT - Was passiert "über Dateien hinweg"? Wenn wir diese beiden Klassen deklarieren:

public class A {
    public static List a = new List() { B.b[0] };
}
public class B {
    public static List b = new List() { A.a[0] };
}

und versuchen, auf sie mit diesem Code zuzugreifen:

try { Console.WriteLine(B.b); } catch (Exception e) { Console.WriteLine(e.InnerException.Message.); }
try { Console.WriteLine(A.a); } catch (Exception e) { Console.WriteLine(e.InnerException.Message); }
try { Console.WriteLine(B.b); } catch (Exception e) { Console.WriteLine(e.InnerException.Message); }

erhalten wir diese Ausgabe:

Der Typinitialisierer für 'A' hat eine Ausnahme ausgelöst.
Objektverweis wurde nicht auf eine Objektinstanz festgelegt.
Der Typinitialisierer für 'A' hat eine Ausnahme ausgelöst.

Die Initialisierung von B verursacht also eine Ausnahme im statischen Konstruktor A und setzt das Feld a auf den Standardwert (null). Da a null ist, kann auch b nicht ordnungsgemäß initialisiert werden.

Wenn wir keine zyklischen Abhängigkeiten haben, funktioniert alles einwandfrei.


EDIT: Nur für den Fall, dass Sie die Kommentare nicht gelesen haben, Jon Skeet bietet eine sehr interessante Lektüre: Die Unterschiede zwischen statischen Konstruktoren und Typinitialisierern.

0 Stimmen

Dies ist sicher. Der statische Konstruktor wird aufgerufen, wenn der Typ zum ersten Mal referenziert wird.

2 Stimmen

Sei hier vorsichtig mit dem Unterschied zwischen Initialisierern von statischen Variablen und statischen Konstruktoren. Es gelten unterschiedliche Regeln, wann die Typinitialisierung erfolgt, abhängig von der Anwesenheit/Abwesenheit eines statischen Konstruktors. Siehe pobox.com/~skeet/csharp/beforefieldinit.html

2voto

Bryant Punkte 8670

Persönlich würde ich die statischen Initialisierer loswerden, da es nicht klar ist, und einen statischen Konstruktor hinzufügen, um diese Variablen zu initialisieren.

static private List a;
static private List b;

static SomeClass()
{
    a = new List() { 0 };
    b = new List() { a[0] };
}

Dann musst du nicht raten, was passiert, und du bist klar in deinen Absichten.

3 Stimmen

Beachten Sie, dass diese Code-Stücke in Bezug auf den Zeitpunkt, wann sie ausgeführt werden, nicht genau äquivalent sind: pobox.com/~skeet/csharp/beforefieldinit.html

0voto

Sam Saffron Punkte 124121

Ja, du hattest Glück. C# führt den Code in der Reihenfolge aus, in der er in der Klasse erscheint.

static private List a = new List() { 0 };
static private List b = new List() { a[0] };

Wird funktionieren aber ...

static private List b = new List() { a[0] };
static private List a = new List() { 0 };

Wird fehlschlagen.

Ich würde empfehlen, alle Abhängigkeiten an einem Ort zu platzieren, der statische Konstruktor ist der richtige Ort dafür.

static MyClass()
{
  a = new List() { 0 };
  b = new List() { a[0] };
}

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