2015-04-17 16 views
7

Ich habe den relevanten Abschnitt von C# -Sprachspezifikation (v5.0) durchgelesen, aber ich finde den Teil, der für das, was ich sehe, nicht relevant ist.C# Statischer Initialisierer mit (und ohne) gemischten statischen Konstruktoren

Wenn Sie einen Lauf des Codes unten haben, werden Sie die folgende Ausgabe sehen, was ist, was ich erwarten:

using System; 

class Test { 
    static int count = 0; 
    static void Main() { 
     Console.WriteLine("In Main(), A.X=" + A.X); 
    } 

    public static int F(string message) { 
     Console.WriteLine(message); 
     A.X = ++count; 

     Console.WriteLine("\tA.X has been set to " + A.X); 
     B.Y = ++count; 

     Console.WriteLine("\tB.Y has been set to " + B.Y); 
     return 999; 
    } 
} 
class A { 
    static A() { } 
    public static int U = Test.F("Init A.U"); 
    public static int X = Test.F("Init A.X"); 
} 

class B { 
    static B() { } 
    public static int R = Test.F("Init B.R"); 
    public static int Y = Test.F("Init B.Y"); 
} 

Die Ausgabe lautet:

Init A.U 
    A.X has been set to 1 
Init B.R 
    A.X has been set to 3 
    B.Y has been set to 4 
Init B.Y 
    A.X has been set to 5 
    B.Y has been set to 6 
    B.Y has been set to 2 
Init A.X 
    A.X has been set to 7 
    B.Y has been set to 8 
In Main(), A.X=999 

Dies genau ist die Ausgabe, die ich erwarten würde. Beachten Sie insbesondere, dass die Methode F() zwar mit dem Parameter "Init A.U" ausgeführt wird, aber wieder aufgerufen wird (unterbrochen wird, wenn Sie möchten), sobald der Verweis auf B.Y auftritt, wodurch die statischen Initialisierer von B ausgeführt werden. Sobald der statische Konstruktor von B abgeschlossen ist, kehren wir wieder zum Aufruf von A.U von F() zurück, was bedeutet, dass B.Y auf 6 und dann auf 2 gesetzt wird. Also macht diese Ausgabe hoffentlich für jeden Sinn.

Hier ist, was ich nicht verstehen: Wenn Sie B statische Konstruktor auf Kommentar, dies ist die Ausgabe sehen Sie:

Init B.R 
     A.X has been set to 1 
     B.Y has been set to 2 
Init B.Y 
     A.X has been set to 3 
     B.Y has been set to 4 
Init A.U 
     A.X has been set to 5 
     B.Y has been set to 6 
Init A.X 
     A.X has been set to 7 
     B.Y has been set to 8 
In Main(), A.X=999 

Abschnitte 10.5.5.1 und 10.12 des C# Spec (v5.0) zeigen Der statische Konstruktor von A (und seine statischen Initialisierer) werden ausgelöst, wenn "alle statischen Member der Klasse referenziert werden". Aber hier haben wir A.X referenziert von innerhalb von F() und A's statischer Konstruktor ist nicht ausgelöst (da seine statische Initialisierer nicht ausgeführt werden).

Da A einen statischen Konstruktor hat, würde ich erwarten, dass diese Initialisierer den "Init BR" -Aufruf zu F() ausführen (und unterbrechen), genau wie Bs statischer Konstruktor den Aufruf von F() in "Init AU" unterbrach Rufen Sie an, dass ich am Anfang zeigte.

Kann mir jemand erklären? Af face value sieht aus wie ein Verstoß gegen die Spezifikation, es sei denn, es gibt einen anderen Teil der Spezifikation, der dies erlaubt.

Dank

+2

Sobald Sie den statischen Konstruktor entfernen, sind alle Wetten deaktiviert, wenn der Typinitialisierer ausgeführt wird. Es kann zu * jeder * Zeit vor dem Zugriff auf die statischen Felder ausgeführt werden. Dies wird in Abschnitt 10.5.5.1 beschrieben. Es wird ausgeführt, bevor Sie auf die Felder zugreifen, damit keine Verletzung angezeigt wird. Warum es läuft, bevor die statischen Initiatoren von A laufen, ist eine interessante Frage. Ich glaube, in .NET 4.5 führen beforefieldinit-Klassen ihren Typinitialisierer aus, wenn das JIT die Typinformation lädt, aber wenn es statischen Konstruktor gibt, wird es ausgeführt, wenn auf die Elemente tatsächlich zugegriffen wird. Hier erfordert JITting 'Test.F', dass die Informationen vom Typ B geladen werden. –

+0

Zusätzlich zu Mikes Kommentaren ist es bemerkenswert, dass sich dieses Verhalten im Laufe der Zeit geändert hat. Schauen Sie sich zum Beispiel die Beobachtungen von Jon Skeet in seinem Artikel [Änderung der Typinitialisierung in .NET 4.0] an (http://codeblog.jonskeet.uk/2010/01/26/type-initialization-changes-in-net- 4-0 /). Die Quintessenz ist, dass mit Ausnahme einiger spezifischer Szenarien die statische Initialisierung der Reihenfolge nicht garantiert ist und Ihr Code nicht auf eine bestimmte Reihenfolge zählen sollte. Glücklicherweise garantiert die spec_does_init-Reihenfolge, wann Sie es erwarten würden, zB wenn ein Typ von einem anderen abhängt usw. –

+0

@mike z - Bedenken Sie, dass ich den statischen c'tor aus B entfernt habe, nicht aus A. Die Laufzeit Es ist sicherlich frei, die statischen Initiatoren von B in Ruhe zu betreiben. Da A jedoch ein statisches c'tor hat (und daher kein vorheriges Feld hat), muss A's statischer c'tor gemäß der Spezifikation (Abschnitte 10.5.5.1 und 10.12) durch den Verweis auf AX innerhalb von F() ausgelöst werden. Aber das passiert nicht. A.X wird w/in F() referenziert, aber A's statischer c'tor wird nicht ausgeführt. Vielleicht vermisse ich etwas, aber von dem, was ich sehe, scheint es die Spezifikation zu verletzen. –

Antwort

2

Ich glaube, ich sehe, was hier vor sich geht, obwohl ich für keine gute Erklärung, warum es so ist.

Das Testprogramm ist ein wenig zu grob, um zu sehen, was passiert. Lassen Sie uns eine kleine Anpassung vornehmen:

class Test { 
    static int count = 0; 
    static void Main() { 
     Console.WriteLine("In Main(), A.X=" + A.X); 
    } 

    public static int F(string message) { 
     Console.WriteLine("Before " + message); 
     return FInternal(message); 
    } 

    private static int FInternal(string message) { 
     Console.WriteLine("Inside " + message); 
     A.X = ++count; 

     Console.WriteLine("\tA.X has been set to " + A.X); 
     B.Y = ++count; 

     Console.WriteLine("\tB.Y has been set to " + B.Y); 
     return 999; 
    } 
} 
class A { 
    static A() { } 
    public static int U = Test.F("Init A.U"); 
    public static int X = Test.F("Init A.X"); 
} 

class B { 
    static B() { } 
    public static int R = Test.F("Init B.R"); 
    public static int Y = Test.F("Init B.Y"); 
} 

Die Ausgabe an, dass in der Frage ähnlich ist, aber mit mehr Details:

Before Init A.U 
Inside Init A.U 
    A.X has been set to 1 
Before Init B.R 
Inside Init B.R 
    A.X has been set to 3 
    B.Y has been set to 4 
Before Init B.Y 
Inside Init B.Y 
    A.X has been set to 5 
    B.Y has been set to 6 
    B.Y has been set to 2 
Before Init A.X 
Inside Init A.X 
    A.X has been set to 7 
    B.Y has been set to 8 
In Main(), A.X=999 

Nichts überraschend hier. Entfernen Sie den statischen Konstruktor von B, und Sie erhalten Folgendes:

Before Init A.U 
Before Init B.R 
Inside Init B.R 
    A.X has been set to 1 
    B.Y has been set to 2 
Before Init B.Y 
Inside Init B.Y 
    A.X has been set to 3 
    B.Y has been set to 4 
Inside Init A.U 
    A.X has been set to 5 
    B.Y has been set to 6 
Before Init A.X 
Inside Init A.X 
    A.X has been set to 7 
    B.Y has been set to 8 
In Main(), A.X=999 

Jetzt ist das interessant. Wir können sehen, dass die ursprüngliche Ausgabe irreführend war. Wir beginnen tatsächlich damit, A.U zu initialisieren. Das ist nicht überraschend, da A zuerst initialisiert werden sollte, da in Main auf A.X zugegriffen wird. Der nächste Teil ist interessant. Es sieht so aus, als wenn B keinen statischen Konstruktor hat, unterbricht die CLR die Methode, die auf die Felder von B (FInternal) zugreifen wird, bevor es die Methode eingibt. Vergleichen Sie dies mit dem anderen Fall. Dort wurde die Initialisierung von B verzögert, bis wir tatsächlich auf die Felder von B zugegriffen haben.

Ich bin nicht ganz sicher, warum Dinge in dieser bestimmten Reihenfolge getan werden, aber Sie können sehen, dass der Grund für die Initialisierung von B nicht unterbrochen wird, um A zu initialisieren ist, dass die Initialisierung von A bereits begonnen hat.

Verwandte Themen