Lista aprobada por referencia: ayúdame a explicar este comportamiento

109

Eche un vistazo al siguiente 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;
    }
}

Supuse myListque habría pasado ref, y la salida sería

3
4

De hecho, la lista se "pasa por ref", pero solo la sortfunción tiene efecto. La siguiente declaración myList = myList2;no tiene ningún efecto.

Entonces la salida es de hecho:

10
50
100

¿Puedes ayudarme a explicar este comportamiento? Si de hecho myListno se pasa por referencia (como parece por myList = myList2no tener efecto), ¿cómo entra myList.Sort()en vigor?

Estaba asumiendo que incluso esa declaración no surtiría efecto y que el resultado sería:

100
50
10
nmdr
fuente
Solo una observación (y me doy cuenta de que el problema se ha simplificado aquí), pero parece que sería mejor ChangeListdevolver un en List<int>lugar de un voidsi de hecho se está creando una nueva lista.
Jeff B

Respuestas:

110

Está pasando una referencia a la lista , pero no está pasando la variable de lista por referencia , por lo que cuando llama ChangeListal valor de la variable (es decir, la referencia, piense que se copia "puntero"), y cambia el valor de la el parámetro dentro ChangeList no es visto por TestMethod.

tratar:

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

Esto luego pasa una referencia a la variable local myRef (como se declara en TestMethod); ahora, si reasigna el parámetro dentro ChangeList, también está reasignando la variable dentro TestMethod .

Marc Gravell
fuente
De hecho, puedo hacer eso, pero quiero saber cómo está
surtiendo
6
@Ngm: cuando llamas ChangeList, solo se copia la referencia : es el mismo objeto. Si cambia el objeto de alguna manera, todo lo que tenga una referencia a ese objeto verá el cambio.
Marc Gravell
225

Inicialmente, se puede representar gráficamente de la siguiente manera:

Estados de inicio

Luego, se aplica la ordenación myList.Sort(); Ordenar colección

Finalmente, cuando lo hizo:, myList' = myList2perdió el de la referencia pero no el original y la colección quedó ordenada.

Referencia perdida

Si usa por referencia ( ref), entonces myList'y myListse convertirá en el mismo (solo una referencia).

Nota: utilizo myList'para representar el parámetro que usa en ChangeList(porque le dio el mismo nombre que el original)

Jaider
fuente
20

Aquí tienes una forma fácil de entenderlo.

  • Su lista es un objeto creado en montón. La variable myListes una referencia a ese objeto.

  • En C # nunca pasa objetos, pasa sus referencias por valor.

  • Cuando accede al objeto de lista a través de la referencia pasada en ChangeList(mientras ordena, por ejemplo), la lista original cambia.

  • La asignación en el ChangeListmétodo se realiza al valor de la referencia, por lo tanto, no se realizan cambios en la lista original (todavía en el montón pero ya no se hace referencia a la variable del método).

Desmontar Kondolikar
fuente
10

Este enlace lo ayudará a comprender el paso por referencia en C #. Básicamente, cuando un objeto de tipo de referencia se pasa por valor a un método, solo los métodos que están disponibles en ese objeto pueden modificar el contenido del objeto.

Por ejemplo, el método List.sort () cambia el contenido de la lista, pero si asigna algún otro objeto a la misma variable, esa asignación es local para ese método. Es por eso que myList permanece sin cambios.

Si pasamos un objeto de tipo de referencia usando la palabra clave ref, entonces podemos asignar algún otro objeto a la misma variable y eso cambia todo el objeto en sí.

(Editar: esta es la versión actualizada de la documentación vinculada anteriormente).

Shekhar
fuente
5

C # solo hace una copia superficial cuando pasa por valor a menos que el objeto en cuestión se ejecute ICloneable(lo que aparentemente la Listclase no lo hace).

Lo que esto significa es que copia el Listmismo, pero las referencias a los objetos dentro de la lista siguen siendo las mismas; es decir, los punteros continúan haciendo referencia a los mismos objetos que el original List.

Si cambia los valores de las cosas que sus nuevas Listreferencias, también cambia el original List(ya que hace referencia a los mismos objetos). Sin embargo, luego cambia las myListreferencias por completo a una nueva List, y ahora solo la original Listhace referencia a esos números enteros.

Lea la sección Pasando parámetros de tipo de referencia de este artículo de MSDN sobre "Pasando parámetros" para obtener más información.

"¿Cómo clono una lista genérica en C #" de StackOverflow habla sobre cómo hacer una copia profunda de una lista.

Ethel Evans
fuente
3

Si bien estoy de acuerdo con lo que todos han dicho anteriormente. Tengo una visión diferente de este código. Básicamente, está asignando la nueva lista a la variable local myList, no a la global. si cambia la firma de ChangeList (List myList) a Private void ChangeList () verá el resultado de 3, 4.

Aquí está mi razonamiento ... Aunque la lista se pasa por referencia, piense en ello como pasar una variable de puntero por valor. Cuando llama a ChangeList (myList), está pasando el puntero a (Global) myList. Ahora esto se almacena en la variable myList (local). Así que ahora su myList (local) y myList (global) apuntan a la misma lista. Ahora haces una ordenación => funciona porque (local) myList hace referencia a la myList original (global). A continuación, crea una nueva lista y asigna el puntero a esa myList (local). Pero tan pronto como la función sale, la variable myList (local) se destruye. 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
fuente
2

Utilice la refpalabra clave.

Mire la referencia definitiva aquí para comprender los parámetros de paso.
Para ser específico, mire esto , para comprender el comportamiento del código.

EDITAR: Sortfunciona sobre la misma referencia (que se pasa por valor) y, por lo tanto, los valores están ordenados. Sin embargo, asignar una nueva instancia al parámetro no funcionará porque el parámetro se pasa por valor, a menos que ponga ref.

Poner le refpermite cambiar el puntero a la referencia a una nueva instancia de Listen su caso. Sin ref, puede trabajar en el parámetro existente, pero no puede hacer que apunte a otra cosa.

Shahkalpesh
fuente
0

Hay dos partes de memoria asignadas para un objeto de tipo de referencia. Uno en pila y otro en montón. La parte en la pila (también conocida como puntero) contiene una referencia a la parte en la pila, donde se almacenan los valores reales.

Cuando no se usa la palabra clave ref, solo se crea una copia de la parte en la pila y se pasa al método: referencia a la misma parte en el montón. Por lo tanto, si cambia algo en la parte del montón, esos cambios se mantendrán. Si cambia el puntero copiado, asignándolo para que haga referencia a otro lugar en el montón, no afectará al puntero de origen fuera del método.

Thinh Tran
fuente