2008-12-11 17 views
8

¿Por qué las listas list1Instance y p en el método Main del código siguiente apuntan a la misma colección?¿Cómo hacer IEnumerable <T> readonly?

class Person 
    { 
     public string FirstName = string.Empty; 
     public string LastName = string.Empty; 

     public Person(string firstName, string lastName) { 
      this.FirstName = firstName; 
      this.LastName = lastName; 
     } 
    } 

    class List1 
    { 
     public List<Person> l1 = new List<Person>(); 

     public List1() 
     { 
      l1.Add(new Person("f1","l1")); 
      l1.Add(new Person("f2", "l2")); 
      l1.Add(new Person("f3", "l3")); 
      l1.Add(new Person("f4", "l4")); 
      l1.Add(new Person("f5", "l5")); 
     } 
     public IEnumerable<Person> Get() 
     { 
      foreach (Person p in l1) 
      { 
       yield return p; 
      } 

      //return l1.AsReadOnly(); 
     } 

    } 

    class Program 
    { 

     static void Main(string[] args) 
     { 
      List1 list1Instance = new List1(); 

      List<Person> p = new List<Person>(list1Instance.Get());   

      UpdatePersons(p); 

      bool sameFirstName = (list1Instance.l1[0].FirstName == p[0].FirstName); 
     } 

     private static void UpdatePersons(List<Person> list) 
     { 
      list[0].FirstName = "uf1"; 
     } 
    } 

¿Podemos cambiar este comportamiento con cabo cambiando el tipo de retorno de List1.Get()?

Gracias

Respuesta

26

De hecho, IEnumerable<T>es ya de sólo lectura. Significa que no puede reemplazar ningún elemento en la colección subyacente con diferentes elementos. Es decir, no puede modificar las referencias a los objetos Person que se encuentran en la colección. Sin embargo, el tipo Person no es de solo lectura y, dado que es un tipo de referencia (es decir, un class), puede modificar sus miembros a través de la referencia.

hay dos soluciones:

  • utilizar un struct como el tipo de retorno (que hace una copia del valor cada vez que se volvió, por lo que el valor original no se modificará   — que puede ser costoso, por cierto)
  • Utilice las propiedades de solo lectura en el tipo Person para realizar esta tarea.
+4

Pero puede convertir cualquier 'IEnumerable ' de nuevo a 'T []', 'List ', o el tipo que sea en realidad, y modificar sus elementos (ellos mismos, no sus propiedades). – Shimmy

+2

@Shimmy Si vamos a obtener información técnica sobre lo que * puedes * hacer, también puedes usar el reflejo para acceder a los miembros privados de cualquier clase y hacer lo que quieras. El hecho de que * pueda * hacer algo (como lanzar 'IEnumerable' a su tipo real) no significa que ** nunca ** deba hacerlo. – Alex

+3

'IEnumerable ' no es esencialmente una colección de solo lectura. – Shimmy

2

No apuntan a la misma colección .Net, sino a los mismos objetos Person. La línea:

List<Person> p = new List<Person>(list1Instance.Get()); 

copia todos los elementos persona de list1Instance.Get() a la lista p. La palabra "copias" aquí significa copias de las referencias. Entonces, su lista y IEnumerable apuntan a los mismos objetos Person.

IEnumerable<T> es always readonly, por definición. Sin embargo, los objetos internos pueden ser mutables, como en este caso.

7

Devuelve una nueva instancia de Persona que es una copia de p en lugar de p en Get(). Necesitará un método para hacer una copia profunda de un objeto Persona para hacer esto. Esto no los hará solo de lectura, pero serán diferentes a los de la lista original.

public IEnumerable<Person> Get() 
{ 
    foreach (Person p in l1) 
    { 
     yield return p.Clone(); 
    } 
} 
0

IEnumerable<T> es de sólo lectura

p es una nueva colección que no depende de list1instance. El error que cometió es que pensó que esta línea list[0].FirstName = "uf1";
solo modificaría una de las listas, cuando de hecho está modificando el objeto Person.
Las dos colecciones son distintas, simplemente tienen los mismos elementos.
Para demostrar que son diferentes, intente agregar y eliminar elementos de una de las listas, y verá que el otro no se ve afectado.

0

En primer lugar, su lista en su clase es pública, por lo que no hay nada que impida que alguien acceda directamente a la lista.

En segundo lugar, me gustaría implementar IEnumerable y devolver esta en mi método GetEnumerator

return l1.AsReadOnly().GetEnumerator(); 
1

Se podría hacer una deepclone de cada elemento de la lista, y no volver nunca referencias a los elementos originales.

public IEnumerable<Person> Get() 
{ 
    return l1 
    .Select(p => new Person(){ 
     FirstName = p.FirstName, 
     LastName = p.LastName 
    }); 
} 
0

Si su persona objeto es un objeto real, entonces debería considerar el uso de una versión inmutable.

public class Person 
{ 
    public FirstName {get; private set;} 
    public LastName {get; private set;} 
    public Person(firstName, lastName) 
    { 
     FirstName = firstName; 
     LastName = lastName; 
    } 
    } 

De esta manera no es posible cambiar el contenido de la instancia una vez creada y por lo tanto no es importante que las instancias existentes se vuelven a utilizar en múltiples listas.

Cuestiones relacionadas