¿Por qué usar la palabra clave 'ref' al pasar un objeto?

290

Si paso un objeto a un método, ¿por qué debería usar la palabra clave ref? ¿No es este el comportamiento predeterminado de todos modos?

Por ejemplo:

class Program
{
    static void Main(string[] args)
    {
        TestRef t = new TestRef();
        t.Something = "Foo";

        DoSomething(t);
        Console.WriteLine(t.Something);
    }

    static public void DoSomething(TestRef t)
    {
        t.Something = "Bar";
    }
}


public class TestRef
{
    public string Something { get; set; }
}

La salida es "Bar", lo que significa que el objeto se pasó como referencia.

Ryan
fuente

Respuestas:

298

Pase un refsi desea cambiar el objeto:

TestRef t = new TestRef();
t.Something = "Foo";
DoSomething(ref t);

void DoSomething(ref TestRef t)
{
  t = new TestRef();
  t.Something = "Not just a changed t, but a completely different TestRef object";
}

Después de llamar a DoSomething, tno se refiere al original new TestRef, sino que se refiere a un objeto completamente diferente.

Esto también puede ser útil si desea cambiar el valor de un objeto inmutable, por ejemplo a string. No puede cambiar el valor de una stringvez que se ha creado. Pero al usar a ref, podría crear una función que cambie la cadena por otra que tenga un valor diferente.

Editar: como han mencionado otras personas. No es una buena idea usarlo a refmenos que sea necesario. El uso refle da al método la libertad de cambiar el argumento de otra cosa, los llamadores del método deberán codificarse para garantizar que manejen esta posibilidad.

Además, cuando el tipo de parámetro es un objeto, las variables del objeto siempre actúan como referencias al objeto. Esto significa que cuando refse usa la palabra clave, tiene una referencia a una referencia. Esto le permite hacer las cosas como se describe en el ejemplo anterior. Pero, cuando el tipo de parámetro es un valor primitivo (p int. Ej. ), Si este parámetro se asigna dentro del método, el valor del argumento que se pasó se cambiará después de que el método devuelva:

int x = 1;
Change(ref x);
Debug.Assert(x == 5);
WillNotChange(x);
Debug.Assert(x == 5); // Note: x doesn't become 10

void Change(ref int x)
{
  x = 5;
}

void WillNotChange(int x)
{
  x = 10;
}
Scott Langham
fuente
88

Debe distinguir entre "pasar una referencia por valor" y "pasar un parámetro / argumento por referencia".

He escrito un artículo razonablemente largo sobre el tema para evitar tener que escribir con cuidado cada vez que aparece en los grupos de noticias :)

Jon Skeet
fuente
1
Bueno, encontré el problema al actualizar VB6 a .Net C # code. Hay firmas de función / método que toman parámetros ref, out y plain. Entonces, ¿cómo podemos distinguir mejor la diferencia entre un parámetro simple y una referencia?
bonCodigo
2
@bonCodigo: No estoy seguro de qué quiere decir con "distinguir mejor": es parte de la firma, y ​​también debe especificar refen el sitio de la llamada ... ¿en qué otro lugar desea que se distinga? La semántica también es razonablemente clara, pero debe expresarse con cuidado (en lugar de "los objetos se pasan por referencia", que es la simplificación excesiva habitual).
Jon Skeet
no sé por qué Visual Studio todavía no muestra explícitamente lo que se pasa
MonsterMMORPG 05 de
3
@MonsterMMORPG: No sé a qué te refieres con eso, me temo.
Jon Skeet
56

En .NET cuando pasa cualquier parámetro a un método, se crea una copia. En los tipos de valor significa que cualquier modificación que realice en el valor está dentro del alcance del método y se pierde al salir del método.

Al pasar un Tipo de referencia, también se realiza una copia, pero es una copia de una referencia, es decir, ahora tiene DOS referencias en memoria para el mismo objeto. Entonces, si usa la referencia para modificar el objeto, se modifica. Pero si modifica la referencia en sí misma (debemos recordar que es una copia), cualquier cambio también se pierde al salir del método.

Como la gente ha dicho antes, una asignación es una modificación de la referencia, por lo tanto se pierde:

public void Method1(object obj) {   
 obj = new Object(); 
}

public void Method2(object obj) {  
 obj = _privateObject; 
}

Los métodos anteriores no modifican el objeto original.

Una pequeña modificación de tu ejemplo

 using System;

    class Program
        {
            static void Main(string[] args)
            {
                TestRef t = new TestRef();
                t.Something = "Foo";

                DoSomething(t);
                Console.WriteLine(t.Something);

            }

            static public void DoSomething(TestRef t)
            {
                t = new TestRef();
                t.Something = "Bar";
            }
        }



    public class TestRef
    {
    private string s;
        public string Something 
        { 
            get {return s;} 
            set { s = value; }
        }
    }
Ricardo Amores
fuente
66
Me gusta esta respuesta mejor que la respuesta aceptada. Explica más claramente lo que sucede al pasar una variable de tipo de referencia con la palabra clave ref. ¡Gracias!
Stefan
17

Como TestRef es una clase (que son objetos de referencia), puede cambiar el contenido dentro de t sin pasarlo como referencia. Sin embargo, si pasa t como referencia, TestRef puede cambiar a qué se refiere la t original. es decir, que apunte a un objeto diferente.

Ferruccio
fuente
16

Con refusted puede escribir:

static public void DoSomething(ref TestRef t)
{
    t = new TestRef();
}

Y t cambiará después de que el método se haya completado.

Rinat Abdullin
fuente
8

Piense en las variables (p foo. Ej. ) De los tipos de referencia (p List<T>. Ej. ) Como identificadores de objetos de la forma "Objeto # 24601". Suponga que la declaración foo = new List<int> {1,5,7,9};causa fooque contenga el "Objeto # 24601" (una lista con cuatro elementos). Luego, la llamada foo.Lengthpreguntará al Objeto # 24601 por su longitud, y responderá 4, por lo que foo.Lengthserá igual a 4.

Si foose pasa a un método sin usar ref, ese método podría realizar cambios en el Objeto # 24601. Como consecuencia de tales cambios, es foo.Lengthposible que ya no sea igual a 4. Sin embargo, el método en sí mismo no podrá cambiar foo, lo que continuará manteniendo el "Objeto # 24601".

Pasar foocomo refparámetro permitirá que el método llamado realice cambios no solo en el Objeto # 24601, sino también en foosí mismo. El método podría crear un nuevo Objeto # 8675309 y almacenar una referencia a eso en foo. Si lo hace, fooya no tendrá "Objeto # 24601", sino "Objeto # 8675309".

En la práctica, las variables de tipo de referencia no contienen cadenas de la forma "Objeto # 8675309"; ni siquiera tienen nada que pueda convertirse significativamente en un número. Aunque cada variable de tipo de referencia contendrá algún patrón de bits, no existe una relación fija entre los patrones de bits almacenados en dichas variables y los objetos que identifican. No hay forma de que el código pueda extraer información de un objeto o una referencia a él, y luego determinar si otra referencia identificó el mismo objeto, a menos que el código contenga o conozca una referencia que identifique el objeto original.

Super gato
fuente
5

Esto es como pasar un puntero a un puntero en C. En .NET esto le permitirá cambiar a qué se refiere la T original, personalmente , aunque creo que si está haciendo eso en .NET, ¡probablemente tenga un problema de diseño!

pezi_pink_squirrel
fuente
3

Al usar la refpalabra clave con tipos de referencia, efectivamente está pasando una referencia a la referencia. En muchos sentidos, es lo mismo que usar la outpalabra clave, pero con la pequeña diferencia de que no hay garantía de que el método realmente asigne algo al refparámetro 'ed.

Isak Savo
fuente
3

ref imita (o se comporta) como un área global solo para dos ámbitos:

  • Llamador
  • Callee
guneysus
fuente
1

Sin embargo, si está pasando un valor, las cosas son diferentes. Puede forzar que se pase un valor por referencia. Esto le permite pasar un número entero a un método, por ejemplo, y hacer que el método modifique el número entero en su nombre.

Andrés
fuente
44
Ya sea que pase una referencia o un valor de tipo de valor, el comportamiento predeterminado es pasar por valor. Solo necesita comprender que con los tipos de referencia, el valor que está pasando es una referencia. Esto no es lo mismo que pasar por referencia.
Jon Skeet
1

Ref denota si la función puede poner sus manos en el objeto en sí, o solo en su valor.

Pasar por referencia no está vinculado a un idioma; Es una estrategia de enlace de parámetros junto a pasar por valor, pasar por nombre, pasar por necesidad, etc.

Una nota al margen: el nombre de la clase TestRefes una elección horriblemente mala en este contexto;).

xtofl
fuente