ToList (): ¿crea una nueva lista?

176

Digamos que tengo una clase

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

Y tengo un List<MyObject>, y ToList()lo cambio, y luego uno de los SimpleInt, ¿se propagará mi cambio 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 conmigo ahora ...

Graviton
fuente
Una forma de redactarlo es que .ToList()hace una copia superficial . Las referencias se copian, pero las nuevas referencias todavía apuntan a las mismas instancias a las que apuntan las referencias originales. Cuando lo piensas, ToListno puedes crear ninguno new MyObject()cuando MyObjectes un classtipo.
Jeppe Stig Nielsen

Respuestas:

217

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

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

(Si MyObjectse declarara como un en structlugar de un classentonces, 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 no afectaría al elemento equivalente en la lista original).

LukeH
fuente
1
También tenga en cuenta que con una Listestructura, objectList[0].SimpleInt=5no se permitiría una asignación como (error de tiempo de compilación de C #). Esto se debe a que el valor de retorno del descriptor de getacceso de la lista no es una variable (es una copia devuelta de un valor de estructura) y, por lo tanto ,.SimpleInt no está permitido establecer su miembro con una expresión de asignación (mutaría una copia que no se conserva) . Bueno, ¿quién usa estructuras mutables de todos modos?
Jeppe Stig Nielsen
2
@Jeppe Stig Nielson: Sí. Y, de manera similar, el compilador también te impedirá hacer cosas como foreach (var s in listOfStructs) { s.SimpleInt = 42; }. El problema realmente desagradable 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
62

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

Entonces, sí, su lista original no se actualizará (es decir, adiciones o eliminaciones), pero los objetos a los que se hace referencia lo harán.

Chris S
fuente
32

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

Sin embargo, reflejará los 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, ToListtodavía contendrá el primer objeto.
Sin embargo, si modifica uno de los objetos en la lista original, ToListseguirá conteniendo el mismo objeto (modificado).

SLaks
fuente
12

La respuesta aceptada aborda correctamente la pregunta del OP basada en su ejemplo. Sin embargo, solo se aplica cuando ToListse aplica a una colección concreta; no se mantiene cuando los elementos de la secuencia fuente 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 ToList(o enumere la secuencia).

Aquí hay una adaptación del código del 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;
}

Si bien el código anterior puede parecer artificial, este comportamiento puede aparecer como un error sutil en otros escenarios. Vea mi otro ejemplo para una situación en la que hace que las tareas se generen repetidamente.

Douglas
fuente
11

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 de LINQ es que son componibles. A menudo, lo IEnumerable<T>que obtiene es el resultado de combinar múltiples operaciones de filtrado, pedidos y / o proyección. Los métodos de extensión tienen gusto ToList()y le ToArray()permiten convertir la secuencia calculada en una colección estándar.

LBushkin
fuente
6

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

Sangha Preet
fuente
6

Simplemente me topé con este viejo post y pensé en agregar mis dos centavos. En general, si tengo dudas, uso rápidamente el método GetHashCode () en cualquier objeto para verificar las identidades. Entonces 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 responde en mi máquina

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

Entonces, esencialmente, el objeto que lleva la lista permanece igual en el código anterior. Espero que el enfoque ayude.

vibhu
fuente
4

Creo que esto es equivalente a preguntar si ToList hace una copia profunda o superficial. Como ToList no tiene forma de clonar MyObject, 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.

Timores
fuente
2

ToList creará una nueva lista.

Si los elementos de la lista son tipos de valor, se actualizarán directamente, si son tipos de referencia, cualquier cambio se reflejará en los objetos a los que se hace referencia.

Oded
fuente
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 iguales a los objetos producidos por IEnumerable cuando se enumera nuevamente

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

Esto actualizará el objeto original también. 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 (agregando o eliminando un elemento) que no se reflejará en la otra lista.

kemiller2002
fuente
1

No veo en ninguna parte de la documentación que ToList () siempre tenga la garantía de devolver 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 está! = 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.

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

Ben B.
fuente
2
Lo dice en la documentación aquí: " El método ToList <TSource> (IEnumerable <TSource>) fuerza la evaluación inmediata de la consulta y devuelve una Lista <T> 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 " . La segunda oración reitera que cualquier cambio en la lista original después de evaluar la consulta no tiene ningún efecto en la lista devuelta.
Raymond Chen el
1
@RaymondChen Esto es cierto para IEnumerables, pero no para las Listas. Aunque ToListparece crear una nueva referencia de objeto de lista cuando se llama a un List, BenB tiene razón cuando dice que esto no está garantizado por la documentación de MS.
Teejay
@RaymondChen Según la fuente de ChrisS, en IEnumerable<>.ToList()realidad se implementa como new List<>(source)y no hay una anulación específica para List<>, por List<>.ToList()lo que 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 probablemente romperá la mayor parte del código.
Teejay