c# - pasar - Paso de parámetros.NET-por referencia v/s por valor
pasar objetos como parametros en c# (6)
Estoy tratando de validar mi comprensión de cómo C # / .NET / CLR trata los tipos de valor y los tipos de referencia. He leído tantas explicaciones contradictorias que todavía
Esto es lo que entiendo hoy, corríjame si mis suposiciones son erróneas.
Los tipos de valor como int, etc., viven en la pila, los tipos de referencia viven en el montón administrado, sin embargo, si un tipo de referencia tiene, por ejemplo, una variable de instancia de tipo doble, vivirá junto con su objeto en el montón
La segunda parte es de lo que estoy más confundido.
Consideremos una clase simple llamada Persona.
La persona tiene una propiedad llamada Nombre.
Digamos que creo una instancia de Person en otra clase, la llamaremos UselessUtilityClass.
Considere el siguiente código:
class UselessUtilityClass
{
void AppendWithUnderScore(Person p)
{
p.Name = p.Name + "_";
}
}
y luego en algún lugar que hacemos:
Person p = new Person();
p.Name = "Priest";
UselessUtilityClass u = new UselessUtilityClass();
u.AppendWithUnderScore(p);
La persona es un tipo de referencia, cuando se pasa a UselessUtilityClass - aquí es donde voy - las tuercas ... la variable V, que es una instancia de la referencia de la persona, se pasa por VALUE , lo que significa que cuando escribo p.Name veré " Sacerdote_"
Y luego si escribo
Person p2 = p;
Y yo hago
p2.Name = "No es un sacerdote";
Y escriba el nombre de p como a continuación obtendré "No un sacerdote"
Console.WriteLine(p.Name) // will print "Not a Priest"
Esto se debe a que son tipos de referencia y apuntan a la misma dirección en la memoria.
¿Mi entendimiento es correcto?
Creo que hay un malentendido cuando la gente dice que Todos los objetos en .NET se pasan por Referencia , esto no se basa en lo que pienso. Podría estar equivocado, por eso he venido a los Apiladores.
Los tipos de valor, como int, etc., viven en la pila, los tipos de referencia viven en el montón administrado, sin embargo, si un tipo de referencia tiene, por ejemplo, una variable de instancia de tipo double, vivirá junto con su objeto en el montón
Correcto.
También puede describirlo como las variables de instancia que forman parte del área de memoria asignada para la instancia en el montón.
La VARIABLE p que es una instancia de la Referencia de persona se pasa por VALUE
La variable en realidad no es una instancia de la clase. La variable es una referencia a la instancia de la clase. La referencia se pasa por valor, lo que significa que usted pasa una copia de la referencia. Esta copia todavía apunta a la misma instancia que la referencia original.
Creo que hay algunos malentendidos cuando la gente dice: Todos los objetos en .NET se pasan por Referencia
Sí, eso es definitivamente un malentendido. Todos los parámetros se pasan por valor (a menos que use las palabras clave ref
o out
para pasarlas por referencia). Pasar una referencia no es lo mismo que pasar por referencia .
Una referencia es un tipo de valor, lo que significa que todo lo que pasa como parámetros son tipos de valor. Nunca se pasa una instancia de objeto en sí, siempre es referencia.
Los tipos de valor, como int, etc., viven en la pila. Los tipos de referencia viven en el montón administrado, sin embargo, si un tipo de referencia tiene, por ejemplo, una variable de instancia de tipo double, vivirá junto con su objeto en el montón
No esto no es correcto. Una declaración correcta es "Las variables locales y los parámetros formales de tipo de valor que no están directamente en un bloque de iteradores ni las variables externas cerradas de un método lambda o anónimo se asignan en la pila del sistema del hilo en ejecución en la implementación de Microsoft de la CLI. y la implementación de Microsoft de C # ".
No hay ningún requisito de que ninguna versión de C # o ninguna versión de la CLI use la pila del sistema para nada. Por supuesto, lo hacemos porque es una estructura de datos conveniente para variables locales y parámetros formales de tipo de valor que no están directamente en un bloque de iteradores o en variables externas cerradas de un método lambda o anónimo.
Vea mis artículos sobre este tema para una discusión de (1) por qué esto es un detalle de implementación, y (2) qué beneficios obtenemos de esta elección de implementación, y (3) qué restricciones el deseo de hacer esta elección de implementación conduce a la diseño del lenguaje.
http://blogs.msdn.com/ericlippert/archive/2009/04/27/the-stack-is-an-implementation-detail.aspx
La persona es un tipo de referencia, cuando se pasa a UselessUtilityClass - aquí es donde voy - las tuercas ...
Tomar una respiración profunda.
Una variable es una ubicación de almacenamiento. Cada ubicación de almacenamiento tiene un tipo asociado.
Una ubicación de almacenamiento cuyo tipo asociado es un tipo de referencia puede contener una referencia a un objeto de ese tipo, o puede contener una referencia nula.
Una ubicación de almacenamiento cuyo tipo asociado es un tipo de valor siempre contiene un objeto de ese tipo.
El valor de una variable es el contenido de la ubicación de almacenamiento .
La VARIABLE p que es una instancia de la Referencia de persona se pasa por VALUE,
La variable p es una ubicación de almacenamiento. Contiene una referencia a una instancia de Person. Por lo tanto, el valor de la variable es una referencia a una Persona. Ese valor, una referencia a una instancia, se pasa al destinatario. Ahora, la otra variable, a la que también ha llamado confusamente "p", contiene el mismo valor: el valor es una referencia a un objeto en particular.
Ahora, también es posible pasar una referencia a una variable , que muchas personas encuentran confusa. Una mejor manera de pensarlo es cuando dices
void Foo(ref int x) { x = 10; }
...
int p = 3456;
Foo(ref p);
lo que esto significa es que "x es un alias para la variable p". Es decir, x y p son dos nombres para la misma variable . Entonces, cualquiera que sea el valor de p es, también es el valor de x, porque son dos nombres para la misma ubicación de almacenamiento.
¿Tiene sentido ahora?
Cuando pasa a una persona, está haciendo una copia de la referencia, no confunda esto con una copia del objeto. En otras palabras, está creando una segunda referencia, al mismo objeto, y luego pasa eso.
Cuando pasa por ref (con la palabra clave ref / out), pasa la misma referencia al objeto que está utilizando en la persona que llama, en lugar de crear una copia de la referencia.
El término "pasar por valor" es un poco engañoso.
Hay dos cosas que estás haciendo:
1) pasar un tipo de referencia (Persona p) como parámetro a un método
2) establecer una variable de tipo de referencia (Persona p2) a una variable ya existente (Persona p)
Echemos un vistazo a cada caso.
Caso 1
Usted creó la Persona p que apunta a una ubicación en la memoria, llamemos a esta ubicación x. Cuando entras en el método AppendWithUnderScore
, ejecutas el siguiente código:
p.Name = p.Name + "_";
La llamada al método crea una nueva variable local p, que apunta a la misma ubicación en la memoria: x. Entonces, si modifica p dentro de su método, cambiará el estado de p.
Sin embargo, dentro de este método, si establece p = null
, no anulará la p fuera del método. Este comportamiento se llama "pasar por valor"
Caso 2
Este caso es similar al caso anterior, pero ligeramente diferente. Cuando crea una nueva variable p2 = p, simplemente está diciendo que p2 hace referencia al objeto en la ubicación de p. Así que ahora, si modifica p2, está modificando p, ya que hacen referencia al mismo objeto. Si ahora dice que p2 = nulo, entonces p también será nulo. Note la diferencia entre este comportamiento y el comportamiento dentro de la llamada al método. Esa diferencia de comportamiento describe cómo funciona "pasar por valor" cuando se llaman métodos
Las especificaciones no dicen nada sobre dónde asignar tipos de valor y objetos. Sería una implementación correcta de C # decir que se asigna todo en el montón y hay situaciones Atr donde los valores se asignan en el montón distintos de los que se escriben.
int i = 4; Func dele = () => (objeto) i;
El resultado será que (una copia de) se asigne en el montón porque el compilador lo convertirá en un miembro de una clase aunque no esté declarado como tal. Aparte de eso, eres bastante acertado. Y no todo se pasa como referencia. Sería más cercano a la verdad afirmar que todos los parámetros se pasaron por valor pero aún no son del todo correctos (por ejemplo, ref o out).
Tal vez algunos ejemplos puedan mostrar las diferencias entre los tipos de referencia y los tipos de valor y entre pasar por referencia y pasar por valor:
//Reference type
class Foo {
public int I { get; set; }
}
//Value type
struct Boo {
//I know, that mutable structures are evil, but it only an example
public int I { get; set; }
}
class Program
{
//Passing reference type by value
//We can change reference object (Foo::I can changed),
//but not reference itself (f must be the same reference
//to the same object)
static void ClassByValue1(Foo f) {
//
f.I++;
}
//Passing reference type by value
//Here I try to change reference itself,
//but it doesn''t work!
static void ClassByValue2(Foo f) {
//But we can''t change the reference itself
f = new Foo { I = f.I + 1 };
}
//Passing reference typ by reference
//Here we can change Foo object
//and reference itself (f may reference to another object)
static void ClassByReference(ref Foo f) {
f = new Foo { I = -1 };
}
//Passing value type by value
//We can''t change Boo object
static void StructByValue(Boo b) {
b.I++;
}
//Passing value tye by reference
//We can change Boo object
static void StructByReference(ref Boo b) {
b.I++;
}
static void Main(string[] args)
{
Foo f = new Foo { I = 1 };
//Reference object passed by value.
//We can change reference object itself, but we can''t change reference
ClassByValue1(f);
Debug.Assert(f.I == 2);
ClassByValue2(f);
//"f" still referenced to the same object!
Debug.Assert(f.I == 2);
ClassByReference(ref f);
//Now "f" referenced to newly created object.
//Passing by references allow change referenced itself,
//not only referenced object
Debug.Assert(f.I == -1);
Boo b = new Boo { I = 1 };
StructByValue(b);
//Value type passes by value "b" can''t changed!
Debug.Assert(b.I == 1);
StructByReference(ref b);
//Value type passed by referenced.
//We can change value type object!
Debug.Assert(b.I == 2);
Console.ReadKey();
}
}