Domanda

Ho una domanda in merito costruttori di tipi all'interno di un tipo Value.Questa domanda è stata ispirata da qualcosa che Jeffrey Richter ha scritto in CLR tramite C# 3rd ed, dice (a pagina 195 - capitolo 8) che non dovresti mai effettivamente definire un costruttore di tipo all'interno di un tipo di valore poiché ci sono momenti in cui CLR non chiamerà Esso.

Quindi, ad esempio (beh... l'esempio di Jeffrey Richters in realtà), non riesco a capire, anche guardando l'IL, perché il costruttore del tipo non viene chiamato nel codice seguente:

internal struct SomeValType
{
    static SomeValType()
    {
        Console.WriteLine("This never gets displayed");
    }
    public Int32 _x;
}
public sealed class Program
{
    static void Main(string[] args)
    {
        SomeValType[] a = new SomeValType[10];
        a[0]._x = 123;
        Console.WriteLine(a[0]._x);     //Displays 123
    }
}

Quindi, applicando le seguenti regole per i costruttori di tipi, non riesco proprio a capire perché il costruttore di tipi di valore sopra non venga chiamato affatto.

  1. Posso definire un costruttore di tipi di valori statici per impostare lo stato iniziale del tipo.
  2. Un tipo non può avere più di un costruttore: non ne esiste uno predefinito.
  3. I costruttori di tipi sono implicitamente privati
  4. Il compilatore JIT controlla se il costruttore del tipo è già stato eseguito in questo AppDomain.In caso contrario, emette la chiamata nel codice nativo, altrimenti non lo fa poiché sa che il tipo è già "inizializzato".

Quindi... non riesco proprio a capire perché non riesco a vedere la costruzione di questo tipo di array.

La mia ipotesi migliore sarebbe che potrebbe essere:

  1. Il modo in cui CLR costruisce una matrice di tipi.Avrei pensato che il costruttore statico sarebbe stato chiamato quando sarebbe stato creato il primo elemento
  2. Il codice nel costruttore non inizializza alcun campo statico, quindi viene ignorato.Ho sperimentato l'inizializzazione di campi statici privati ​​all'interno del costruttore, ma il campo rimane con il valore predefinito 0, pertanto il costruttore non viene chiamato.
  3. Oppure... il compilatore sta in qualche modo ottimizzando la chiamata del costruttore a causa dell'impostazione dell'Int32 pubblico, ma nella migliore delle ipotesi è un'ipotesi confusa!!

A parte le migliori pratiche ecc., ne sono semplicemente molto incuriosito perché voglio essere in grado di vedere di persona perché non viene chiamato.

MODIFICARE:Ho aggiunto una risposta alla mia domanda qui sotto, solo una citazione di ciò che dice Jeffrey Richter al riguardo.

Se qualcuno avesse qualche idea sarebbe geniale.Grazie mille James

È stato utile?

Soluzione

IL Specifica Microsoft C#4 è leggermente cambiato rispetto alle versioni precedenti e ora riflette in modo più accurato il comportamento che vediamo qui:

11.3.10 Costruttori statici

Di seguito sono riportati i costruttori statici per gli struct la maggior parte delle stesse regole delle classi.L'esecuzione di un costruttore statico per un tipo struct viene attivato dal metodo si verifica il primo dei seguenti eventi All'interno di un dominio applicazione:

  • Viene fatto riferimento a un membro statico del tipo struct.
  • Viene chiamato un costruttore esplicitamente dichiarato del tipo struct.

La creazione di valori predefiniti (Sezione 11.3.4) dei tipi struct non Attivare il costruttore statico.(Un Ne è un esempio il valore iniziale di elementi in una matrice.)

IL Specifica ECMA e il Specifica Microsoft C#3 entrambi hanno un evento extra in quell'elenco:"Viene fatto riferimento a un membro di istanza del tipo struct". Quindi sembra che C#3 contravvenga alle sue stesse specifiche qui.Le specifiche C#4 sono state maggiormente allineate al comportamento effettivo di C#3 e 4.

MODIFICARE...

Dopo ulteriori indagini, sembra che praticamente tutti i membri dell'istanza accedano tranne l'accesso diretto al campo attiverà il costruttore statico (almeno nelle attuali implementazioni Microsoft di C#3 e 4).

Pertanto le attuali implementazioni sono più strettamente correlate alle regole fornite nelle specifiche ECMA e C#3 rispetto a quelle nelle specifiche C#4:le regole C#3 vengono implementate correttamente quando si accede a tutti i membri dell'istanza tranne campi;le regole C#4 lo sono soltanto implementato correttamente per l'accesso al campo.

(Le diverse specifiche sono tutte concordanti - e apparentemente implementate correttamente - quando si tratta delle regole relative all'accesso ai membri statici e ai costruttori esplicitamente dichiarati.)

Altri suggerimenti

Dal §18.3.10 della norma (vedi anche Il linguaggio di programmazione C# libro):

L'esecuzione di un costruttore statico per una struttura viene attivata dal primo dei seguenti eventi che si verificano all'interno di un dominio dell'applicazione:

  • Un membro di istanza della struttura è referenziato.
  • Un membro statico di Viene fatto riferimento alla struttura.
  • Un costruttore dichiarato in modo esplicito dell'oggetto struct viene chiamato.

[Nota:La creazione dei valori predefiniti (Sezione 18.3.4) di struct types non attiva l'istruzione statica costruttore.(Un esempio di questo è il valore iniziale degli elementi in un matrice.) nota finale]

Quindi sono d'accordo con te sul fatto che le ultime due righe del tuo programma dovrebbero attivare ciascuna la prima regola.

Dopo i test, il consenso sembra essere che si attiva in modo coerente per metodi, proprietà, eventi e indicizzatori.Ciò significa che è corretto per tutti i membri dell'istanza esplicita tranne campi.Quindi, se le regole C# 4 di Microsoft venissero scelte come standard, la loro implementazione passerebbe da per lo più giusta a per lo più sbagliata.

Inserisco questo semplicemente come "risposta" in modo da poter condividere ciò che lo stesso Richter ha scritto al riguardo (qualcuno ha un collegamento per le ultime specifiche CLR tra l'altro, è facile ottenere l'edizione del 2006 ma trovarla un po' più difficile da trovare prendi l'ultima):

Per questo tipo di cose, di solito è meglio guardare le specifiche CLR piuttosto che le specifiche C#.Le specifiche CLR dicono:

4.Se non contrassegnato come BeforeFieldInit, il metodo inizializzatore di quel tipo viene eseguito (ovvero viene attivato da):

• primo accesso a qualsiasi campo statico di quel tipo, oppure

• prima invocazione di qualsiasi metodo statico di quel tipo o

• prima invocazione di qualsiasi istanza o metodo virtuale di quel tipo se è un tipo valore o

• prima invocazione di qualsiasi costruttore per quel tipo.

Poiché nessuna di queste condizioni è soddisfatta, lo è il costruttore statico non invocato.Le uniche parti difficili da notare sono che "_x" è un campo di istanza, non un campo statico, e la costruzione di un array di strutture lo fa non richiamare qualsiasi costruttore di istanza sugli elementi dell'array.

Altro esempio interessante:

   struct S
    {
        public int x;
        static S()
        {
            Console.WriteLine("static S()");
        }
        public void f() { }
    }

    static void Main() { new S().f(); }

Aggiornamento: la mia osservazione è che, a meno che non venga utilizzato lo stato statico, il costruttore statico non verrà mai toccato, qualcosa che il runtime sembra decidere e non si applica ai tipi di riferimento.Ciò solleva la domanda se si tratta di un bug rimasto perché ha un impatto minimo, è previsto dalla progettazione o è un bug in sospeso.

Aggiornamento 2: personalmente, a meno che tu non stia facendo qualcosa di strano nel costruttore, questo comportamento dal runtime non dovrebbe mai causare problemi.Non appena accedi allo stato statico, si comporta correttamente.

Aggiornamento3: in seguito a un commento di LukeH e facendo riferimento alla risposta di Matthew Flaschen, l'implementazione e la chiamata del proprio costruttore nella struttura attivano anche la chiamata del costruttore statico.Ciò significa che in uno dei tre scenari il comportamento non è quello indicato sulla scatola.

Ho appena aggiunto una proprietà statica al tipo e ho effettuato l'accesso a tale proprietà statica, denominata costruttore statico.Senza l'accesso alla proprietà statica, semplicemente creando una nuova istanza del tipo, il costruttore statico non veniva chiamato.

internal struct SomeValType
    {
        public static int foo = 0;
        public int bar;

        static SomeValType()
        {
            Console.WriteLine("This never gets displayed");
        }
    }

    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            // Doesn't hit static constructor
            SomeValType v = new SomeValType();
            v.bar = 1;

            // Hits static constructor
            SomeValType.foo = 3;
        }
    }

Una nota in questo collegamento specifica che i costruttori statici lo sono non chiamato quando si accede semplicemente alle istanze:

http://www.jaggersoft.com/pubs/StructsVsClasses.htm#default

Immagino che tu stia creando un ARRAY del tuo tipo di valore.Quindi la nuova parola chiave verrebbe utilizzata per inizializzare la memoria per l'array.

È valido dirlo

SomeValType i;
i._x = 5;

senza alcuna nuova parola chiave da nessuna parte, che è essenzialmente ciò che stai facendo qui.Se SomeValType fosse un tipo di riferimento, dovresti inizializzare ogni elemento dell'array con

array[i] = new SomeRefType();

Questo è un comportamento folle dell'attributo "beforefieldinit" in MSIL.Influisce anche su C++/CLI, ho presentato una segnalazione di bug in cui Microsoft ha spiegato molto bene perché il comportamento è così com'è e ho sottolineato più sezioni nello standard del linguaggio che non erano d'accordo / necessitano di essere aggiornate per descrivere il comportamento effettivo .Ma non è visibile pubblicamente.Ad ogni modo, ecco l'ultima parola al riguardo da parte di Microsoft (discutendo una situazione simile in C++/CLI):

Dal momento che stiamo invocando lo standard qui, la riga della Partizione I, 8.9.5 dice questo:

Se contrassegnato come BeforeFieldInit, l'oggetto viene eseguito il metodo inizializzatore del tipo al primo accesso, o qualche tempo prima, a qualsiasi campo statico definito per tale digitare.

Questa sezione entra effettivamente nei dettagli su come un'implementazione del linguaggio può scegliere di impedire il comportamento che stai descrivendo.C++/CLI sceglie di non farlo per, piuttosto, permettono al programmatore di farlo, se lo desiderano.

Fondamentalmente, dal momento che il codice sottostante ha assolutamente nessun campo statico, il JIT è completamente corretto semplicemente non richiamando costruttori di classi statiche.

Lo stesso comportamento è quello che stai vedendo, anche se in una lingua diversa.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top