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
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. –
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. –
@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. –