2011-02-04 5 views
48

Como sabe, no está permitido usar la sintaxis de inicialización de matriz con Listas. Le dará un error en tiempo de compilación. Ejemplo:List <int> test = {1, 2, 3} - ¿Es una característica o un error?

List<int> test = { 1, 2, 3} 
// At compilation the following error is shown: 
// Can only use array initializer expressions to assign to array types. 

Sin embargo hoy hice lo siguiente (muy simplificada):

class Test 
{ 
    public List<int> Field; 
} 

List<Test> list = new List<Test> 
{ 
    new Test { Field = { 1, 2, 3 } } 
}; 

El código anterior se compila bien, pero cuando se ejecuta dará un "objeto de referencias no se ajusta a un objeto " Error de tiempo de ejecución.

Supongo que ese código dará un error en tiempo de compilación. Mi pregunta para usted es: ¿por qué no lo hace, y hay alguna buena razón para cuando ese escenario se ejecutaría correctamente?

Esto ha sido probado usando .NET 3.5, ambos compiladores .Net y Mono.

Saludos.

+1

El ejemplo más simple 'Test test = new Test {Field = {1, 2, 3}};' tiene el mismo comportamiento. – CodesInChaos

+3

La "Referencia de objeto no está configurada en un objeto" es bastante lógica ya que su campo de Lista no está inicializado. Cambiar el cuerpo de la prueba a la lista pública Campo = new List (); hace que esto se ejecute sin ningún problema también. –

+0

tome un reflector, y vea cómo el compilador lo trata, y lo entenderá. también mira cómo var stuff = new List () {4,5}; se ha manejado –

Respuesta

43

Creo que esto es un comportamiento por diseño. El Test = { 1, 2, 3 } se compila en el código que llama al método Add de la lista almacenada en el campo Test.

La razón por la que obtienes NullReferenceException es que Test es . Si inicializa el campo Test a una nueva lista, a continuación, el código funcionará:

class Test {  
    public List<int> Field = new List<int>(); 
} 

// Calls 'Add' method three times to add items to 'Field' list 
var t = new Test { Field = { 1, 2, 3 } }; 

es bastante lógico - si se escribe new List<int> { ... } entonces se crea una nueva instancia de la lista. Si no agrega construcción de objetos, usará la instancia existente (o null). Por lo que yo puedo ver, la especificación C# no contiene ninguna regla de conversión explícita de que se correspondería con este escenario, pero da un ejemplo (véase la Sección 7.6.10.3 ):

Un List<Contact> se pueden crear y inicializado como sigue:

var contacts = new List<Contact> { 
    new Contact { 
     Name = "Chris Smith", 
     PhoneNumbers = { "206-555-0101", "425-882-8080" } 
    }, 
    new Contact { 
     Name = "Bob Harris", 
     PhoneNumbers = { "650-555-0199" } 
    } 
}; 

que tiene el mismo efecto que

var contacts = new List<Contact>(); 
Contact __c1 = new Contact(); 
__c1.Name = "Chris Smith"; 
__c1.PhoneNumbers.Add("206-555-0101"); 
__c1.PhoneNumbers.Add("425-882-8080"); 
contacts.Add(__c1); 
Contact __c2 = new Contact(); 
__c2.Name = "Bob Harris"; 
__c2.PhoneNumbers.Add("650-555-0199"); 
contacts.Add(__c2); 

donde __c1 y __c2 son variables temporales que de otro modo serían invisibles e inaccesibles.

+0

Un poco relevante: http://stackoverflow.com/questions/459652/why-do-c-collection-initializers-work-this-way –

+0

Gracias, excelente respuesta. Con esta descripción, el comportamiento es lógico. Sin embargo, lo que todavía no me parece lógico es permitir = {} sintaxis para agregar elementos, así leería "Este campo es igual a eso", pero de hecho la lista puede contener más elementos que los que especifiqué si se agregaron sobre la construcción del objeto. Prefiero tener un error de compilación ;-) – Mika

+1

Sí, hay un poco de inconsistencia. Usando la lógica de esta muestra, uno debería poder escribir 'var l = new List (); l = {1, 2, 3}; '- ¡pero eso no funciona! –

3
var test = (new [] { 1, 2, 3}).ToList(); 
+5

No está preguntando cómo hacer que funcione, él está preguntando por qué el programa original incluso se compila. –

+5

se compila porque es válido C#, falla en tiempo de ejecución porque no creó una instancia del objeto de lista –

+0

@K Ivanov: ¡Qué explicación tan fantástica! – BoltClock

1

cambiar el código para esto:

class Test 
{ 
    public List<int> Field = new List<int>(); 
} 

La razón es que se debe crear explícitamente un objeto de colección antes de poder poner los artículos a la misma.

+3

No pregunta cómo hacer que funcione, sino que pregunta por qué el programa original incluso compila. –

+0

Compila porque es correcto. ¿Qué más quieres escuchar? –

+1

Podría explicar por qué se compila el código OP. – CodesInChaos

18

este código:

Test t = new Test { Field = { 1, 2, 3 } }; 

son trasladados a esta:

Test t = new Test(); 
t.Field.Add(1); 
t.Field.Add(2); 
t.Field.Add(3); 

Desde Field es null, se obtiene la NullReferenceException.

Esto se llama un collection initializer, y funcionará en su primer ejemplo si usted hace esto:

List<int> test = new List<int> { 1, 2, 3 }; 

Realmente necesita nuevo algo con el fin de poder utilizar esta sintaxis, es decir, una el inicializador de colecciones solo puede aparecer en el contexto de una expresión de creación de objetos. En la especificación de C#, la sección 7.6.10.1, esta es la sintaxis de una expresión de creación del objeto:

object-creation-expression: 
    new type (argument-list?) object-or-collection-initializer? 
    new type object-or-collection-initializer 
object-or-collection-initializer: 
    object-initializer 
    collection-initializer 

Así que todo comienza con una expresión new. Dentro de la expresión, se puede utilizar un inicializador de colección sin el (la sección 7.6.10.2) new:

object-initializer: 
    { member-initializer-list? } 
    { member-initializer-list , } 
member-initializer-list: 
    member-initializer 
    member-initializer-list , member-initializer 
member-initializer: 
    identifier = initializer-value 
initializer-value: 
    expression 
    object-or-collection-initializer // here it recurses 

Ahora, lo que realmente falta es algún tipo de lista literal, lo que sería realmente útil. Propuse una de esas literales para los enumerables here.

+0

Sí, exactamente. El código original es sintácticamente correcto, pero en realidad no hay una lista para poner esos elementos. –

+0

Su último ejemplo es bien conocido. Pero no sabía que podía usar un inicializador de colección dentro de un inicializador de objetos sin 'nueva MyCollection'. Y el artículo de MSDN que vinculó tampoco lo menciona. – CodesInChaos

+0

Otra buena respuesta. Supongo que tenían que hacerlo de esta manera para permitir la adición de elementos sin anular los valores que pueden haberse agregado en el constructor del objeto inicial. Es un poco ambiguo ya que espera que el valor resultante de una operación '=' sea una coincidencia exacta de la asignación del lado derecho, pero en este caso solo está agregando un conjunto de valores. – Mika

2

La razón de esto es que el segundo ejemplo es una lista de miembros initialiser - y la expresión de MemberListBinding System.Linq.Expressions dan una idea de esto - por favor ver mi respuesta a esta otra pregunta para más detalles: What are some examples of MemberBinding LINQ expressions?

Este tipo de inicializador requiere que la lista ya esté inicializada, para que la secuencia que proporcione pueda agregarse.

Como resultado - sintácticamente no hay absolutamente nada de malo en el código - el NullReferenceException es un error de tiempo de ejecución causado por la Lista que en realidad no se ha creado. Un constructor predeterminado que new es la lista, o un new en línea en el cuerpo del código, resolverá el error de tiempo de ejecución.

En cuanto a por qué hay una diferencia entre eso y la primera línea de código: en su ejemplo no está permitido porque este tipo de expresión no puede estar en el lado derecho de una tarea porque realmente no crea cualquier cosa, es solo abreviatura de Add.

+0

Eché un vistazo al enlace. Cosas buenas para obtener un conocimiento más profundo de cómo se hacen las cosas en realidad. ¡Gracias! En cuanto a mi ejemplo, creo que está muy claro ahora con todas las respuestas que he recibido :) – Mika

25

Esperaría que el código dara un error en tiempo de compilación.

Dado que su expectativa es contraria tanto a la especificación como a la implementación, su expectativa no se cumplirá.

¿Por qué no falla en el tiempo de compilación?

Porque la especificación establece específicamente que es legal en la sección 7.6.10.2, que cito aquí para su conveniencia:


Un inicializador miembro que especifica un inicializador de colección después del signo igual es una inicialización de una colección incrustada. En lugar de asignar una nueva colección al campo o a la propiedad, los elementos dados en el inicializador se agregan a la colección a la que hace referencia el campo o la propiedad.


Cuándo tal código se ejecute correctamente?

Como dice la especificación, los elementos que figuran en el inicializador se agregan a la colección a la que hace referencia la propiedad. La propiedad no hace referencia a una colección; es nulo Por lo tanto, en tiempo de ejecución da una excepción de referencia nula. Alguien tiene que inicializar la lista. Recomendaría cambiar la clase "Prueba" para que su constructor inicialice la lista.

¿Qué escenario motiva esta característica?

Las consultas de LINQ necesitan expresiones, no declaraciones. Agregar un miembro a una colección recién creada en una lista recién creada requiere llamar a "Agregar". Debido a que "Agregar" es inválido, una llamada solo puede aparecer en una declaración de expresión. Esta función le permite crear una nueva colección (con "nuevo") y llenarla, o completar una colección existente (sin "nueva"), donde la colección es miembro de un objeto que está creando como resultado de un LINQ consulta.

+0

Gracias por una respuesta muy directa a mis preguntas. Como también comenté sobre las otras respuestas, es lógico en su comportamiento en el contexto de lo que queremos ser capaces de hacer, aunque no es muy lógico en su sintaxis ya que uno esperaría que una operación '=' hiciera una asignación exacta. . Su motivación LINQ contribuye a esto, y puedo ver por qué decidieron diseñarlo de esta manera. – Mika

+0

Agregarlo a la documentación de MSDN en http://msdn.microsoft.com/en-us/library/bb384062.aspx estaría bien. Solo muestra otras variantes de inicializadores de colección/objeto. – CodesInChaos

+0

@Mika el significado de '=' en su código coincide con '' 'en los inicializadores de colecciones normales. Lo inusual de su código es que no tiene 'nueva lista ' allí. – CodesInChaos

Cuestiones relacionadas