ToList () - cria uma nova lista?

176

Digamos que eu tenho uma aula

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

E eu tenho um List<MyObject>, e eu ToList(), e depois altero um dos SimpleInt, minha alteração será propagada de volta para a lista original. Em outras palavras, qual seria o resultado do método a seguir?

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: Me desculpe se parece óbvio descobrir. Mas eu não tenho compilador comigo agora ...

Graviton
fonte
Uma maneira de frasear isso é .ToList()fazer uma cópia superficial . As referências são copiadas, mas as novas referências ainda apontam para as mesmas instâncias que as referências originais apontam para. Quando você pensa sobre isso, ToListnão pode criar nenhum new MyObject()quando MyObjecté um classtipo.
Jeppe Stig Nielsen

Respostas:

217

Sim, ToListcriará uma nova lista, mas porque neste casoMyObject é um tipo de referência, a nova lista conterá referências aos mesmos objetos que a lista original.

Atualizando o SimpleInt propriedade de um objeto mencionado na nova lista também afetará o objeto equivalente na lista original.

(Se MyObjectfosse declarado como um structe não um class, a nova lista conteria cópias dos elementos na lista original e a atualização de uma propriedade de um elemento na nova lista não afetaria o elemento equivalente na lista original.)

LukeH
fonte
1
Observe também que, com uma Listdas estruturas, uma atribuição como objectList[0].SimpleInt=5não seria permitida (erro em tempo de compilação em C #). Isso ocorre porque o valor de retorno do getacessador do indexador de lista não é uma variável (é uma cópia retornada de um valor de struct) e, portanto, a configuração de seu membro .SimpleIntcom uma expressão de atribuição não é permitida (isso mudaria uma cópia que não é mantida) . Bem, quem usa estruturas mutáveis, afinal?
Jeppe Stig Nielsen
2
@Jeppe Stig Nielson: Sim. E, da mesma forma, o compilador também impedirá que você faça coisas assim foreach (var s in listOfStructs) { s.SimpleInt = 42; }. A pegadinha realmente desagradável é quando você tenta algo como listOfStructs.ForEach(s => s.SimpleInt = 42): o compilador permite e o código é executado sem exceções, mas as estruturas da lista permanecerão inalteradas!
LukeH 06/07
62

Da fonte do Refletor:

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

Portanto, sim, sua lista original não será atualizada (por exemplo, adições ou remoções), mas os objetos referenciados serão.

Chris S
fonte
32

ToList sempre criará uma nova lista, que não refletirá nenhuma alteração subsequente na coleção.

No entanto, ele refletirá alterações nos próprios objetos (a menos que sejam estruturas mutáveis).

Em outras palavras, se você substituir um objeto na lista original por um objeto diferente, ToListele ainda conterá o primeiro objeto.
No entanto, se você modificar um dos objetos na lista original, ToListele ainda conterá o mesmo objeto (modificado).

SLaks
fonte
12

A resposta aceita aborda corretamente a pergunta do OP com base em seu exemplo. No entanto, isso só se aplica quando ToListaplicado a uma coleção concreta; não é válido quando os elementos da sequência de origem ainda não foram instanciados (devido à execução adiada). No caso deste último, você poderá obter um novo conjunto de itens sempre que ligar ToList(ou enumerar a sequência).

Aqui está uma adaptação do código do OP para demonstrar esse comportamento:

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

Embora o código acima possa parecer artificial, esse comportamento pode aparecer como um bug sutil em outros cenários. Veja meu outro exemplo para uma situação em que faz com que as tarefas sejam geradas repetidamente.

Douglas
fonte
11

Sim, ele cria uma nova lista. Isso ocorre por design.

A lista conterá os mesmos resultados que a sequência enumerável original, mas materializada em uma coleção persistente (na memória). Isso permite consumir os resultados várias vezes, sem incorrer no custo de recalcular a sequência.

A beleza das sequências LINQ é que elas são compostas. Freqüentemente, IEnumerable<T>você obtém o resultado da combinação de várias operações de filtragem, pedido e / ou projeção. Os métodos de extensão gostam ToList()e ToArray()permitem converter a sequência computada em uma coleção padrão.

LBushkin
fonte
6

Uma nova lista é criada, mas os itens nela são referências aos itens originais (assim como na lista original). As alterações na lista em si são independentes, mas nos itens a alteração será encontrada nas duas listas.

Preet Sangha
fonte
6

Apenas tropeçar neste post antigo e pensei em adicionar meus dois centavos. Geralmente, em caso de dúvida, uso rapidamente o método GetHashCode () em qualquer objeto para verificar as identidades. Então, para cima -

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

E responda na minha máquina -

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

Portanto, essencialmente o objeto que a lista carrega permanece o mesmo no código acima. Espero que a abordagem ajude.

vibhu
fonte
4

Eu acho que isso equivale a perguntar se o ToList faz uma cópia profunda ou superficial. Como o ToList não tem como clonar o MyObject, ele deve fazer uma cópia superficial; portanto, a lista criada contém as mesmas referências que a original; portanto, o código retorna 5.

Timores
fonte
2

ToList criará uma nova lista.

Se os itens na lista forem tipos de valor, eles serão atualizados diretamente; se forem tipos de referência, quaisquer alterações serão refletidas nos objetos referenciados.

Oded
fonte
2

No caso em que o objeto de origem é um IEnumerable verdadeiro (ou seja, não apenas uma coleção empacotada como enumerável), ToList () NÃO pode retornar as mesmas referências de objeto que no IEnumerable original. Ele retornará uma nova lista de objetos, mas esses objetos podem não ser iguais ou iguais aos objetos produzidos pelo IEnumerable quando forem enumerados novamente

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

Isso também atualizará o objeto original. A nova lista conterá referências aos objetos contidos nela, assim como a lista original. Você pode alterar os elementos e a atualização será refletida no outro.

Agora, se você atualizar uma lista (adicionando ou excluindo um item) que não será refletida na outra lista.

kemiller2002
fonte
1

Não vejo em nenhum lugar da documentação que ToList () esteja sempre garantido para retornar uma nova lista. Se um IEnumerable é uma lista, pode ser mais eficiente verificar isso e simplesmente retornar a mesma lista.

A preocupação é que, às vezes, você queira ter certeza absoluta de que a lista retornada é! = Para a lista original. Como a Microsoft não documenta que o ToList retornará uma nova lista, não podemos ter certeza (a menos que alguém tenha encontrado essa documentação). Também pode mudar no futuro, mesmo que funcione agora.

nova lista (IEnumerable enumerablestuff) é garantida para retornar uma nova lista. Eu usaria isso em seu lugar.

Ben B.
fonte
2
Ele diz na documentação aqui: " O método ToList <TSource> (IEnumerable <TSource>) força a avaliação imediata da consulta e retorna uma Lista <T> que contém os resultados da consulta. Você pode anexar esse método à sua consulta para obter uma cópia em cache dos resultados da consulta. "A segunda frase reitera que qualquer alteração na lista original após a avaliação da consulta não tem efeito na lista retornada.
Raymond Chen
1
@ RaymondChen Isso é verdade para IEnumerables, mas não para Lists. Embora ToListpareça criar uma nova referência de objeto de lista quando chamada em um List, BenB está certo quando diz que isso não é garantido pela documentação da MS.
Teejay
@RaymondChen De acordo com a fonte do ChrisS, IEnumerable<>.ToList()é realmente implementado como new List<>(source)e não há substituição específica para List<>; portanto, List<>.ToList()ele realmente retorna uma nova referência de objeto de lista. Mas, mais uma vez, conforme a documentação da MS, não há garantia de que isso não mude no futuro, embora provavelmente interrompa a maior parte do código existente.
Teejay