2014-12-15 17 views
32

Considerando il seguente codice:intesa C requisiti # campo di inizializzazione

public class Progressor 
{ 
    private IProgress<int> progress = new Progress<int>(OnProgress); 

    private void OnProgress(int value) 
    { 
     //whatever 
    } 
} 

Questo dà il seguente errore di compilazione:

A field initializer cannot reference the non-static field, method, or property 'Progressor.OnProgress(int)'

Capisco la restrizione si lamenta, ma non capisco perché è un problema, ma il campo può essere inizializzato nel costruttore invece come segue:

public class Progressor 
{ 
    private IProgress<int> progress; 

    public Progressor() 
    { 
     progress = new Progress<int>(OnProgress); 
    } 

    private void OnProgress(int value) 
    { 
     //whatever 
    } 
} 

Qual è la differenza in C# rispetto all'inizializzazione del campo rispetto all'inizializzazione del costruttore che richiede questa restrizione?

+7

L'unica cosa che mi viene in mente è il fatto, che quando l'inizializzazione di un campo particolare esegue tutti gli altri campi possono o non possono ancora essere inizializzati. L'esecuzione di un metodo di istanza in quel punto, che consentirebbe l'accesso a tutti i membri dell'istanza, consente di accedere ad altri campi che non erano ancora inizializzati. Questa è una potenziale fonte di problemi. Perché è consentito nel costruttore? Poiché tutti i campi vengono inizializzati prima dell'esecuzione del codice del costruttore. – MarcinJuraszek

+0

@MarcinJuraszek Questa è stata la mia comprensione, anche se è possibile rendere l'argomento che il runtime potrebbe semplicemente inizializzare tutti i campi sui valori predefiniti (ad esempio 0, null, ecc.), Quindi eseguire le inizializzazioni del campo, quindi eseguire il costruttore applicabile. – sdzivanovich

+2

@sdzivanovich Questo è esattamente ciò che accade: tutti i campi vengono inizializzati con valori predefiniti prima che avvenga l'inizializzazione.Ma ancora non risolve il problema del codice di inizializzazione essendo imprevedibile (vedi la mia risposta). – MarcinJuraszek

risposta

22

L'inizializzazione del campo viene eseguita prima della chiamata del costruttore della classe base, quindi non è un oggetto valido. Qualsiasi chiamata di metodo con this come argomento a questo punto porta a codice non verificabile e genera VerificationException se il codice non verificabile non è consentito. Ad esempio: nel codice di sicurezza trasparente.

  • 10.11.2 Instance variable initializers
    When an instance constructor has no constructor initializer, or it has a constructor initializer of the form base(...), that constructor implicitly performs the initializations specified by the variable-initializers of the instance fields declared in its class. This corresponds to a sequence of assignments that are executed immediately upon entry to the constructor and before the implicit invocation of the direct base class constructor. The variable initializers are executed in the textual order in which they appear in the class declaration.
  • 10.11.3 Constructor execution
    Variable initializers are transformed into assignment statements, and these assignment statements are executed before the invocation of the base class instance constructor. This ordering ensures that all instance fields are initialized by their variable initializers before any statements that have access to that instance are executed.
+0

Verificato da uno sviluppatore compilatore C# [qui] (http://blogs.msdn.com/b/ericlippert/archive/2008/02/15/why-do-initializers-run-in-the-opposite-order-as -constructors-part-one.aspx) e [qui] (http://blogs.msdn.com/b/ericlippert/archive/2008/02/18/why-do-initializers-run-in-the-opposite- order-as-constructors-part-two.aspx) – Yishai

+3

Citazione necessaria per il tuo reclamo che porterebbe a un 'VerificationException'. Effettivamente testarlo in una nuova applicazione console (generare manualmente l'IL corretto invece di provarlo da C#) mostra che funziona bene. È certamente possibile che fallisca in alcuni ambienti, ma non in altri, ma la tua risposta dovrebbe comunque dare qualche indicazione sul fatto che non fallirà necessariamente. – hvd

+1

@hvd Non sono sicuro di poter fornire un riferimento alle specifiche (mi piacerebbe trovare le specifiche sul processo di verifica .NET). Nel mio metodo di prova richiamo con 'this' come argomento prima che la chiamata del costruttore della classe base lanci sempre' VerificationException' nel codice di sicurezza trasparente. Per il codice di sicurezza critico non viene generata alcuna eccezione ma PEVerify mostra errore: " ref ('this' ptr) 'ClassName'" invece di "ref 'ClassName'". Lo stato di "this" rende qualsiasi chiamata non verificabile. – PetSerAl

9

Sezione 10.5.5.2: campo Istanza di inizializzazione descrive questo comportamento:

A variable initializer for an instance field cannot reference the instance being created. Thus, it is a compile-time error to reference this in a variable initializer, as it is a compile-time error for a variable initializer to reference any instance member through a simple-name

Questo comportamento si applica al codice perché OnProgress è un implicito riferimento all'istanza in fase di creazione.

+5

Bene. Penso che OP sappia perché sta ottenendo ed errore. La domanda è più sul perché è necessaria questa restrizione. – MarcinJuraszek

+4

@MarcinJuraszek: Concesso, ma sarà difficile rispondere senza un designer di linguaggio C# –

7

La risposta è più o meno, i progettisti di C# l'hanno preferito in questo modo.

Poiché tutti gli inizializzatori di campo sono tradotti in istruzioni nel costruttore (o nei costruttori) che precedono qualsiasi altra istruzione nel costruttore, non vi è alcun motivo tecnico per cui ciò non dovrebbe essere possibile. Quindi è una scelta di design.

La cosa buona di un costruttore è che chiarisce in quale ordine vengono eseguiti gli incarichi.

Si noti che con i membri static, i progettisti C# hanno scelto diversamente. Ad esempio:

static int a = 10; 
static int b = a; 

è permesso, e diverso da questo (accettati anche):

static int b = a; 
static int a = 10; 

che possono confondere.

Se si effettua:

partial class C 
{ 
    static int b = a; 
} 

e altrove (in altri file):

partial class C 
{ 
    static int a = 10; 
} 

io non penso anche che è ben definito che cosa accadrà.

Naturalmente per il vostro esempio particolare con i delegati in un inizializzatore campo di istanza:

Action<int> progress = OnProgress; // ILLEGAL (non-static method OnProgress) 

non c'è davvero alcun problema in quanto non è una lettura o un'invocazione del membro non statico. Piuttosto, vengono utilizzate le informazioni sul metodo e non dipende da alcuna inizializzazione. Ma secondo la specifica del linguaggio C# è ancora un errore in fase di compilazione.

+0

L'ultimo esempio con il delegato è in realtà sbagliato. Questo compito non usa semplicemente le informazioni sul metodo, ma deve costruire un'istanza delegata che coinvolge "questo" e quindi lancia la stessa eccezione. – Bunny83

+1

@ Bunny83 Sì, lo so. Ho provato a modificare il mio post ora per renderlo più chiaro. Nell'ultimo esempio il compilatore dovrebbe fare qualcosa _equivalent_ a 'progress = Delegate.CreateDelegate (typeof (Action ), this," OnProgress ")', quindi sicuramente implica il passaggio di un riferimento a 'this' (che sarà mantenuto dal delegato) proprio come dici tu. E questo non è permesso. Anche se il riferimento non sarà "seguito" fino a dopo. –

18

Tutto nella mia risposta è solo il mio pensiero su "perché sarebbe pericoloso consentire questo tipo di accesso". Non so se questo è il vero motivo per cui è stato limitato.

C# spec dice, che l'inizializzazione di campo avviene nei campi di ordine sono dichiarati nella classe:

10.5.5.2. Instance field initialization

The variable initializers are executed in the textual order in which they appear in the class declaration.

Ora, diciamo che il codice che hai citato è possibile - è possibile chiamare metodo di istanza da campo inizializzazione. Potrebbe rendere possibile il seguente codice:

public class Progressor 
{ 
    private string _first = "something"; 
    private string _second = GetMyString(); 

    private string GetMyString() 
    { 
     return "this is really important string"; 
    } 
} 

Fin qui tutto bene. Ma cerchiamo di abusare di questo potere un po ':

public class Progressor 
{ 
    private string _first = "something"; 
    private string _second = GetMyString(); 
    private string _third = "hey!"; 

    private string GetMyString() 
    { 
     _third = "not hey!"; 
     return "this is really important string"; 
    } 
} 

Quindi, _second di ottenere inizializzato prima _third. GetMyString corre, _third get "non è hey!" valore assegnato, ma in seguito viene eseguito l'inizializzazione del proprio campo ed è impostato su "hey!". Non molto utile né leggibile, giusto?

Si potrebbe anche usare _third entro GetMyString metodo:

public class Progressor 
{ 
    private string _first = "something"; 
    private string _second = GetMyString(); 
    private string _third = "hey!"; 

    private string GetMyString() 
    { 
     return _third.Substring(0, 1); 
    } 
} 

Che cosa vi aspettate di essere valore _second? Bene, prima che l'inizializzazione del campo esegua tutti i campi ottengono valori predefiniti. Per string sarebbe null, quindi riceverai inaspettato NullReferenceException.

Quindi, imo, i progettisti hanno deciso che è solo più facile impedire alle persone di fare questo tipo di errori.

Si potrebbe dire, OK, non consentire l'accesso alle proprietà e ai metodi di chiamata, ma consenti l'utilizzo di campi dichiarati sopra quello da cui si desidera accedervi. Qualcosa di simile:

public class Progressor 
{ 
    private string _first = "something"; 
    private string _second = _first.ToUpperInvariant(); 
} 

ma non

public class Progressor 
{ 
    private string _first = "something"; 
    private string _second = _third.ToUpperInvariant(); 
    private string _third = "another"; 
} 

Questo è sembra utile e sicuro. Ma c'è ancora un modo per abusarne!

public class Progressor 
{ 
    private Lazy<string> _first = new Lazy<string>(GetMyString); 
    private string _second = _first.Value; 

    private string GetMyString() 
    { 
     // pick one from above examples 
    } 
} 

E tutti i problemi con i metodi capita di tornare di nuovo.

+0

Ma se insisti, puoi ancora scrivere '_first =" qualcosa "; _second = this.GetMyString(); _third = "hey!"; 'all'interno di un costruttore di istanze. Ovviamente si potrebbe ottenere un 'NullReferenceException' se' GetMyString' ha usato un campo nullo. –

+0

Ma scrivere che in un costruttore rende più ovvio cosa succederà. – MarcinJuraszek

+1

Sono d'accordo. Immagino anche che sia il motivo per cui non lo permetteranno con gli inizializzatori di campo. Una cosa interessante è che tutti gli esempi "pericolosi" che fornisci sopra, diventano perfettamente legali se rendi 'statici 'tutti i membri della classe. Questo è quello che ho cercato di descrivere nella mia risposta. –