Il semble que la syntaxe de l'initialiseur d'objet C # 3.0 permet d'exclure la paire de parenthèses ouverture / fermeture dans le constructeur lorsqu'il existe un constructeur sans paramètre. Exemple:
var x = new XTypeName { PropA = value, PropB = value };
Par opposition à:
var x = new XTypeName() { PropA = value, PropB = value };
Je suis curieux de savoir pourquoi la paire de parenthèses ouvertes / fermées du constructeur est facultative ici après XTypeName
?
c#
syntax
types
language-design
initializer
James Dunne
la source
la source
Réponses:
Cette question a fait l'objet de mon blog le 20 septembre 2010 . Les réponses de Josh et Chad ("ils n'ajoutent aucune valeur alors pourquoi les exiger?" Et "pour éliminer la redondance") sont fondamentalement correctes. Pour étoffer un peu plus cela:
La fonctionnalité de vous permettre d'élider la liste d'arguments dans le cadre de la «fonctionnalité plus large» des initialiseurs d'objets a rencontré notre barre pour les fonctionnalités «sucrées». Quelques points que nous avons considérés:
Jetez un autre coup d'œil à cette liste de critères ci-dessus. L'un d'eux est que le changement n'introduit aucune nouvelle ambiguïté dans l'analyse lexicale, grammaticale ou sémantique d'un programme. Votre changement proposé n'introduire une ambiguïté d'analyse sémantique:
La ligne 1 crée un nouveau C, appelle le constructeur par défaut, puis appelle la méthode d'instance M sur le nouvel objet. La ligne 2 crée une nouvelle instance de BM et appelle son constructeur par défaut. Si les parenthèses sur la ligne 1 étaient facultatives, la ligne 2 serait ambiguë. Il faudrait alors trouver une règle résolvant l'ambiguïté; nous ne pouvions pas en faire une erreur car ce serait alors un changement de rupture qui transforme un programme C # légal existant en un programme cassé.
Par conséquent, la règle devrait être très compliquée: essentiellement que les parenthèses ne sont facultatives que dans les cas où elles n'introduisent pas d'ambiguïtés. Il faudrait analyser tous les cas possibles qui introduisent des ambiguïtés, puis écrire du code dans le compilateur pour les détecter.
Dans cette optique, revenez en arrière et regardez tous les coûts que je mentionne. Combien d'entre eux deviennent maintenant grands? Les règles compliquées ont des coûts de conception, de spécifications, de développement, de test et de documentation importants. Les règles compliquées sont beaucoup plus susceptibles de provoquer des problèmes d'interactions inattendues avec des fonctionnalités à l'avenir.
Tout pour quoi? Un petit avantage pour le client qui n'ajoute aucun nouveau pouvoir de représentation à la langue, mais qui ajoute des cas de coin fous qui n'attendent que de crier "gotcha" à une pauvre âme sans méfiance qui s'y heurte. Des fonctionnalités comme celle-ci sont immédiatement supprimées et inscrites sur la liste «ne jamais faire ça».
Celui-là était immédiatement clair; Je connais assez bien les règles en C # pour déterminer quand un nom en pointillé est attendu.
Tous les trois. La plupart du temps, nous regardons simplement les spécifications et les nouilles, comme je l'ai fait ci-dessus. Par exemple, supposons que nous voulions ajouter un nouvel opérateur de préfixe à C # appelé "frob":
(MISE À JOUR:
frob
c'est bien sûrawait
; l'analyse ici est essentiellement l'analyse que l'équipe de conception a traversée lors de l'ajoutawait
.)"frob" ici est comme "new" ou "++" - il vient avant une expression quelconque. Nous travaillerions sur la priorité et l'associativité souhaitées, etc., puis nous commencerions à poser des questions telles que "et si le programme avait déjà un type, un champ, une propriété, un événement, une méthode, une constante ou un local appelé frob?" Cela conduirait immédiatement à des cas comme:
cela signifie-t-il "faire l'opération frob sur le résultat de x = 10, ou créer une variable de type frob appelée x et lui affecter 10?" (Ou, si le frobbing produit une variable, il peut s'agir d'une affectation de 10 à
frob x
. Après tout,*x = 10;
analyse et est légal six
c'estint*
.)Cela signifie-t-il "frob le résultat de l'opérateur unaire plus sur x" ou "ajouter une expression frob à x"?
Etc. Pour résoudre ces ambiguïtés, nous pourrions introduire des heuristiques. Quand vous dites "var x = 10;" c'est ambigu; cela pourrait signifier "inférer le type de x" ou cela pourrait signifier "x est de type var". Nous avons donc une heuristique: nous essayons d'abord de rechercher un type nommé var, et ce n'est que s'il n'en existe pas que nous déduisons le type de x.
Ou, nous pourrions changer la syntaxe pour qu'elle ne soit pas ambiguë. Quand ils ont conçu C # 2.0, ils ont eu ce problème:
Cela signifie-t-il "yield x dans un itérateur" ou "appeler la méthode yield avec l'argument x?" En le changeant en
il est désormais sans ambiguïté.
Dans le cas de parens optionnelles dans un initialiseur d'objet, il est facile de raisonner pour savoir s'il y a des ambiguïtés introduites ou non car le nombre de situations dans lesquelles il est permis d'introduire quelque chose qui commence par {est très petit . Fondamentalement, juste divers contextes d'instruction, lambdas d'instruction, initialiseurs de tableau et c'est à peu près tout. Il est facile de raisonner à travers tous les cas et de montrer qu'il n'y a pas d'ambiguïté. S'assurer que l'EDI reste efficace est un peu plus difficile mais peut être fait sans trop de problèmes.
Ce genre de bidouillage avec les spécifications est généralement suffisant. Si c'est une fonctionnalité particulièrement délicate, nous sortons des outils plus lourds. Par exemple, lors de la conception de LINQ, l'un des gars du compilateur et l'un des gars de l'EDI qui ont tous deux une formation en théorie des parseurs se sont construits un générateur d'analyseurs qui pourrait analyser les grammaires à la recherche d'ambiguïtés, puis ont alimenté les grammaires C # proposées pour la compréhension des requêtes. ; cela a permis de trouver de nombreux cas où les requêtes étaient ambiguës.
Ou, lorsque nous avons fait une inférence de type avancée sur les lambdas en C # 3.0, nous avons rédigé nos propositions, puis nous les avons envoyées au-dessus de l'étang à Microsoft Research à Cambridge où l'équipe des langues était suffisamment bonne pour élaborer une preuve formelle que la proposition d'inférence de type était théoriquement solide.
Sûr.
En C # 1, ce que cela signifie est clair. C'est la même chose que:
Autrement dit, il appelle G avec deux arguments qui sont des booléens. En C # 2, cela pourrait signifier ce que cela signifiait en C # 1, mais cela pourrait aussi signifier "passer 0 à la méthode générique F qui prend les paramètres de type A et B, puis passer le résultat de F à G". Nous avons ajouté une heuristique compliquée à l'analyseur qui détermine lequel des deux cas vous avez probablement voulu dire.
De même, les casts sont ambigus même en C # 1.0:
Est-ce "cast -x to T" ou "soustraire x de T"? Encore une fois, nous avons une heuristique qui fait une bonne estimation.
la source
Parce que c'est ainsi que la langue a été spécifiée. Ils n'ajoutent aucune valeur, alors pourquoi les inclure?
Il est également très similaire aux tableaux typés par implicité
Référence: http://msdn.microsoft.com/en-us/library/ms364047%28VS.80%29.aspx
la source
Cela a été fait pour simplifier la construction des objets. Les concepteurs de langage n'ont pas (à ma connaissance) expliqué spécifiquement pourquoi ils pensaient que c'était utile, bien que cela soit explicitement mentionné dans la page de spécification C # version 3.0 :
Je suppose qu'ils ont estimé que les parenthèses, dans ce cas, n'étaient pas nécessaires pour montrer l'intention du développeur, car l'initialiseur d'objet montre plutôt l'intention de construire et de définir les propriétés de l'objet.
la source
Dans votre premier exemple, le compilateur déduit que vous appelez le constructeur par défaut (la spécification du langage C # 3.0 indique que si aucune parenthèse n'est fournie, le constructeur par défaut est appelé).
Dans le second, vous appelez explicitement le constructeur par défaut.
Vous pouvez également utiliser cette syntaxe pour définir des propriétés tout en transmettant explicitement des valeurs au constructeur. Si vous aviez la définition de classe suivante:
Les trois déclarations sont valides:
la source
Je ne suis pas Eric Lippert, donc je ne peux pas le dire avec certitude, mais je suppose que c'est parce que la parenthèse vide n'est pas nécessaire au compilateur pour déduire la construction d'initialisation. Par conséquent, cela devient une information redondante et inutile.
la source