2010-01-24 20 views
17

Estoy tratando de validar mi comprensión de cómo C# /. NET/CLR trata los tipos de valores y tipos de referencia. He leído tantas explicaciones contradictorias que todavía.NET Paso de parámetros - por referencia v/s por valor

Esto es lo que entiendo hoy, por favor corríjanme si mis suposiciones son incorrectas.

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 una variable de instancia de tipo double, vivirá junto con su objeto en el montón

La segunda parte es lo que más me causa confusión.

Consideremos una clase simple llamada Persona.

La persona tiene una propiedad llamada Nombre.

Digamos que creo una instancia de Persona en otra clase, lo 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); 

persona es un tipo de referencia, cuando se pasa a UselessUtilityClass - aquí es donde voy - tuercas .. .el VARIABLE p que es una instancia de la referencia de persona pasada por VALOR, lo que significa que cuando escriba p.Name veré "Priest_"

Y entonces si escribía

Person p2 = p; 

Y lo hago

p2.Name = "No es un sacerdote";

y escribir el nombre de p, como a continuación voy a conseguir "No es un Sacerdote"

Console.WriteLine(p.Name) // will print "Not a Priest" 

Esto es debido a que son los tipos de referencia y apuntan a la misma dirección en la memoria.

¿Es correcto mi entendimiento?

Creo que hay un malentendido cuando la gente dice Todos los objetos en .NET se pasan por la referencia, esto no se basa en lo que creo. Podría estar equivocado, es por eso que he venido a los Stackers.

Respuesta

27

tipos de valor como int, etc vivir en la pila. Tipos de referencia vivir en el montón administrado embargo si tiene un tipo de referencia, por ejemplo, tiene una variable de instancia de tipo double, vivirá junto con su objeto en el montón

No, esto no es correcto. Una afirmación correcta es "Variables locales y parámetros formales del tipo de valor que no están directamente en un bloque iterador ni variables externas cerradas de un método lambda o anónimo se asignan en la pila del sistema del subproceso que se ejecuta en la implementación de Microsoft de la CLI y la implementación de Microsoft de C# ".

No es necesario que ninguna versión de C# ni 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 iterador o variables externas cerradas de un método lambda o anónimo.

Consulte 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 desea realizar esta implementación la elección conduce al diseño del lenguaje.

http://blogs.msdn.com/ericlippert/archive/2009/04/27/the-stack-is-an-implementation-detail.aspx

http://blogs.msdn.com/ericlippert/archive/2009/05/04/the-stack-is-an-implementation-detail-part-two.aspx

persona es un tipo de referencia, cuando pasó a UselessUtilityClass - aquí es donde voy - frutos secos ...

Respire hondo.

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 persona de referencia se pasa por valor,

La variable p es un lugar de almacenamiento. Contiene una referencia a una instancia de Persona. 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, que confusamente también se llama "p", contiene el mismo valor; el valor es una referencia a un objeto particular.

Ahora, también es posible pasar una referencia a variable, que a mucha gente le resulta confusa. Una mejor manera de pensar en ello es cuando se dice

void Foo(ref int x) { x = 10; } 
... 
int p = 3456; 
Foo(ref p); 

lo que esto significa es "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, ese también es el valor de x, porque son dos nombres para la misma ubicación de almacenamiento.

¿Tiene sentido ahora?

1

Cuando pasa a una persona, está haciendo una copia de la referencia; no la confunda con una copia del objeto. En otras palabras, está creando una segunda referencia, al mismo objeto y luego pasando eso.

Cuando pasa por ref (con la palabra clave ref/out), está pasando la misma referencia al objeto que está utilizando en la persona que llama, en lugar de crear una copia de la referencia.

8

tipos de valor como int etc viven en la pila, Tipos de referencia viven en el montón administrado sin embargo, si un tipo de referencia tiene, por ejemplo, tiene una instancia variable 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 persona de referencia se pasa por VALOR

la variable no es realmente 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 pasa una copia de la referencia. Esta copia aún apunta a la misma instancia que la referencia original.

Creo que hay un malentendido pasando cuando la gente dice Todos los objetos en .NET se pasan por referencia

Sí, esto es sin duda un malentendido. Todos los parámetros se pasan por el valor (a menos que utilice 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 haya pasado como parámetros son tipos de valores. Nunca pasa una instancia de objeto, siempre es referencia.

+0

La declaración sobre stack y heap no es del todo correcta. Consulte mi respuesta para obtener una explicación –

+0

Ser un tipo de valor no tiene nada que ver con dónde se almacena. La única diferencia es que los tipos de valores tienen semántica de copia. El resto está definido por la implementación. – wj32

+0

@Guffa: "Los tipos de valor como int etc. viven en la pila. Los tipos de referencia viven en el heap administrado. Sin embargo, si un tipo de referencia tiene una variable de instancia de tipo double, vivirá junto con su objeto en el heap. Correcto." Primero, esto depende de la implementación. En segundo lugar, no es cierto en la implementación de Microsoft; cf., por ejemplo, locales capturados. – jason

0

El término "pasar por valor" es un poco engañoso.

Hay dos cosas que está haciendo:

1) que pasan a un tipo de referencia (persona p) como un parámetro a un método

2) el establecimiento de un tipo refence variable (p2 persona) a una ya variable existente (Persona p)

Veamos cada caso.

Caso 1

Creaste persona p apunta a una ubicación en la memoria, vamos a llamar a esta ubicación x. Cuando uno entra en AppendWithUnderScore método, se ejecuta 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. Por lo tanto, si modifica p dentro de su método, cambiará el estado de p..

Sin embargo, dentro de este método, si configura p = null, entonces no nula la p fuera del método. Este comportamiento se denomina "paso 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. Entonces, si modifica p2, está modificando p ya que hacen referencia al mismo objeto. Si ahora dice p2 = null, ahora p también será nulo. Tenga en cuenta la diferencia entre este comportamiento y el comportamiento dentro de la llamada al método. Esa diferencia de comportamiento describe cómo funciona el "paso por valor" al llamar a los métodos

0

Las especificaciones no dicen nada sobre dónde asignar tipos de valores y objetos. Sería una implementación correcta de C# decir asignar todo en el montón y allí situaciones Atr donde los valores se asignan en el montón que no sean los que escribe.

int i = 4; Func dele =() => (objeto) i;

Otorgará (una copia de) i asignado en el montón porque el compilador lo convertirá en miembro de una clase aunque no esté declarado como tal. Aparte de eso, eres bastante acertado. Y no todo pasa como referencia. Estaría más cerca de la verdad decir que cada parámetro se pasó por valor pero aún no es del todo correcto (por ejemplo, ref o out).

+0

No, escribir ese código no encajonará el valor de la variable. Pondrá una referencia en un cierre, pero eso es completamente diferente. No será hasta que use el delegado que el valor estará encuadrado. – Guffa

+0

@Guffa Bueno, no escribí nada sobre el boxeo :) y como tener una referencia a un valor asignado a la pila es una idea terrible almacenar una referencia a ella dará como resultado que el valor al que se hace referencia sea asignado y no apilado. La única versión en la que desearía una referencia a un valor asignado a la pila sería a partir de otro valor asignado a la pila con una vida ni mayor que la del valor al que se hace referencia. –

1

Tal vez esto algunos ejemplos que pueden mostrar diferencias entre los tipos de referencia y los tipos de valor y entre paso por referencia y que pasa 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(); 
    } 

} 
Cuestiones relacionadas