2012-07-10 19 views
5

Me surgió algo bastante desconcertante en C# recientemente. En nuestra base de código, tenemos una clase TreeNode. Al cambiar un código, descubrí que era imposible asignar una variable a la propiedad Nodes. En una inspección más cercana quedó claro que la propiedad es de solo lectura y que este comportamiento es de esperar.Configuración de una propiedad de solo lectura con tipo anónimo

Lo extraño es que nuestra base de código hasta entonces siempre dependía de la asignación de algún tipo anónimo a la propiedad Nodes y se compiló y funcionó a la perfección.

En resumen: ¿por qué la tarea en AddSomeNodes funcionó en primer lugar?

using System.Collections.Generic; 

namespace ReadOnlyProperty 
{ 
    public class TreeNode 
    { 
     private readonly IList<TreeNode> _nodes = new List<TreeNode>(); 

     public IList<TreeNode> Nodes 
     { 
      get { return _nodes; } 
     } 
    } 

    public class TreeBuilder 
    { 
     public IEnumerable<TreeNode> AddSomeNodes() 
     { 
      yield return new TreeNode 
      { 
       Nodes = { new TreeNode() } 
      }; 
     } 

     public IEnumerable<TreeNode> AddSomeOtherNodes() 
     { 
      var someNodes = new List<TreeNode>(); 

      yield return new TreeNode 
      { 
       Nodes = someNodes 
      }; 
     } 
    } 
} 

Respuesta

4

AddSomeNodes no está creando una instancia de List<TreeNode> porque que la sintaxis es un inicializador colección (por lo tanto no está asignando a Nodes significado no rompe el contrato readonly), el compilador realmente traduce el inicializador de la colección en llamadas al .Add.

La llamada AddSomeOtherNodes realmente intenta reasignar el valor, pero es readonly. Esta es también la sintaxis del inicializador de objetos, que se traduce en llamadas a propiedades simples. Esta propiedad no tiene un setter, por lo que la llamada genera un error de compilación. Intentar agregar un setter que establezca el valor de solo lectura generará otro error de compilación porque está marcado readonly.

From MSDN:

Mediante el uso de un inicializador de colección que no tiene que especificar varios llamadas al método Add de la clase en el código fuente; el compilador agrega las llamadas.

Además, sólo para aclarar, no son no tipos anónimos en juego en su código - es toda la sintaxis de inicializador.


Sin relación a su pregunta, pero en la misma zona.

Curiosamente, la sintaxis Nodes = { new TreeNode() } no funciona con un miembro local, que sólo parece funcionar cuando está anidado dentro de un inicializador objeto o durante la asignación de objeto:

List<int> numbers = { 1, 2, 3, 4 }; // This isn't valid. 
List<int> numbers = new List<int> { 1, 2, 3, 4 }; // Valid. 

// This is valid, but will NullReferenceException on Numbers 
// if NumberContainer doesn't "new" the list internally. 
var container = new NumberContainer() 
{ 
    Numbers = { 1, 2, 3, 4 } 
}; 

La documentación de MSDN no parece tener alguna aclaración sobre esto.

+0

@Downvoter Why the downvote? Mi observación sobre la situación de los OP es precisa según la documentación y mis propias pruebas. Por no mencionar todas las demás respuestas. –

+0

¡Acabo de recibir un voto negativo también! – MBen

+0

¡Gracias por proporcionar una respuesta tan completa y concisa tan rápido! Tiene toda la razón al decir que cometí un error al llamarlo de tipo anónimo. Este comportamiento tiene sentido cuando se considera la sintaxis de inicialización de la colección, no la asignación de un tipo anónimo. A veces es difícil desambiguar estos conceptos mentalmente porque son tan sintácticamente iguales. ¡Gracias! – Michiel

1

Esto no es asignar que está añadiendo un elemento a la ICollection

Nodes = {new TreeNode() }, that is why it works. 
2

Su propiedad de nodos no se está asignando.

Utilizando el inicializador de colección especial:

CollectionProperty = { a, b, c }; 

se cambia a:

CollectionProperty.Add(a); 
CollectionProperty.Add(b); 
CollectionProperty.Add(c); 
0

El compilador equivalente a lo que pasó es lo siguiente:

public IEnumerable<TreeNode> AddSomeNodes() 
{ 
    TreeNode node = new TreeNode(); 
    node.Nodes.Add(new TreeNode()); 

    yield return node; 
} 

La distinción importante aquí es que utilizaron la sintaxis del inicializador de la colección para asignar el valor.

1

He compilado el código (después de quitar el método AddSomeOtherNodes) y la abrí en el reflector y aquí está el resultado:

public IEnumerable<TreeNode> AddSomeNodes() 
{ 
    TreeNode iteratorVariable0 = new TreeNode(); 
    iteratorVariable0.Nodes.Add(new TreeNode()); 
    yield return iteratorVariable0; 
} 

Como se puede ver, con esta sintaxis del método Add se llama en la variable de nodos .

Cuestiones relacionadas