Perché le parentesi del costruttore di inizializzatore di oggetti C # 3.0 sono facoltative?

Sembra che la syntax di inizializzatore dell’object C # 3.0 consenta di escludere la coppia di parentesi aperta / chiusa nel costruttore quando esiste un costruttore senza parametri. Esempio:

var x = new XTypeName { PropA = value, PropB = value }; 

Al contrario di:

 var x = new XTypeName() { PropA = value, PropB = value }; 

Sono curioso di XTypeName perché la coppia parentesi aperta / chiusa del costruttore sia facoltativa qui dopo XTypeName ?

Questa domanda è stata object del mio blog il 20 settembre 2010 . Le risposte di Josh e Chad (“non aggiungono alcun valore quindi perché richiederle?” E “eliminare la ridondanza”) sono fondamentalmente corrette. Per incarnare un po ‘di più:

La funzione di consentire di elidere l’elenco degli argomenti come parte della “feature più grande” degli inizializzatori di oggetti ha soddisfatto la nostra barra per le funzionalità “zuccherine”. Alcuni punti che abbiamo considerato:

  • il costo della progettazione e delle specifiche era basso
  • stavamo per cambiare radicalmente il codice parser che gestisce comunque la creazione dell’object; il costo di sviluppo aggiuntivo di rendere facoltativo l’elenco dei parametri non era grande rispetto al costo della funzione più grande
  • il carico di prova era relativamente piccolo rispetto al costo della caratteristica più ampia
  • il carico di documentazione era relativamente piccolo rispetto a …
  • si prevedeva che il carico di manutenzione fosse ridotto; Non ricordo alcun bug segnalato in questa funzione negli anni da quando è stato spedito.
  • la funzione non pone rischi immediatamente evidenti per le funzionalità future in quest’area. (L’ultima cosa che vogliamo fare è creare una funzione economica e facile ora che rende molto più difficile implementare una funzione più interessante in futuro.)
  • la funzione non aggiunge nuove ambiguità all’analisi lessicale, grammaticale o semantica del linguaggio. Non pone problemi per il tipo di analisi del “programma parziale” eseguita dal motore “IntelliSense” dell’IDE durante la digitazione. E così via.
  • la funzione colpisce un “punto debole” comune per la funzione di inizializzazione dell’object più grande; in genere se si sta utilizzando un inizializzatore di oggetti è proprio perché il costruttore dell’object non consente di impostare le proprietà desiderate. È molto comune che tali oggetti siano semplicemente “sacchetti di proprietà” che non hanno parametri nel ctor in primo luogo.

Perché allora non hai reso opzionali parentesi vuote nel richiamo predefinito del costruttore di un’espressione di creazione dell’object che non ha un inizializzatore dell’object?

Dai un altro sguardo a quella lista di criteri sopra. Uno di questi è che il cambiamento non introduce alcuna nuova ambiguità nell’analisi lessicale, grammaticale o semantica di un programma. Il tuo cambiamento proposto introduce un’ambiguità di analisi semantica:

 class P { class B { public class M { } } class C : B { new public void M(){} } static void Main() { new C().M(); // 1 new CM(); // 2 } } 

La riga 1 crea una nuova C, chiama il costruttore predefinito e quindi chiama il metodo di istanza M sul nuovo object. La riga 2 crea una nuova istanza di BM e chiama il suo costruttore predefinito. Se le parentesi sulla linea 1 fossero opzionali, la linea 2 sarebbe ambigua. Dovremmo quindi trovare una regola che risolva l’ambiguità; non è stato ansible commettere un errore perché ciò rappresenterebbe una modifica irrisolta che modifica un programma C # legale esistente in un programma non funzionante.

Pertanto la regola dovrebbe essere molto complicata: in sostanza, le parentesi sono opzionali solo nei casi in cui non introducono ambiguità. Dovremmo analizzare tutti i possibili casi che introducono ambiguità e quindi scrivere il codice nel compilatore per rilevarli.

In questa luce, torna indietro e guarda tutti i costi che ho menzionato. Quanti di loro ora diventano grandi? Le regole complicate hanno costi elevati di progettazione, specifiche, sviluppo, test e documentazione. È molto più probabile che le regole complicate causino problemi con interazioni impreviste con le funzionalità in futuro.

Tutto per cosa? Un piccolo vantaggio per il cliente che non aggiunge alcun nuovo potere di rappresentazione alla lingua, ma aggiunge pazzi casi angusti che aspettano solo di urlare “gotcha” ad una povera e insospettabile anima che vi si imbatte. Funzionalità come questa vengono immediatamente tagliate e inserite nell’elenco “mai fare questo”.

Come hai determinato quella particolare ambiguità?

Quello fu immediatamente chiaro; Sono abbastanza familiare con le regole in C # per determinare quando è previsto un nome puntato.

Quando si considera una nuova funzionalità, come si determina se provoca qualche ambiguità? A mano, con la dimostrazione formale, con l’analisi della macchina, che cosa?

Tutti e tre. Per lo più guardiamo solo le specifiche e il noodle, come ho fatto sopra. Ad esempio, supponiamo di voler aggiungere un nuovo operatore di prefisso a C # chiamato “frob”:

 x = frob 123 + 456; 

(AGGIORNAMENTO: frob è ovviamente in await , l’analisi qui è essenzialmente l’analisi che il team di progettazione ha attraversato durante l’aggiunta await .)

“frob” qui è come “nuovo” o “++” – viene prima di un’espressione di qualche tipo. Elaboriamo la precedenza e l’associatività desiderate e così via, e poi iniziamo a fare domande come “cosa succede se il programma ha già un tipo, campo, proprietà, evento, metodo, costante o locale chiamato frob?” Ciò porterebbe immediatamente a casi come:

 frob x = 10; 

significa “esegui l’operazione frob sul risultato di x = 10 o crea una variabile di tipo frob chiamata x e assegna 10 ad essa?” (Oppure, se il frobbing produce una variabile, potrebbe essere un compito da 10 a frob x tutto, *x = 10; analizza ed è legale se x è int* .)

 G(frob + x) 

Ciò significa “frob il risultato dell’operatore unario più su x” o “aggiungi espressione frob a x”?

E così via. Per risolvere queste ambiguità potremmo introdurre l’euristica. Quando dici “var x = 10;” è ambiguo; potrebbe significare “inferire il tipo di x” o potrebbe significare “x è di tipo var”. Quindi abbiamo una euristica: per prima cosa cerchiamo di cercare un tipo di nome var, e solo se uno non esiste ne deduciamo il tipo di x.

Oppure, potremmo cambiare la syntax in modo che non sia ambigua. Quando hanno progettato C # 2.0 hanno avuto questo problema:

 yield(x); 

Significa “resa x in un iteratore” o “chiama il metodo yield con l’argomento x?” Cambiandolo in

 yield return(x); 

ora è inequivocabile.

Nel caso di partions opzionali in un inizializzatore di oggetti, è semplice ragionare sulla presenza o meno di ambiguità perché il numero di situazioni in cui è consentito introdurre qualcosa che inizia con {è molto piccolo . Fondamentalmente solo vari contesti di istruzioni, dichiarazioni lambda, inizializzatori di array e questo è tutto. È facile ragionare in tutti i casi e dimostrare che non c’è ambiguità. Assicurarsi che l’IDE rimanga efficiente è un po ‘più difficile, ma può essere fatto senza troppi problemi.

Solitamente questo tipo di giocherellare con le specifiche è sufficiente. Se si tratta di una caratteristica particolarmente complessa, estraiamo strumenti più pesanti. Ad esempio, quando si progetta LINQ, uno dei ragazzi del compilatore e uno dei ragazzi IDE che hanno entrambi esperienza nella teoria del parser si sono costruiti un generatore di parser in grado di analizzare le grammatiche alla ricerca di ambiguità, e quindi hanno inserito le grammatiche proposte in C # per la comprensione delle query in esso ; così facendo ho trovato molti casi in cui le query erano ambigue.

Oppure, quando abbiamo avanzato l’inferenza di tipo su lambdas in C # 3.0, abbiamo scritto le nostre proposte e poi le abbiamo inviate a Microsoft Research a Cambridge, dove il team linguistico era abbastanza buono da fornire una prova formale che la proposta di inferenza di tipo era teoricamente valido.

Ci sono ambiguità in C # oggi?

Sicuro.

 G(F(0)) 

In C # 1 è chiaro cosa significa. È lo stesso di:

 G( (F0) ) 

Cioè, chiama G con due argomenti che sono bool. In C # 2, ciò potrebbe significare cosa significa in C # 1, ma potrebbe anche significare “passare 0 al metodo generico F che prende i parametri di tipo A e B, e quindi passare il risultato di F a G”. Abbiamo aggiunto un’euristica complicata al parser che determina quale dei due casi si intende probabilmente.

Allo stesso modo, i cast sono ambigui anche in C # 1.0:

 G((T)-x) 

È “cast -x a T” o “sottrarre x da T”? Di nuovo, abbiamo un’euristica che fa una buona ipotesi.

Perché è così che è stata specificata la lingua. Non aggiungono alcun valore, quindi perché includerli?

È anche molto simile agli array di tipo implicito

 var a = new[] { 1, 10, 100, 1000 }; // int[] var b = new[] { 1, 1.5, 2, 2.5 }; // double[] var c = new[] { "hello", null, "world" }; // string[] var d = new[] { 1, "one", 2, "two" }; // Error 

Riferimento: http://msdn.microsoft.com/en-us/library/ms364047%28VS.80%29.aspx

Questo è stato fatto per semplificare la costruzione di oggetti. I progettisti di linguaggi non hanno (per quanto sappia) espressamente detto perché pensavano che ciò fosse utile, anche se è esplicitamente menzionato nella pagina delle specifiche di C # Version 3.0 :

Un’espressione di creazione di un object può omettere l’elenco di argomenti del costruttore e racchiudere parentesi, a condizione che includa un inizializzatore di oggetti o di raccolte. Omettere l’elenco degli argomenti del costruttore e racchiudere le parentesi equivale a specificare un elenco di argomenti vuoto.

Suppongo che sentissero che la parentesi, in questo caso, non era necessaria per mostrare l’intento dello sviluppatore, poiché l’inizializzatore dell’object mostra invece l’intento di build e impostare le proprietà dell’object.

Nel tuo primo esempio, il compilatore deduce che stai chiamando il costruttore predefinito (la specifica della lingua C # 3.0 afferma che se non viene fornita alcuna parentesi, viene chiamato il costruttore predefinito).

Nel secondo, si chiama esplicitamente il costruttore predefinito.

È inoltre ansible utilizzare tale syntax per impostare le proprietà mentre si passano esplicitamente i valori al costruttore. Se avessi la seguente definizione di class:

 public class SomeTest { public string Value { get; private set; } public string AnotherValue { get; set; } public string YetAnotherValue { get; set;} public SomeTest() { } public SomeTest(string value) { Value = value; } } 

Tutte e tre le affermazioni sono valide:

 var obj = new SomeTest { AnotherValue = "Hello", YetAnotherValue = "World" }; var obj = new SomeTest() { AnotherValue = "Hello", YetAnotherValue = "World"}; var obj = new SomeTest("Hello") { AnotherValue = "World", YetAnotherValue = "!"}; 

Non sono Eric Lippert, quindi non posso dirlo con certezza, ma suppongo che sia dovuto al fatto che la parentesi vuota non è necessaria al compilatore per dedurre il costrutto di inizializzazione. Pertanto diventa informazioni ridondanti e non è necessario.