Lista aprovada por ref - ajude-me a explicar este comportamento

109

Dê uma olhada no seguinte programa:

class Test
{
    List<int> myList = new List<int>();

    public void TestMethod()
    {
        myList.Add(100);
        myList.Add(50);
        myList.Add(10);

        ChangeList(myList);

        foreach (int i in myList)
        {
            Console.WriteLine(i);
        }
    }

    private void ChangeList(List<int> myList)
    {
        myList.Sort();

        List<int> myList2 = new List<int>();
        myList2.Add(3);
        myList2.Add(4);

        myList = myList2;
    }
}

Eu presumi myListque teria passado ref, e a saída

3
4

A lista é de fato "passada por ref", mas apenas a sortfunção tem efeito. A seguinte declaração myList = myList2;não tem efeito.

Portanto, a saída é de fato:

10
50
100

Você pode me ajudar a explicar esse comportamento? Se, de fato, myListnão for ignorado (como parece por myList = myList2não ter efeito), como o myList.Sort()efeito tem efeito?

Eu estava presumindo que até mesmo essa declaração não teria efeito e a saída seria:

100
50
10
nmdr
fonte
Apenas uma observação (e eu percebo que o problema foi simplificado aqui), mas parece que seria melhor ChangeListretornar um em List<int>vez de ser um voidse de fato estiver criando uma nova lista.
Jeff B

Respostas:

110

Você está passando uma referência para a lista , mas não está passando a variável da lista por referência - então, quando você chama ChangeListo valor da variável (ou seja, a referência - pense no "ponteiro") é copiada - e muda para o valor do parâmetro dentro ChangeList não é visto por TestMethod.

experimentar:

private void ChangeList(ref List<int> myList) {...}
...
ChangeList(ref myList);

Isso então passa uma referência à variável local myRef (conforme declarado em TestMethod); agora, se você reatribuir o parâmetro interno, ChangeListtambém estará reatribuindo a variável interna TestMethod .

Marc Gravell
fonte
Na verdade, posso fazer isso, mas quero saber como a classificação está
surtindo
6
@Ngm - quando você chama ChangeList, apenas a referência é copiada - é o mesmo objeto. Se você mudar o objeto de alguma forma, tudo que tiver uma referência a esse objeto verá a mudança.
Marc Gravell
225

Inicialmente, pode ser representado graficamente da seguinte forma:

Estados de inicialização

Então, a classificação é aplicada myList.Sort(); Classificar coleção

Finalmente, quando você fez myList' = myList2:, você perdeu aquele da referência, mas não o original e a coleção ficou classificada.

Referência perdida

Se você usar por referência ( ref), então myList'e myListse tornará o mesmo (apenas uma referência).

Nota: Eu uso myList'para representar o parâmetro que você usa em ChangeList(porque você deu o mesmo nome do original)

Jaider
fonte
20

Aqui está uma maneira fácil de entender isso

  • Sua lista é um objeto criado no heap. A variável myListé uma referência a esse objeto.

  • Em C # você nunca passa objetos, você passa suas referências por valor.

  • Quando você acessa o objeto de lista por meio da referência passada ChangeList(durante a classificação, por exemplo), a lista original é alterada.

  • A atribuição no ChangeListmétodo é feita para o valor da referência, portanto, nenhuma alteração é feita na lista original (ainda na pilha, mas não mais referenciada na variável do método).

Unmesh Kondolikar
fonte
10

Este link o ajudará a entender a passagem por referência em C #. Basicamente, quando um objeto do tipo de referência é passado por valor para um método, apenas os métodos que estão disponíveis naquele objeto podem modificar o conteúdo do objeto.

Por exemplo, o método List.sort () altera o conteúdo da lista, mas se você atribuir algum outro objeto à mesma variável, essa atribuição será local para aquele método. É por isso que myList permanece inalterado.

Se passarmos o objeto do tipo de referência usando a palavra-chave ref, podemos atribuir algum outro objeto à mesma variável e isso muda o próprio objeto inteiro.

(Editar: esta é a versão atualizada da documentação vinculada acima.)

Shekhar
fonte
5

C # apenas faz uma cópia superficial quando passa por valor, a menos que o objeto em questão seja executado ICloneable(o que aparentemente a Listclasse não executa).

Isso significa que ele copia a Listsi mesmo, mas as referências aos objetos dentro da lista permanecem as mesmas; ou seja, os ponteiros continuam a fazer referência aos mesmos objetos do original List.

Se você alterar os valores das coisas que suas novas Listreferências fazem, você também altera o original List(uma vez que está referenciando os mesmos objetos). No entanto, você então altera quais myListreferências inteiramente, para um novo List, e agora apenas o original Listestá fazendo referência a esses inteiros.

Leia a passagem de Referência-Type Parâmetros seção de este artigo MSDN em "Passando parâmetros" para mais informações.

"How do I Clone a Generic List in C #" do StackOverflow fala sobre como fazer uma cópia profunda de uma lista.

Ethel Evans
fonte
3

Embora eu concorde com o que todos disseram acima. Eu tenho uma opinião diferente sobre este código. Basicamente, você está atribuindo a nova lista à variável local myList, não à global. se você alterar a assinatura de ChangeList (List myList) para private void ChangeList (), verá a saída de 3, 4.

Aqui está meu raciocínio ... Mesmo que a lista seja passada por referência, pense nisso como uma variável de ponteiro por valor. Quando você chama ChangeList (myList), você está passando o ponteiro para (Global) myList. Agora, isso é armazenado na variável myList (local). Portanto, agora sua (local) myList e (global) myList estão apontando para a mesma lista. Agora você faz uma classificação => funciona porque (local) myList está referenciando a myList original (global) Em seguida, você cria uma nova lista e atribui o ponteiro a essa myList (local). Mas, assim que a função sai, a variável myList (local) é destruída. HTH

class Test
{
    List<int> myList = new List<int>();
    public void TestMethod()
    {

        myList.Add(100);
        myList.Add(50);
        myList.Add(10);

        ChangeList();

        foreach (int i in myList)
        {
            Console.WriteLine(i);
        }
    }

    private void ChangeList()
    {
        myList.Sort();

        List<int> myList2 = new List<int>();
        myList2.Add(3);
        myList2.Add(4);

        myList = myList2;
    }
}
sandeep
fonte
2

Use a refpalavra - chave.

Veja a referência definitiva aqui para entender a passagem de parâmetros.
Para ser específico, veja isso para entender o comportamento do código.

EDIT: Sortfunciona na mesma referência (que é passada por valor) e, portanto, os valores são ordenados. No entanto, atribuir uma nova instância ao parâmetro não funcionará porque o parâmetro é passado por valor, a menos que você coloque ref.

Colocar refpermite que você altere o ponteiro para a referência a uma nova instância de Listno seu caso. Sem ref, você pode trabalhar no parâmetro existente, mas não pode fazer com que aponte para outra coisa.

Shahkalpesh
fonte
0

Existem duas partes de memória alocadas para um objeto do tipo de referência. Um na pilha e um na pilha. A parte na pilha (também conhecida como ponteiro) contém referência à parte na pilha - onde os valores reais são armazenados.

Quando a palavra-chave ref não é usada, apenas uma cópia de parte na pilha é criada e passada para o método - referência à mesma parte na pilha. Portanto, se você alterar algo na parte do heap, essa alteração permanecerá. Se você alterar o ponteiro copiado - atribuindo-o para se referir a outro local na pilha - não afetará o ponteiro de origem fora do método.

Thinh Tran
fonte