2008-10-08 13 views
15

Während auf einer C# app arbeitet Ich habe gerade bemerkt, dass an mehreren Stellen statische Initialisierer haben Abhängigkeiten voneinander wie folgt aus:Reihenfolge der statischen Konstruktoren/initializers in C#

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

Ohne besondere etwas zu tun, das funktioniert. Ist das nur Glück? Hat C# Regeln, um das zu beheben?

Bearbeiten: (re: Panos) In einer Datei scheint lexical Reihenfolge König zu sein? Was ist mit Dateien?

Bei der Suche habe ich versucht, eine zyklische Abhängigkeit wie folgt aus:

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

und das Programm nicht die gleiche (den Test Anzug auf der ganzen Linie gescheitert und ich sah nicht weiter) laufen.

+0

Ich denke, dass über Dateien (d. H. Über verschiedene Klassen) wird es gleich passieren. Während der Typinitialisierung von Klasse A wird Klasse B aufgefordert, zu initialisieren, und Klasse B wird einen Nullverweis auf Klasse A finden. – Panos

+0

Nun, über Dateien der gleichen Klasse (Partialklasse), ist wahrscheinlich bis zum Pre-Prozessor zu festzustellen, ob es fehlschlägt oder nicht. – Panos

+0

Wenn also A B.b referenziert, dann A.a, der sich einleitet, wird B.b stoßen? – BCS

Antwort

13

Es scheint auf die Reihenfolge der Zeilen zu hängen. Dieser Code funktioniert:

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

während dieser Code nicht funktioniert (es wirft ein NullReferenceException)

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

Also, natürlich keine Regeln für die zyklische Abhängigkeit existieren. Es ist jedoch eigenartig, dass der Compiler beschwert sich nicht ...


EDIT - Was „über Dateien“ passiert?

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

und versuchen, sie mit diesem Code zuzugreifen: Wenn wir diese zwei Klassen deklarieren

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

bekommen wir diese Ausgabe:

The type initializer for 'A' threw an exception. 
Object reference not set to an instance of an object. 
The type initializer for 'A' threw an exception. 

So ist die Initialisierung von B bewirkt ein Ausnahme im statischen Konstruktor A und linken Feld a mit dem Standardwert (null). Da anull ist, kann b auch nicht richtig initialisiert werden.

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


EDIT: Gerade falls Sie nicht die Kommentare gelesen haben, bietet Jon Skeet eine sehr interessante Lektüre: The differences between static constructors and type initializers.

+0

Interessant, also Initialisierung geschieht nicht, bis Zeug referenziert wird ... – BCS

+0

Das ist sicher. Der statische Konstruktor wird aufgerufen, wenn auf den Typ zum ersten Mal verwiesen wird. – Panos

+2

Achten Sie hier auf den Unterschied zwischen statischen Variableninitialisierungen und statischen Konstruktoren. Es gibt verschiedene Regeln bei der Typinitialisierung, die auf dem Vorhandensein/Fehlen eines statischen Konstruktors basieren. Siehe http://pobox.com/~skeet/csharp/beforefieldinit.html –

0

Ja, Sie hatten Glück. C# scheint den Code in der Reihenfolge auszuführen, in der er in der Klasse erscheint.

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

Funktioniert aber ...

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

fehl.

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

2

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<int> a; 
static private List<int> b; 

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

Dann müssen Sie nicht raten, was vor sich geht und Sie sind in Ihren Absichten klar.

+2

Beachten Sie, dass diese Teile des Codes nicht * genau * äquivalent sind, wenn sie ausgeführt werden: http://pobox.com/~skeet/csharp /beforefieldinit.html –

14

Siehe section 10.4 of the C# spec für die Regeln hier:

, wenn eine Klasse initialisiert wird, werden alle statischen Felder in dieser Klasse werden zunächst initialisiert auf die Standardwerte, und dann werden die statischen Feld Initialisierungen werden in Text Reihenfolge ausgeführt. Wenn eine Instanz einer Klasse erstellt wird, werden alle Instanzfelder in dieser Instanz ebenfalls zuerst auf ihre Standardwerte initialisiert, und dann werden die Instanzfeldinitialisierungen in Textfolge ausgeführt. Es ist möglich, statische Felder mit variablen Initialisatoren in ihrem Default-Wert-Zustand zu beobachten. Dies wird jedoch als Stilrichtung stark abgeraten.

Also mit anderen Worten, in Ihrem Beispiel ‚b‘ in den Grundzustand (null) initialisiert und so den Verweis auf sie in der Initialisierungsliste von ‚a‘ legal ist, aber in einer Nullreferenceexception führen würde.

Diese Regeln unterscheiden sich von Java (siehe Java-Regeln für Forward-Referenzen, die restriktiver sind, siehe section 8.3.2.3 of the JLS).

+0

* Große Antwort * auf die Frage, die ich gestellt habe. Es scheint jedoch, dass ich nicht die Frage stellte, die ich wollte: Was ist mit Dateien? – BCS

+0

Ich bin kein C# whiz (wusste nur, wo zu suchen), aber check out http://msdn.microsoft.com/en-us/library/aa645612(VS.71).aspx - "Es ist möglich zu konstruieren zirkuläre Abhängigkeiten, die es erlauben, statische Felder mit variablen Initializern in ihrem Default-Wert-Zustand zu beobachten ". Hilft das? – Cowan

+0

@Cowan nicht wirklich. Das Problem mit der Kreuzdatei wird nicht behandelt. – BCS