L’ordine di inizializzazione della class statica in C # è deterministico?

Ho fatto qualche ricerca e penso che il seguente codice sia garantito per produrre output:

BX = 7

BX = 0

AX = 1

A = 1, B = 0

static class B { public static int X = 7; static B() { Console.WriteLine("BX = " + X); X = AX; Console.WriteLine("BX = " + X); } } static class A { public static int X = BX + 1; static A() { Console.WriteLine("AX = " + X); } } static class Program { static void Main() { Console.WriteLine("A = {0}, B = {1}", AX, BX); } } 

Ho eseguito questo numero di volte e ottengo sempre l’output sopra la sezione del codice; Volevo solo verificare che non cambierà mai? Anche se testualmente, la class A e la class B sono riorganizzate?

È garantito che il primo utilizzo di un object statico attiverà l’inizializzazione dei suoi membri statici, seguito dall’istanziazione del suo costruttore statico? Per questo programma, l’utilizzo di AX in main attiverà l’inizializzazione di AX, che a sua volta inizializza BX, quindi B () e dopo aver terminato l’inizializzazione di AX, passerà ad A (). Infine, Main () emetterà AX e BX

Direttamente dall’ECMA-334:

17.4.5.1:Se nella class esiste un costruttore statico (§17.11), l’esecuzione degli inizializzatori del campo statico avviene immediatamente prima dell’esecuzione di tale costruttore statico , altrimenti gli inizializzatori del campo statici vengono eseguiti in un tempo dipendente dall’implementazione prima del primo utilizzo di un campo statico di quella class. ”

E:

17.11: l’esecuzione di un costruttore statico viene triggersta dal primo degli eventi seguenti che si verificano all’interno di un dominio dell’applicazione:

  • Viene creata un’istanza della class.
  • Tutti i membri statici della class sono referenziati.

Se una class contiene il metodo Main (§10.1) in cui inizia l’esecuzione, il costruttore statico per quella class viene eseguito prima che venga chiamato il metodo Main. Se una class contiene campi statici con inizializzatori, tali inizializzatori vengono eseguiti in ordine testuale immediatamente prima dell’esecuzione del costruttore statico (§17.4.5).

Quindi l’ordine è:

  • AX utilizzato, così chiamato static A() .
  • AX deve essere inizializzato, ma utilizza BX , quindi viene chiamato static B() .
  • BX deve essere inizializzato e inizializzato a 7. BX = 7
  • Tutti i campi statici di B sono inizializzati, quindi viene chiamato static B() . X viene stampato (“7”), quindi viene impostato su AX . A ha già iniziato ad essere inizializzato, quindi otteniamo il valore di AX , che è il valore predefinito (“quando una class è inizializzata, tutti i campi statici in quella class vengono inizialmente inizializzati al loro valore predefinito”); BX = 0 e stampato (“0”).
  • Fine dell’inizializzazione B , e il valore di AX è impostato su B.X+1 . AX = 1 .
  • Tutti i campi statici di A sono inizializzati, quindi viene chiamato il carattere static A() . AX è stampato (“1”).
  • Tornando in Main , i valori di AX e BX vengono stampati (“1”, “0”).

In realtà commenta questo nello standard:

17.4.5: È ansible che i campi statici con inizializzatori variabili siano osservati nello stato del loro valore predefinito. Tuttavia, questo è fortemente scoraggiato come una questione di stile.

Circa quattro diverse regole nelle specifiche C # sono coinvolte nel fare questa garanzia, ed è specifico per C #. L’unica garanzia offerta dal runtime .NET è che l’inizializzazione del tipo inizia prima che il tipo venga utilizzato.

  • I campi statici vengono inizializzati a zero fino a quando non viene eseguito l’inizializzatore del tipo.
  • Gli inizializzatori di campo statici vengono eseguiti immediatamente prima del costruttore statico.
  • I costruttori statici vengono chiamati alla prima chiamata del costruttore dell’istanza o al primo riferimento al membro statico.
  • Gli argomenti di questa funzione sono valutati nell’ordine da sinistra a destra.

Fare affidamento su questa è una pessima idea, perché è probabile che confondano chiunque legga il tuo codice, specialmente se hanno familiarità con linguaggi con una syntax simile che non rendono tutte e quattro le garanzie sopra riportate.

Si noti che il commento di Porges era correlato alla mia affermazione iniziale (basata sul comportamento di .NET) che le garanzie sono troppo deboli per assicurare il comportamento osservato. Porges ha ragione nel dire che le garanzie sono abbastanza forti, ma in realtà è implicata una catena molto più complessa di quanto suggerisca.

Potresti essere interessato a sapere che è persino ansible assegnare valori a un campo tra l’inizializzazione predefinita e l’inizializzazione delle variabili.

  private static int b = Foo(); private static int a = 4; private static int Foo() { Console.WriteLine(a); a = 3; Console.WriteLine(a); return 2; } public static void Main() { Console.WriteLine(a); } 

uscite

 0 3 4 

L’inizializzazione deterministica dei membri statici è infatti garantita … ma non è in “ordine testuale”. Inoltre, potrebbe non essere eseguito in modo completamente pigro (cioè solo quando la variabile statica viene prima referenziata). Tuttavia, nel tuo esempio usando numeri interi non farebbe differenza.

In alcuni casi, è auspicabile ottenere un’inizializzazione pigra (in particolare con costosi Singleton), nel qual caso a volte bisogna saltare attraverso alcuni cerchi per farlo bene.