2010-05-05 10 views
142

Digamos que tengo una claseToList() - ¿Crea una nueva lista?

public class MyObject 
{ 
    public int SimpleInt{get;set;} 
} 

Y tengo una List<MyObject>, y yo ToList() y luego cambiar uno de los SimpleInt, será mi cambio se propaga de nuevo a la lista original. En otras palabras, ¿cuál sería el resultado del siguiente método?

public void RunChangeList() 
{ 
    var objs = new List<MyObject>(){new MyObject(){SimpleInt=0}}; 
    var whatInt = ChangeToList(objs); 
} 
public int ChangeToList(List<MyObject> objects) 
{ 
    var objectList = objects.ToList(); 
    objectList[0].SimpleInt=5; 
    return objects[0].SimpleInt; 

} 

¿Por qué?

P/S: Lo siento si parece obvio averiguarlo. Pero no tengo compilador ahora ...

Respuesta

171

Sí, ToList creará una nueva lista, pero como en este caso MyObject es un tipo de referencia, la nueva lista contendrá referencias a los mismos objetos que la lista original.

La actualización de la propiedad SimpleInt de un objeto al que se hace referencia en la nueva lista también afectará al objeto equivalente en la lista original.

(Si MyObject fue declarado como struct en lugar de un class entonces la nueva lista contendría copias de los elementos en la lista original, y la actualización de una propiedad de un elemento en la nueva lista sería no afectar al elemento equivalente en la lista original.)

+1

También tenga en cuenta que con una 'Lista' de estructuras, una asignación como' objectList [0] .SimpleInt = 5' no estaría permitida (error de tiempo de compilación de C#). Esto se debe a que el valor de retorno del descriptor de acceso 'get' del indexador de listas no es una variable (es una copia devuelta de un valor struct) y por lo tanto _setting_ its member '.SimpleInt' con una expresión de asignación no permitida (mutaría una copia que no se guarda). Bueno, ¿quién usa estructuras mutables de todos modos? –

+1

@Jeppe Stig Nielson: Sí. Y, de manera similar, el compilador también le impedirá hacer cosas como 'foreach (var s en listOfStructs) {s.SimpleInt = 42; } ' Lo realmente malo es cuando intentas algo como 'listOfStructs.ForEach (s => s.SimpleInt = 42)': el compilador lo permite y el código se ejecuta sin excepciones, ¡pero las estructuras en la lista permanecerán sin cambios! – LukeH

29

ToList siempre creará una nueva lista, que no reflejará ningún cambio posterior en la colección.

Sin embargo, reflejará cambios en los objetos mismos (a menos que sean estructuras mutables).

En otras palabras, si reemplaza un objeto en la lista original con un objeto diferente, el ToList aún contendrá el primer objeto.
Sin embargo, si modifica uno de los objetos en la lista original, el ToList aún contendrá el mismo objeto (modificado).

6

Se crea una lista nueva pero los elementos que contiene son referencias a los elementos originales (como en la lista original). Los cambios en la lista en sí son independientes, pero los artículos encontrarán el cambio en ambas listas.

10

Sí, crea una nueva lista. Esto es por diseño.

La lista contendrá los mismos resultados que la secuencia enumerable original, pero se materializó en una colección persistente (en memoria). Esto le permite consumir los resultados varias veces sin incurrir en el costo de volver a calcular la secuencia.

La belleza de las secuencias LINQ es que son composable. A menudo, el IEnumerable<T> que obtiene es el resultado de combinar múltiples operaciones de filtrado, pedido y proyección. Los métodos de extensión como ToList() y ToArray() le permiten convertir la secuencia calculada en una colección estándar.

2

ToList creará una nueva lista.

Si los elementos de la lista son tipos de valores, se actualizarán directamente, si son tipos de referencia, los cambios se reflejarán de nuevo en los objetos a los que se hace referencia.

1
var objectList = objects.ToList(); 
    objectList[0].SimpleInt=5; 

Esto también actualizará el objeto original. La nueva lista contendrá referencias a los objetos que contiene, al igual que la lista original. Puede cambiar los elementos y la actualización se reflejará en el otro.

Ahora si actualiza una lista (agregar o eliminar un elemento) que no se reflejará en la otra lista.

4

Creo que esto es equivalente a preguntar si ToList hace una copia profunda o superficial. Como ToList no tiene forma de clonar MiObjeto, debe hacer una copia superficial, por lo que la lista creada contiene las mismas referencias que la original, por lo que el código devuelve 5.

55

Desde la fuente Reflector'd:

public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source) 
{ 
    if (source == null) 
    { 
     throw Error.ArgumentNull("source"); 
    } 
    return new List<TSource>(source); 
} 

Así que sí, su lista original no será actualizada (es decir, adiciones o remociones) sin embargo, los objetos referenciados lo harán.

1

No veo en ningún lugar de la documentación que ToList() siempre garantice la devolución de una nueva lista. Si un IEnumerable es una lista, puede ser más eficiente verificar esto y simplemente devolver la misma lista.

La preocupación es que a veces es posible que desee estar absolutamente seguro de que la lista devuelta es! = A la lista original. Debido a que Microsoft no documenta que ToList devolverá una nueva lista, no podemos estar seguros (a menos que alguien haya encontrado esa documentación). También podría cambiar en el futuro, incluso si funciona ahora.

nueva lista (IEnumerable enumerablestuff) se garantiza que devolverá una nueva lista. Yo usaría esto en su lugar.

+2

Lo dice en la documentación aquí: "[El método ToList (IEnumerable ) fuerza la evaluación de consulta inmediata y devuelve una lista que contiene los resultados de la consulta. Puede agregar este método a su consulta para obtener una copia en caché de los resultados de la consulta.] (http://msdn.microsoft.com/en-us/library/bb342261.aspx) "La segunda oración reitera que cualquier cambio en la lista original después de la evaluación de la consulta no tiene ningún efecto en la lista devuelta . –

+1

@RaymondChen Esto es cierto para IEnumerables, pero no para Lists. Aunque 'ToList' parece crear una nueva referencia de objeto de lista cuando se le llama en una' Lista', BenB tiene razón cuando dice que esto no está garantizado por la documentación de MS. – Teejay

+0

@RaymondChen De acuerdo con la fuente ChrisS, 'IEnumerable <>. ToList()' se implementa realmente como 'new List <> (source)' y no hay una anulación específica para 'List <>', así 'List <>. ToList() 'de hecho devuelve una nueva referencia de objeto de lista. Pero una vez más, según la documentación de MS, no hay garantía de que esto no cambie en el futuro, aunque es probable que rompa la mayor parte del código. – Teejay

2

En el caso donde el objeto fuente es un verdadero IEnumerable (es decir, no solo una colección empaquetada como enumerable), ToList() NO puede devolver las mismas referencias de objeto que en el IEnumerable original. Devolverá una nueva Lista de objetos, pero esos objetos pueden no ser iguales o incluso Igual que los objetos producidos por IEnumerable cuando se enumera nuevamente

7

La respuesta aceptada aborda correctamente la pregunta del OP en función de su ejemplo. Sin embargo, solo se aplica cuando se aplica ToList a una colección de concreto; no se cumple cuando los elementos de la secuencia de origen aún no se han instanciado (debido a la ejecución diferida). En el caso de este último, puede obtener un nuevo conjunto de elementos cada vez que llame al ToList (o enumere la secuencia).

Aquí es una adaptación del código de la OP para demostrar este comportamiento:

public static void RunChangeList() 
{ 
    var objs = Enumerable.Range(0, 10).Select(_ => new MyObject() { SimpleInt = 0 }); 
    var whatInt = ChangeToList(objs); // whatInt gets 0 
} 

public static int ChangeToList(IEnumerable<MyObject> objects) 
{ 
    var objectList = objects.ToList(); 
    objectList.First().SimpleInt = 5; 
    return objects.First().SimpleInt; 
} 

Mientras que el código anterior puede parecer artificial, este comportamiento puede aparecer como un error sutil en otros escenarios. Consulte my other example para una situación en la que causa que las tareas se generen repetidamente.

4

Acabo de tropezar con esta publicación anterior y pensé en agregar mis dos centavos. Generalmente, si tengo dudas, rápidamente uso el método GetHashCode() en cualquier objeto para verificar las identidades.Así que para arriba -

public class MyObject 
{ 
    public int SimpleInt { get; set; } 
} 


class Program 
{ 

    public static void RunChangeList() 
    { 
     var objs = new List<MyObject>() { new MyObject() { SimpleInt = 0 } }; 
     Console.WriteLine("objs: {0}", objs.GetHashCode()); 
     Console.WriteLine("objs[0]: {0}", objs[0].GetHashCode()); 
     var whatInt = ChangeToList(objs); 
     Console.WriteLine("whatInt: {0}", whatInt.GetHashCode()); 
    } 

    public static int ChangeToList(List<MyObject> objects) 
    { 
     Console.WriteLine("objects: {0}", objects.GetHashCode()); 
     Console.WriteLine("objects[0]: {0}", objects[0].GetHashCode()); 
     var objectList = objects.ToList(); 
     Console.WriteLine("objectList: {0}", objectList.GetHashCode()); 
     Console.WriteLine("objectList[0]: {0}", objectList[0].GetHashCode()); 
     objectList[0].SimpleInt = 5; 
     return objects[0].SimpleInt; 

    } 

    private static void Main(string[] args) 
    { 
     RunChangeList(); 
     Console.ReadLine(); 
    } 

y contestar en mi máquina -

  • objs: 45653674
  • objs [0]: 41149443
  • objetos: 45653674
  • objetos [0]: 41149443
  • objectList: 39785641
  • objectList [0]: 41149443
  • whatInt: 5

Así que, esencialmente el objeto que lleva la lista siguen siendo los mismos en el código de seguridad. Espero que el enfoque ayude.

Cuestiones relacionadas