¿Cuál es el uso de "ref" para las variables de tipo de referencia en C #?

176

Entiendo que si paso a-tipo de valor ( int, struct, etc.) como un parámetro (sin la refpalabra clave), una copia de esa variable se pasa al método, pero si uso elref palabra clave se pasa una referencia a esa variable, No uno nuevo.

Pero con los tipos de referencia, como las clases, incluso sin la refpalabra clave, se pasa una referencia al método, no una copia. Entonces, ¿para qué sirve la refpalabra clave con tipos de referencia?


Toma por ejemplo:

var x = new Foo();

¿Cuál es la diferencia entre lo siguiente?

void Bar(Foo y) {
    y.Name = "2";
}

y

void Bar(ref Foo y) {
    y.Name = "2";
}
Andreas Grech
fuente

Respuestas:

154

Puede cambiar qué foopuntos usar y:

Foo foo = new Foo("1");

void Bar(ref Foo y)
{
    y = new Foo("2");
}

Bar(ref foo);
// foo.Name == "2"
usuario7116
fuente
17
así que básicamente obtienes una referencia a la referencia original
lhahne
2
Puede cambiar a qué se refiere la referencia original, así que sí.
user7116
1
Chris, tu explicación es genial; Gracias por ayudarme a entender este concepto.
Andreas Grech
44
Entonces, ¿usar 'ref' en un objeto es como usar punteros dobles en C ++?
Tom Hazel
1
@TomHazel: -ish , siempre que esté utilizando punteros "dobles" en C ++ para cambiar lo que apunta un puntero.
user7116
29

Hay casos en los que desea modificar la referencia real y no el objeto señalado:

void Swap<T>(ref T x, ref T y) {
    T t = x;
    x = y;
    y = t;
}

var test = new[] { "0", "1" };
Swap(ref test[0], ref test[1]);
Mehrdad Afshari
fuente
21

Jon Skeet escribió un gran artículo sobre el paso de parámetros en C #. Detalla claramente el comportamiento exacto y el uso de pasar parámetros por valor, por referencia ( ref) y por salida ( out).

Aquí hay una cita importante de esa página en relación con los refparámetros:

Los parámetros de referencia no pasan los valores de las variables utilizadas en la invocación del miembro de la función, sino que utilizan las variables mismas. En lugar de crear una nueva ubicación de almacenamiento para la variable en la declaración del miembro de la función, se utiliza la misma ubicación de almacenamiento, por lo que el valor de la variable en el miembro de la función y el valor del parámetro de referencia siempre serán los mismos. Los parámetros de referencia necesitan el modificador de referencia como parte de la declaración y la invocación, lo que significa que siempre está claro cuando se pasa algo por referencia.

Noldorin
fuente
11
Me gusta la analogía de pasarle la correa de su perro a un amigo por pasar un valor de referencia ... sin embargo, se descompone rápidamente, porque creo que probablemente se dará cuenta si su amigo cambia su shitzu a un doberman antes de que le devuelva the leash ;-)
corlettk
16

Muy bien explicado aquí: http://msdn.microsoft.com/en-us/library/s6938f28.aspx

Resumen del artículo:

Una variable de un tipo de referencia no contiene sus datos directamente; Contiene una referencia a sus datos. Cuando pasa un parámetro de tipo de referencia por valor, es posible cambiar los datos señalados por la referencia, como el valor de un miembro de la clase. Sin embargo, no puede cambiar el valor de la referencia misma; es decir, no puede usar la misma referencia para asignar memoria para una nueva clase y hacer que persista fuera del bloque. Para hacer eso, pase el parámetro usando la palabra clave ref o out.

himanshupareek66
fuente
44
La explicación es realmente muy buena. Sin embargo, se desaconsejan las respuestas de solo enlace en SO. Agregué un resumen del artículo, para conveniencia de los lectores aquí.
Marcel
10

Cuando pasa un tipo de referencia con la palabra clave ref, pasa la referencia por referencia, y el método al que llama puede asignar un nuevo valor al parámetro. Ese cambio se propagará al alcance de la llamada. Sin referencia, la referencia se pasa por valor, y esto no sucede.

C # también tiene la palabra clave 'out' que es muy parecida a ref, excepto que con 'ref', los argumentos deben inicializarse antes de llamar al método, y con 'out' debe asignar un valor en el método de recepción.

Rytmis
fuente
5

Le permite modificar la referencia pasada. Ej.

void Bar()
{
    var y = new Foo();
    Baz(ref y);
}

void Baz(ref Foo y)
{
    y.Name = "2";

    // Overwrite the reference
    y = new Foo();
}

También se puede utilizar a cabo si no se preocupan por la referencia aprobada en:

void Bar()
{
    var y = new Foo();
    Baz(out y);
}

void Baz(out Foo y)
{
    // Return a new reference
    y = new Foo();
}
Hans Van Slooten
fuente
4

Otro montón de código

class O
{
    public int prop = 0;
}

class Program
{
    static void Main(string[] args)
    {
        O o1 = new O();
        o1.prop = 1;

        O o2 = new O();
        o2.prop = 2;

        o1modifier(o1);
        o2modifier(ref o2);

        Console.WriteLine("1 : " + o1.prop.ToString());
        Console.WriteLine("2 : " + o2.prop.ToString());
        Console.ReadLine();
    }

    static void o1modifier(O o)
    {
        o = new O();
        o.prop = 3;
    }

    static void o2modifier(ref O o)
    {
        o = new O();
        o.prop = 4;
    }
}
Svend
fuente
3

Además de las respuestas existentes:

Como solicitó la diferencia de los 2 métodos: no existe una variación co (ntra) al usar refo out:

class Foo { }
class FooBar : Foo { }

static void Bar(Foo foo) { }
static void Bar(ref Foo foo) { foo = new Foo(); }

void Main()
{
    Foo foo = null;
    Bar(foo);           // OK
    Bar(ref foo);       // OK

    FooBar fooBar = null;
    Bar(fooBar);        // OK (covariance)
    Bar(ref fooBar);    // compile time error
}
springy76
fuente
1

Un parámetro en un método parece estar siempre pasando una copia, la pregunta es una copia de qué. Un constructor de copias realiza una copia de un objeto y, dado que todas las variables son Object en C #, creo que este es el caso para todas ellas. Las variables (objetos) son como las personas que viven en algunas direcciones. Cambiamos a las personas que viven en esas direcciones o podemos crear más referencias a las personas que viven en esas direcciones en la guía telefónica (hacer copias superficiales). Por lo tanto, más de un identificador puede referirse a la misma dirección. Los tipos de referencia desean más espacio, por lo que, a diferencia de los tipos de valores que están directamente conectados por una flecha a su identificador en la pila, tienen valor para otra dirección en el montón (un espacio más grande para habitar). Este espacio debe tomarse del montón.

Tipo de valor: Identificador (contiene el valor = dirección del valor de la pila) ----> Valor del tipo de valor

Tipo de referencia: Identificador (contiene el valor = dirección del valor de la pila) ----> (contiene el valor = dirección del valor del montón) ----> Valor del montón (la mayoría de las veces contiene direcciones a otros valores), imagine más flechas pegadas en diferentes direcciones a Array [0], Array [1], array [2]

La única forma de cambiar un valor es seguir las flechas. Si una flecha se pierde / cambia en la forma en que el valor es inalcanzable.

Petar Drianov
fuente
-1

Las variables de referencia llevan la dirección de un lugar a otro, por lo que cualquier actualización sobre ellas en cualquier lugar reflejará en todos los lugares ENTONCES cuál es el uso de REF. La variable de referencia (405) es válida hasta que no se asigne memoria nueva a la variable de referencia pasada en el método.

Una vez que se asigna nueva memoria (410), el cambio de valor en este objeto (408) no se reflejará en todas partes. Por esta referencia viene. Ref es referencia de referencia, por lo que cada vez que se asigna una memoria nueva se conoce porque apunta a esa ubicación, por lo tanto, el valor puede ser compartido por todos. Puedes ver la imagen para más claridad.

Ref en variable de referencia

srbh
fuente