2010-09-07 11 views
98

Parece que la sintaxis del inicializador de objetos C# 3.0 permite excluir el par abierto/cerrado de paréntesis en el constructor cuando existe un constructor sin parámetros. Ejemplo:¿Por qué son opcionales los paréntesis del constructor del inicializador de objetos C# 3.0?

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

A diferencia:

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

tengo curiosidad por qué el constructor cerca paréntesis par de apertura/cierre es opcional aquí después XTypeName?

+7

Como un lado, encontramos esto en una revisión del código la semana pasada: var list = new List {}; Si se puede abusar de algo ... – blu

+0

@ blu Esa es una de las razones por las que quería hacer esta pregunta. Noté la inconsistencia en nuestro código. La inconsistencia en general me molesta, así que pensé que vería si había una buena razón detrás de la opcionalidad en la sintaxis. :) –

+0

Posible duplicado de [¿Cuál es la diferencia entre un inicializador de objetos y un constructor?] (Http://stackoverflow.com/questions/740658/whats-the-difference-between-an-object-initializer-and-a- constructor) –

Respuesta

130

This question was the subject of my blog on September 20th 2010. Las respuestas de Josh y Chad ("no agregan ningún valor, ¿por qué las necesitan?" Y "para eliminar la redundancia") son básicamente correctas. Para completar eso un poco más:

La función de permitirle eludir la lista de argumentos como parte de la "función más amplia" de los inicializadores de objetos cumplió con nuestra barra para las características "azucaradas".Algunos puntos que consideran:

  • el costo de diseño y especificación fue baja
  • que iban a ser ampliamente cambiar el código de programa de análisis que se encarga de la creación de objetos de todas formas; el costo de desarrollo adicional de hacer la lista de parámetros opcional no era grande en comparación con el costo de la función más grande
  • la carga de prueba era relativamente pequeña en comparación con el costo de la función más grande
  • la carga de documentación era relativamente pequeña en comparación. .
  • se anticipó que la carga de mantenimiento era pequeña; No recuerdo ningún error reportado en esta característica en los años desde que se envió.
  • la característica no presenta ningún riesgo inmediatamente obvio para las características futuras en esta área. (Lo último que queremos hacer es crear una característica barata y fácil ahora que hace que sea mucho más difícil implementar una función más convincente en el futuro.)
  • la función no agrega nuevas ambigüedades al análisis léxico, gramatical o semántico de el idioma. No plantea problemas para el tipo de análisis de "programa parcial" que realiza el motor "IntelliSense" del IDE mientras escribe. Y así.
  • la función alcanza un "punto óptimo" común para la función de inicialización de objetos más grande; por lo general, si está utilizando un inicializador de objetos, es precisamente porque el constructor del objeto no no le permite establecer las propiedades que desea. Es muy común que tales objetos sean simplemente "bolsas de propiedades" que no tienen parámetros en el ctor en primer lugar.

¿Por qué entonces no también hacen paréntesis vacíos opcional en la llamada al constructor por defecto de una expresión de creación del objeto que hace no tienen un inicializador de objeto?

Eche otro vistazo a la lista de criterios anterior. Una de ellas es que el cambio no introduce ninguna nueva ambigüedad en el análisis léxico, gramatical o semántico de un programa. Su cambio propuesto no introducir una semántica ambigüedad análisis:

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

Línea 1 crea un nuevo C, llama al constructor por defecto, y luego llama al método de instancia M en el nuevo objeto. La línea 2 crea una nueva instancia de B.M y llama a su constructor predeterminado. Si los paréntesis en la línea 1 fueran opcionales, la línea 2 sería ambigua. Tendríamos que idear una regla para resolver la ambigüedad; no podríamos convertirlo en error porque eso sería un cambio radical que cambia un programa legal existente de C# en un programa dañado.

Por lo tanto, la regla debería ser muy complicada: en esencia, los paréntesis son solo opcionales en los casos en que no introducen ambigüedades. Tendríamos que analizar todos los casos posibles que introducen ambigüedades y luego escribir código en el compilador para detectarlos.

En esa luz, retroceda y mire todos los costos que menciono. ¿Cuántos de ellos ahora se vuelven grandes? Las reglas complicadas tienen grandes costos de diseño, especificaciones, desarrollo, pruebas y documentación. Es mucho más probable que las reglas complicadas causen problemas con interacciones inesperadas con características en el futuro.

¿Por qué?Un pequeño beneficio para el cliente que no agrega un nuevo poder de representación al lenguaje, pero sí agrega casos de esquinas locas esperando gritar "gotcha" a algún pobre alma desprevenida que se topa con él. Características como esa se cortan inmediatamente y se ponen en la lista "nunca hacer esto".

¿Cómo determinaste esa ambigüedad particular?

Esa fue inmediatamente clara; Estoy bastante familiarizado con las reglas en C# para determinar cuándo se espera un nombre punteado.

Al considerar una nueva característica, ¿cómo se determina si causa alguna ambigüedad? A mano, por prueba formal, por análisis de máquina, ¿qué?

Los tres. Sobre todo, solo miramos las especificaciones y fideos, como lo hice anteriormente. Por ejemplo, supongamos que deseamos añadir un nuevo operador de prefijo a C# llamado "FROB":

x = frob 123 + 456; 

(ACTUALIZACIÓN: frob es, por supuesto await; el análisis aquí es esencialmente el análisis que el equipo de diseño pasó por al añadir await.)

"frob" aquí es como "nuevo" o "++" - se presenta antes de una expresión de algún tipo. Trabajaríamos con la precedencia y la asociatividad deseadas, y así sucesivamente, y luego comenzaremos a hacer preguntas como "¿y si el programa ya tiene un tipo, campo, propiedad, evento, método, constante o local llamado frob?" Eso llevaría inmediatamente a casos como:

frob x = 10; 

quiere decir que "hacer la operación FROB en el resultado de x = 10, o cree una variable de tipo FROB llamados x 10 y asignar a ella?" (O, si frobbing produce una variable, podría ser una asignación del 10 al frob x. Después de todo, *x = 10; analiza y es legal si x es int*.)

G(frob + x) 

¿Eso quiere decir "FROB el resultado de la unario operador más en x "o" agregar expresión frob a x "?

Y así sucesivamente. Para resolver estas ambigüedades, podríamos introducir la heurística. Cuando dices "var x = 10;" eso es ambiguo; podría significar "inferir el tipo de x" o podría significar "x es de tipo var". Entonces tenemos una heurística: primero intentamos buscar un tipo llamado var, y solo si no existe, inferimos el tipo de x.

O, podríamos cambiar la sintaxis para que no sea ambigua. Cuando diseñaron C# 2.0 que tenían este problema: "¿llamar al método de rendimiento con el argumento x"

yield(x); 

¿Eso significa "rendimiento x en un repetidor" o Al cambiarlo a

yield return(x); 

ahora es inequívoco.

En el caso de parens opcionales en un inicializador de objeto es sencillo de razonar acerca de si existen ambigüedades introducidas o no porque el número de situaciones en las que está permitido introducir algo que comienza con {es muy pequeña.Básicamente solo varios contextos de declaración, declaración lambdas, inicializadores de matriz y eso es todo. Es fácil razonar a través de todos los casos y mostrar que no hay ambigüedad. Asegurarse de que el IDE se mantenga eficiente es algo más difícil, pero se puede hacer sin demasiados problemas.

Este tipo de manipulación con la especificación generalmente es suficiente. Si es una característica particularmente complicada, sacamos herramientas más pesadas. Por ejemplo, al diseñar LINQ, uno de los chicos del compilador y uno de los chicos del IDE que tienen experiencia en teoría del analizador construyeron un analizador sintáctico que podía analizar gramáticas en busca de ambigüedades, y luego introdujeron las gramáticas propuestas de C# para la comprensión de las consultas. ; al hacerlo, encontró muchos casos en los que las consultas eran ambiguas.

O, cuando hicimos una inferencia tipo avanzada sobre lambdas en C# 3.0, redactamos nuestras propuestas y luego las enviamos a Microsoft Research en Cambridge, donde el equipo de idiomas era lo suficientemente bueno para elaborar una prueba formal de que la propuesta de inferencia tipo era teóricamente sólida.

¿Hay ambigüedades en C# hoy?

Sure.

G(F<A, B>(0)) 

En C# 1 está claro lo que eso significa. Es lo mismo que:

G((F<A), (B>0)) 

Es decir, llama a G con dos argumentos que son bools. En C# 2, eso podría significar lo que significaba en C# 1, pero también podría significar "pasar 0 al método genérico F que toma los parámetros de tipo A y B, y luego pasar el resultado de F a G". Agregamos una heurística complicada al analizador que determina cuál de los dos casos probablemente quiso decir.

Del mismo modo, los moldes son ambiguos, incluso en C# 1.0:

G((T)-x) 

es que "echó a -x T" o "restar x de T"? De nuevo, tenemos una heurística que hace una buena suposición.

+0

Una respuesta finamente redactada. Gracias Señor. Quería indicar respeto con el título de "Sr. Lippert" en lugar de un nombre de pila con una persona a la que todavía no he sido presentado formalmente :). Básicamente, tengo curiosidad por saber por qué, ahora, la sintaxis "regular" de inicialización de objetos sin las llaves para "inicializadores de objetos" no puede tener sus paréntesis excluidos cuando se quiere llamar también al constructor sin parámetros predeterminado. –

+3

Lo siento, lo olvidé ... El enfoque de la señal de murciélago, aunque parece funcionar, es preferible a (IMO) un medio de contacto directo por el cual uno no obtendría la exposición pública deseada para la educación pública en la forma de una publicación SO es indexable, buscable y se puede consultar fácilmente. ¿En lugar de eso, contactaremos directamente para coreografiar una danza de post-respuesta o de SO organizada? :) –

+5

Te recomiendo que evites organizar una publicación. Eso no sería injusto para otros que puedan tener una visión adicional de la cuestión. Un mejor enfoque sería publicar la pregunta, luego enviar un correo electrónico con un enlace pidiendo participación. – chilltemp

1

No soy Eric Lippert, así que no puedo decirlo con certeza, pero supongo que es porque el compilador no necesita el paréntesis vacío para deducir la construcción de inicialización. Por lo tanto, se convierte en información redundante y no necesaria.

+0

Correcto, es redundante, pero tengo curiosidad por saber por qué la introducción repentina de ellos es opcional. Parece romper con la consistencia de la sintaxis del lenguaje. Si no tuviera el corchete abierto para indicar un bloque de inicializador, entonces esta debería ser una sintaxis ilegal. Es curioso que menciones al Sr. Lippert, yo estaba como pescando públicamente su respuesta para que yo y otros nos beneficiémos de una curiosidad ociosa. :) –

9

Porque así es como se especificó el idioma. No agregan ningún valor, entonces, ¿por qué incluirlos?

También es muy similar a las matrices implícitamente mecanografiadas

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 

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

+1

No agregan ningún valor en el sentido de que debe ser obvio cuál es la intención, pero rompe con la consistencia en que ahora tenemos dos sintaxis de construcción de objetos dispares, una con paréntesis requeridos (y expresiones de argumento delimitadas por comas) y uno sin. –

+1

@James Dunne, en realidad es una sintaxis muy similar a la sintaxis de matriz tipada implícitamente, ver mi edición. No hay ningún tipo, ningún constructor, y la intención es obvia, por lo que no hay necesidad de declararlo – CaffGeek

7

Esto se hizo para simplificar la construcción de objetos. Los diseñadores del lenguaje no tienen (que yo sepa) dijo específicamente por qué pensaban que esto era útil, aunque se menciona explícitamente en el C# Version 3.0 Specification page: expresión de creación

Un objeto puede omitir la lista de argumentos del constructor y los paréntesis que encierran, siempre incluye un objeto o inicializador de colección. Omitir la lista de argumentos del constructor y adjuntar paréntesis equivale a especificar una lista de argumentos vacía.

supongo que sentían el paréntesis, en este caso, no eran necesarias con el fin de mostrar la intención desarrollador, ya que el inicializador de objeto muestra la intención de construir y establecer las propiedades del objeto en su lugar.

4

En su primer ejemplo, el compilador deduce que está llamando al constructor predeterminado (la especificación del lenguaje C# 3.0 establece que si no se proporcionan paréntesis, se llama al constructor predeterminado).

En el segundo, llama explícitamente al constructor predeterminado.

También puede usar esa sintaxis para establecer propiedades al pasar explícitamente valores al constructor. Si usted tuvo la siguiente definición de clase:

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; 
    } 
} 

Las tres afirmaciones son válidas:

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

Derecha. En el primer y segundo caso en su ejemplo, estos son funcionalmente idénticos, ¿correcto? –

+1

@James Dunne - Correcto. Esa es la parte especificada por la especificación de idioma. Los paréntesis vacíos son redundantes, pero aún así puede proporcionarlos. –

Cuestiones relacionadas