TLDR: questo è un bug noto di vecchia data. Ne ho scritto per la prima volta nel 2010:
https://blogs.msdn.microsoft.com/ericlippert/2010/01/18/a-definite-assignment-anomaly/
È innocuo e puoi tranquillamente ignorarlo e congratularti con te stesso per aver trovato un bug piuttosto oscuro.
Perché il compilatore non impone che Emaildebba essere definitivamente assegnato?
Oh, lo fa, in un modo. Ha solo un'idea sbagliata di quale condizione implica che la variabile sia definitivamente assegnata, come vedremo.
Perché questo codice viene compilato se la struttura viene creata in un assembly separato, ma non viene compilata se la struttura è definita nell'assembly esistente?
Questo è il nocciolo del bug. Il bug è una conseguenza dell'intersezione di come il compilatore C # esegue un controllo delle assegnazioni definito sulle strutture e di come il compilatore carica i metadati dalle librerie.
Considera questo:
struct Foo
{
public int x;
public int y;
}
// Yes, public fields are bad, but this is just
// to illustrate the situation.
void M(out Foo f)
{
OK, a questo punto cosa sappiamo? fè un alias per una variabile di tipo Foo, quindi l'archiviazione è già stata allocata ed è sicuramente almeno nello stato in cui è uscita dall'allocatore di archiviazione. Se il chiamante ha inserito un valore nella variabile, quel valore è presente.
Cosa ci serve? Richiediamo che fsiano definitivamente assegnati in qualsiasi punto in cui il controllo lascia Mnormalmente. Quindi ti aspetteresti qualcosa di simile:
void M(out Foo f)
{
f = new Foo();
}
quale imposta f.xe f.yai loro valori predefiniti. Ma che dire di questo?
void M(out Foo f)
{
f = new Foo();
f.x = 123;
f.y = 456;
}
Anche questo dovrebbe andare bene. Ma, ed ecco il kicker, perché dobbiamo assegnare i valori predefiniti solo per farli saltare via un momento dopo? Il controllore di assegnazione definito di C # verifica se ogni campo è assegnato! Questo è legale:
void M(out Foo f)
{
f.x = 123;
f.y = 456;
}
E perché non dovrebbe essere legale? È un tipo di valore. fè una variabile e contiene già un valore valido di tipo Foo, quindi impostiamo semplicemente i campi e abbiamo finito, giusto?
Giusto. Quindi qual è il bug?
Il bug che hai scoperto è: come un risparmio sui costi, il compilatore C # non carica i metadati per i campi privati delle strutture che si trovano nelle librerie di riferimento . Quei metadati possono essere enormi , e rallenterebbe il compilatore per pochissime vittorie caricando tutto in memoria ogni volta.
E ora dovresti essere in grado di dedurre la causa del bug che hai trovato. Quando il compilatore verifica se il parametro out è definitivamente assegnato, confronta il numero di campi noti con il numero di campi definiti inizializzati e nel tuo caso conosce solo i campi pubblici zero perché i metadati del campo privato non sono stati caricati . Il compilatore conclude "zero campi obbligatori, zero campi inizializzati, siamo a posto".
Come ho detto, questo bug esiste da più di un decennio e persone come te lo riscoprono e lo segnalano di tanto in tanto. È innocuo ed è improbabile che venga risolto perché ripararlo ha un vantaggio quasi nullo ma un costo elevato per le prestazioni.
E ovviamente il bug non si ripropone per i campi privati di strutture che sono nel codice sorgente nel progetto, perché ovviamente il compilatore ha già informazioni sui campi privati a portata di mano.