Tipo de referencia en C #

79

Considere este código:

public class Program
{
    private static void Main(string[] args)
    {
        var person1 = new Person { Name = "Test" };
        Console.WriteLine(person1.Name);

        Person person2 = person1;
        person2.Name = "Shahrooz";
        Console.WriteLine(person1.Name); //Output: Shahrooz
        person2 = null;
        Console.WriteLine(person1.Name); //Output: Shahrooz
    }
}

public class Person
{
    public string Name { get; set; }
}

Obviamente, cuando se asigna person1a person2y la Namepropiedad de person2está cambiado, el Namedel person1también será cambiado. person1y person2tener la misma referencia.

¿Por qué cuando person2 = null, la person1variable tampoco será nula?

No tengo idea de nombre
fuente

Respuestas:

183

Ambos persony person2son referencias al mismo objeto. Pero estas son referencias diferentes. Entonces cuando estas corriendo

person2 = null;

está cambiando solo la referencia person2, dejando la referencia persony el objeto correspondiente sin cambios.

Supongo que la mejor manera de explicar esto es con una ilustración simplificada. Así es como se veía la situación antes person2 = null :

Antes de la asignación nula

Y aquí está la imagen después de la asignación nula:

Ingrese la descripción de la imagen aquí

Como puede ver, en la segunda imagen person2no hace referencia a nada (o null, estrictamente hablando, dado que no hace referencia a nada y la referencia a nullson condiciones diferentes, vea el comentario de Rune FS ), mientras que persontodavía hace referencia a un objeto existente.

Andrei
fuente
22
En otras palabras, person y person2 apuntan a algo, luego person2 apuntan a nada estableciendo un valor nulo.
rro
2
@ShahroozJefri ㇱ Básicamente, person1y person2son 2 tarjetas de presentación diferentes que contienen una dirección. Si lo hace person1 = person2, person1ahora es una copia de person2. Siguen siendo tarjetas de visita diferentes , pero ambas contienen la misma dirección apuntando hacia el mismo objeto. El objeto en sí no cambia.
Nolonar
Quitaría la flecha person2en la segunda imagen porque no hace referencia a nada, no es una referencia colgante.
Sameer Singh
@SameerSingh, hecho, gracias por la respuesta. Al principio pensaba lo mismo, pero cambié de opinión por alguna razón.
Andrei
2
Al usar referencias, crear una variable y asignarla a otro puntero, crea una cosa llamada Alias, donde, digamos, tenemos 3 variables, apuntando a la misma ubicación de memoria. Cuando anula una de esas variables, hay una variable menos que apunta a esa ubicación de memoria, cuando todas las variables apuntan a NULL, el GC limpia esa ubicación para asignar una nueva variable, al menos en Java el GC funciona de esa manera D:
NemesisDoom
55

Considere person1y person2 como indicadores de algún lugar de almacenamiento. En el primer paso, solo person1se mantiene la dirección del objeto del almacenamiento y luego person2se mantiene la dirección de la ubicación de memoria del objeto del almacenamiento. Más tarde, cuando asigne nulla person2, person1no se verá afectado. Por eso ves el resultado.

Puede leer: Valor frente a tipos de referencia de Joseph Albahari

Sin embargo, con los tipos de referencia, un objeto se crea en la memoria y luego se maneja a través de una referencia separada, como un puntero.

Intentaré representar el mismo concepto utilizando el siguiente diagrama.

ingrese la descripción de la imagen aquí

Se creó un nuevo objeto de tipo persona y la person1referencia (puntero) apunta a la ubicación de la memoria en el almacenamiento.

ingrese la descripción de la imagen aquí

Se creó una nueva referencia (puntero) person2que apunta a la misma en el almacenamiento.

ingrese la descripción de la imagen aquí

Se cambió la propiedad del objeto Name a un nuevo valor, person2ya que ambas referencias apuntan al mismo objeto, Console.WriteLine(person1.Name);salidas Shahrooz.

ingrese la descripción de la imagen aquí

Después de asignar nulluna person2referencia, no apuntará a nada, pero person1seguirá manteniendo la referencia al objeto.

(Finalmente, para la gestión de la memoria, debería ver La pila es un detalle de implementación, Parte uno y La pila es un detalle de implementación, Parte dos de Eric Lippert)

Habib
fuente
3
Cualquier referencia a "el montón" es un detalle de implementación irrelevante. Ni siquiera estoy seguro de que sea verdad, ciertamente no tiene por qué serlo.
jmoreno
@jmoreno, es por eso que el último párrafo, de hecho seguí el estilo en el artículo de Joseph Albahari, vinculado en la parte superior de la respuesta
Habib
4
@jmoreno, en lo que respecta a esta respuesta, es correcto que el objeto de tipo personse almacenaría en el montón y sus referencias person1y person2estaría en la pila. Pero estoy de acuerdo en que es más un detalle de implementación. Pero tener stack and heap en la respuesta (como el artículo de albahari) lo dejaría más claro en mi opinión.
Habib
1
@jmoreno y Habin: he reemplazado el uso de heap con almacenamiento , ya que el uso o no uso * del mecanismo de almacenamiento particular comúnmente conocido como heap es irrelevante.
Pieter Geerkens
1
Gran respuesta, pero es un poco exagerado para la pregunta que se hace.
Razor
14

Ha cambiado person2a la referencia null, pero person1no está haciendo referencia allí.

Lo que quiero decir es que si miramos person2y person1antes de la asignación, ambos hacen referencia al mismo objeto. Luego asigna person2 = null, por lo que la persona 2 ahora hace referencia a un tipo diferente. No eliminó el objeto al que person2se hacía referencia.

He creado este gif para ilustrarlo:

ingrese la descripción de la imagen aquí

No tengo idea de nombre
fuente
13

Porque ha establecido la referencia a null.

Cuando establece una referencia a null, la referencia en sí esnull ... no el objeto al que hace referencia.

Piense en ellos como una variable que tiene un desplazamiento de 0. persontiene el valor 120. person2tiene el valor 120. Los datos en el desplazamiento 120 son el Personobjeto. Cuando haces esto:

person2 = null;

..you're diciendo efectivamente, person2 = 0;. Sin embargo, persontodavía tiene el valor 120.

Simon Whitehead
fuente
¿Entonces la persona 2 y la persona no tienen la misma referencia?
Hacen referencia al mismo objeto ... pero son referencias separadas. Esto se reduce a la copy-by-valuesemántica. Estás copiando el valor de la referencia.
Simon Whitehead
4

Ambos persony person2 apuntan al mismo objeto. Por lo tanto, cuando cambie el nombre de cualquiera de ellos, ambos cambiarán (ya que apuntan a la misma estructura en la memoria).

Pero cuando se establece person2en null, se convierte person2en un puntero nulo, por lo que ya no apunta al mismo objeto person. No le hará nada al objeto en sí para destruirlo, y dado que persontodavía apunta / hace referencia al objeto, tampoco lo matará la recolección de basura.

Si también establece person = null, y no tiene otras referencias al objeto, el recolector de basura eventualmente lo eliminará.

Øyvind Bråthen
fuente
2

person1y person2apunte a la misma dirección de memoria. Cuando anula person2, anula la referencia, no la dirección de memoria, por lo que person1continúa refiriéndose a esa dirección de memoria, esa es la razón. Si cambia Classs Persona a Struct, el comportamiento cambiará.

pointnetdesveloper
fuente
Pueden apuntar o no a la misma dirección de memoria (o cualquier dirección, para el caso). Lo importante es que identifiquen el mismo objeto . En algunos tipos de recolectores de basura concurrentes, es posible que mientras un objeto se reubica, algunas referencias pueden contener la dirección anterior mientras que otras identifican la nueva [el código que escribe en cualquiera de las direcciones puede tener que bloquearse hasta que se actualicen todas las referencias, y el código que compara direcciones tendría que ver si una dirección era "actual" y la otra no lo era y, de ser así, encontrar la dirección "nueva" asociada con la anterior.]
supercat
2

Me parece más útil pensar en los tipos de referencia como si tuvieran ID de objeto. Si uno tiene una variable de tipo de clase Car, la declaración myCar = new Car();le pide al sistema que cree un auto nuevo e informe su ID (digamos que es el objeto # 57); luego pone "objeto # 57" en variable myCar. Si uno escribe Car2 = myCar;, escribe "objeto # 57" en la variable Car2. Si uno escribe car2.Color = blue;, eso indica al sistema que busque el automóvil identificado por Car2 (por ejemplo, el objeto # 57) y lo pinte de azul.

Las únicas operaciones que se realizan directamente en los ID de objeto son la creación de un nuevo objeto y obtener el ID, obtener un ID "en blanco" (es decir, nulo), copiar un ID de objeto a una variable o ubicación de almacenamiento que pueda contenerlo, verificar si hay dos los ID de objeto coinciden (hacen referencia al mismo objeto). Todas las demás solicitudes le piden al sistema que encuentre el objeto al que hace referencia un ID y actúe sobre ese objeto (sin afectar la variable u otra entidad que tenía el ID).

En las implementaciones existentes de .NET, es probable que las variables de objeto contengan punteros a objetos almacenados en un montón de basura recolectada, pero ese es un detalle de implementación inútil porque hay una diferencia crítica entre una referencia de objeto y cualquier otro tipo de puntero. Generalmente se supone que un puntero representa la ubicación de algo que permanecerá el tiempo suficiente para trabajar con él. Las referencias a objetos no lo hacen. Un fragmento de código puede cargar el registro SI con una referencia a un objeto ubicado en la dirección 0x12345678, comenzar a usarlo y luego ser interrumpido mientras el recolector de basura mueve el objeto a la dirección 0x23456789. Eso sonaría como un desastre, pero la basura examinará los metadatos asociados con el código, observará que el código usó SI para contener la dirección del objeto que estaba usando (es decir, 0x12345678), determine que el objeto que estaba en 0x12345678 se ha movido a 0x23456789 y actualice SI para que contenga 0x23456789 antes de que regrese. Tenga en cuenta que en ese escenario, el recolector de basura cambió el valor numérico almacenado en SI, pero se refería ael mismo objeto antes de la mudanza y después. Si antes del movimiento se refería al objeto número 23 592 creado desde el inicio del programa, continuará haciéndolo después. Curiosamente, .NET no almacena ningún identificador único e inmutable para la mayoría de los objetos; dadas dos instantáneas de la memoria de un programa, no siempre será posible saber si algún objeto en particular en la primera instantánea existe en la segunda, o si se han abandonado todos los rastros y se ha creado un nuevo objeto que se parece a él en todos los detalles observables.

Super gato
fuente
1

person1 y person2 son dos referencias separadas en la pila que apuntan al mismo objeto Person en el montón.

Cuando elimina una de las referencias, se elimina de la pila y ya no apunta al objeto Persona en el montón. La otra referencia permanece, aún apuntando al objeto Person existente en el montón.

Una vez que se eliminan todas las referencias al objeto Persona, eventualmente el recolector de basura eliminará el objeto de la memoria.

Ryan Spears
fuente
1

Cuando crea un tipo de referencia, en realidad está copiando una referencia con todos los objetos apuntando a la misma ubicación de memoria, sin embargo, si ha asignado Person2 = Null, no tendrá ningún efecto ya que person2 es solo una copia de la persona de referencia y acabamos de borrar una copia de referencia .

Suraj Singh
fuente
1

Tenga en cuenta que puede obtener la semántica de valor cambiando a struct.

public class Program
{
    static void Main()
    {
        var person1 = new Person { Name = "Test" };
        Console.WriteLine(person1.Name);

        Person person2 = person1;
        person2.Name = "Shahrooz";
        Console.WriteLine(person1.Name);//Output:Test
        Console.WriteLine(person2.Name);//Output:Shahrooz
        person2 = new Person{Name = "Test2"};
        Console.WriteLine(person2.Name);//Output:Test2

    }
}
public struct Person
{
    public string Name { get; set; }
}
Mark Hurd
fuente
0
public class Program
{
    private static void Main(string[] args)
    {
        var person = new Person {Name = "Test"};
        Console.WriteLine(person.Name);

        Person person2 = person;
        person2.Name = "Shahrooz";
        Console.WriteLine(person.Name);//Output:Shahrooz
        // Here you are just erasing a copy to reference not the object created.
        // Single memory allocation in case of reference type and  parameter
         // are passed as a copy of reference type .   
        person2 = null;
        Console.WriteLine(person.Name);//Output:Shahrooz

    }
}
public class Person
{
    public string Name { get; set; }
}
Suraj Singh
fuente
@doctorlove olvidó los comentarios en realidad :-(, Bueno, para el tipo de referencia, solo se asigna un espacio de memoria y la Persona 2 o la Persona 1 ... son solo una copia de ese tipo de referencia que apunta hacia la ubicación de la memoria, Borrando una copia del tipo de referencia no afectará nada.
Suraj Singh
@doctorlove Gracias por tu sugerencia, lo tendré en cuenta.
Suraj Singh
0

Primero copia la referencia person1a person2. Ahora person1y person2haga referencia al mismo objeto, lo que significa que Namese pueden observar modificaciones en el valor de ese objeto (es decir, cambiar la propiedad) en ambas variables. Luego, al asignar un valor nulo, está eliminando la referencia que acaba de asignar person2. Solo está asignado a person1ahora. Tenga en cuenta que la referencia en sí no es modifica.

Si tuviera una función que aceptara un argumento por referencia, podría pasar reference to person1 por referencia y podría cambiar la referencia en sí:

public class Program
{
    private static void Main(string[] args)
    {
        var person1 = new Person { Name = "Test" };
        Console.WriteLine(person1.Name);

        PersonNullifier.NullifyPerson(ref person1);
        Console.WriteLine(person1); //Output: null
    }
}


class PersonNullifier
{
    public static void NullifyPerson( ref Person p ) {
        p = null;
    }
}

class  Person {
    public string Name{get;set;}
}
Asad Saeeduddin
fuente
0

Diciendo:

person2.Name = "Shahrooz";

sigue la referencia de person2y "muta" (cambia) el objeto al que la referencia pasa a conducir. La referencia en sí (el valor de person2) no se modifica; seguimos haciendo referencia a la misma instancia en la misma "dirección".

Asignar a person2como en:

person2 = null;

cambia la referencia . Ningún objeto se cambia. En este caso, la flecha de referencia es "movido" de un objeto a "nada", null. Pero una asignación como person2 = new Person();también solo cambiaría la referencia. Ningún objeto está mutado.

Jeppe Stig Nielsen
fuente