Tipo de referencia en C #

Considera 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 una person1 a una person2 y se person2 la propiedad Name de la person1 , también se cambiará el Name de la person1 . person1 y person2 tienen la misma referencia.

¿Por qué es que cuando person2 = null , la variable person1 tampoco será nula?

Tanto la person como la person2 son referencias al mismo objeto. Pero estas son referencias diferentes. Entonces cuando estás corriendo

 person2 = null; 

está cambiando solo la persona de referencia2, sin modificar la person referencia y el objeto correspondiente.

Supongo que la mejor manera de explicar esto es con una ilustración simplificada. Así es como se veía la situación antes de 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, person2 no hace referencia a nada (o null , en sentido estricto, ya que la referencia nada y la referencia a null son condiciones diferentes, vea el comentario de Rune FS ), mientras la person sigue haciendo referencia a un objeto existente.

Considere person1 y person2 como punteros a algún lugar en almacenamiento. En el primer paso, solo la person1 retiene la dirección del objeto del almacenamiento y más tarde la person2 retiene la dirección de la ubicación de la memoria del objeto desde el almacenamiento. Más tarde, cuando asigna null a person2 , person1 no se ve afectado. Es por eso que ves el resultado.

Puede leer: Value vs Reference Types 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, más bien como un puntero.

Trataré de representar el mismo concepto usando el siguiente diagtwig.

enter image description here

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

enter image description here

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

enter image description here

Se cambió el nombre de la propiedad del objeto a un nuevo valor, a través de person2 ya que ambas referencias apuntan al mismo objeto, Console.WriteLine(person1.Name); salidas Shahrooz .

enter image description here

Después de asignar person2 referencia null a person2 , no apuntará a nada, pero person1 aún mantiene la referencia al objeto.

(Finalmente, para la gestión de la memoria, debería ver The Stack Is An Implementation Detail, Part One y The Stack Is An Implementation Detail, Part Two, de Eric Lippert)

Ha cambiado person2 para hacer referencia a null , pero person1 no hace referencia allí.

Lo que quiero decir es que si miramos a person2 y person1 antes 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 person2 el objeto al que se hizo referencia a person2 .

Creé este gif para ilustrarlo:

enter image description here

Porque ha establecido la referencia a null .

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

Piense en ellos como una variable que contiene un desplazamiento desde 0. la person tiene el valor 120. person2 tiene el valor 120. Los datos en el desplazamiento 120 son el objeto Person . Cuando haces esto:

 person2 = null; 

… dices efectivamente, person2 = 0; . Sin embargo, la person todavía tiene el valor 120.

Tanto la person como la person2 apuntan al mismo objeto. Por lo tanto, cuando cambie el nombre de cualquiera de los dos, ambos se cambiarán (ya que apuntan a la misma estructura en la memoria).

Pero cuando configura a person2 como null , convierte a la person2 en un puntero nulo, por lo que ya no apunta al mismo objeto como person . No le hará nada al objeto en sí mismo para destruirlo, y como la person todavía apunta / hace referencia al objeto, tampoco será destruido por la recolección de basura.

Si también establece person = null , y no tiene otras referencias al objeto, eventualmente será eliminado por el recolector de elementos no utilizados.

person1 y person2 apuntan a la misma dirección de memoria. Cuando person2 , person2 la referencia, no la dirección de memoria, por lo que la person1 continúa person1 a esa dirección de memoria, esa es la razón. Si cambia a la Classs Person Struct en una Struct , el comportamiento cambiará.

Me resulta más útil pensar en los tipos de referencia como la celebración de ID de objeto. Si uno tiene una variable del tipo de clase Car , la statement myCar = new Car(); le pide al sistema que cree un automóvil nuevo e informe su identificación (digamos que es el objeto n. ° 57); luego pone “objeto # 57” en la variable myCar . Si uno escribe Car2 = myCar; , que escribe “objeto # 57” en la variable Car2. Si uno escribe car2.Color = blue; , que instruye al sistema para encontrar el coche identificado por Car2 (por ejemplo, el objeto n. ° 57) y pintarlo de azul.

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

En implementaciones existentes de .NET, es probable que las variables de objeto contengan punteros a objetos almacenados en un montón recogido de basura, pero eso 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. Por lo general, se asume que un puntero representa la ubicación de algo que permanecerá colocado el tiempo suficiente para ser trabajado. Las referencias de objeto no. Una pieza 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 interrumpirse mientras el recolector de basura mueve el objeto a la dirección 0x23456789. Eso parecería un desastre, pero la basura examinará los metadatos asociados con el código, observará que el código usó SI para mantener la dirección del objeto que estaba usando (es decir, 0x12345678), determina que el objeto que estaba en 0x12345678 se movió a 0x23456789, y actualice SI para contener 0x23456789 antes de que vuelva. Tenga en cuenta que en ese caso, el recolector de basura cambió el valor numérico almacenado en SI, pero se refirió al mismo objeto antes del movimiento y después. Si antes del movimiento se refería al objeto 23.592º creado desde el inicio del progtwig, 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 progtwig, no siempre será posible saber si existe un objeto en particular en la primera instantánea en el segundo, 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.

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

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

Una vez que se eliminan todas las referencias al objeto Person, eventualmente el Garbage Collector eliminará el objeto de la memoria.

Cuando crea un tipo de referencia, en realidad copia una referencia con todos los objetos que apuntan a la misma ubicación de memoria. Sin embargo, si ha asignado Person2 = Null, no tendrá efecto ya que person2 es solo una copia de la persona de referencia y hemos borrado una copia de referencia .

Tenga en cuenta que puede obtener semántica de valores cambiando a una 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; } } 
 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; } } 

Primero copie la referencia a person1 a person2 . Ahora person1 y person2 refieren al mismo objeto, lo que significa que se pueden observar modificaciones en el valor de ese objeto (es decir, cambiar la propiedad Name ) en ambas variables. Luego, al asignar null, está eliminando la referencia que acaba de asignar a person2 . Solo está asignado a person1 ahora. Tenga en cuenta que la referencia en sí no cambia.

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

Diciendo:

 person2.Name = "Shahrooz"; 

sigue la referencia de person2 y “muta” (cambia) el objeto al que conduce la referencia. La referencia en sí misma (el valor de person2 ) no se modifica; todavía referimos la misma instancia a la misma “dirección”.

Asignar a person2 como en:

 person2 = null; 

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