2008-10-09 14 views
204

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 qué usar la palabra clave 'ref' al pasar un objeto?

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", que significa que el objeto se pasa como una referencia.

Respuesta

209

pasar una ref si desea cambiar lo que el objeto es:

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 HacerAlgo, t no se refiere a la 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, p. Ej. a string. No puede cambiar el valor de string una vez que se haya creado. Pero al usar un ref, puede crear una función que cambie la cadena por otra que tenga un valor diferente.

Editar: Como otras personas han mencionado. No es una buena idea usar ref a menos que sea necesario. El uso de ref le da libertad al método para cambiar el argumento por otra cosa, los llamadores del método necesitarán ser codificados para asegurar que manejen esta posibilidad.

Además, cuando el tipo de parámetro es un objeto, las variables de objeto siempre actúan como referencias al objeto. Esto significa que cuando se usa la palabra clave ref, 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 (por ejemplo int), entonces si este parámetro se asigna a dentro del método, el valor del argumento de que fue aprobada en será cambiado después devuelve el método:

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

Si está pasando un valor, sin embargo, 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.

+4

Si está pasando un valor de referencia o 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. –

13

Con ref puede escribir:

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

y T se cambiará después de que el método ha completado.

16

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

3

Al utilizar la palabra clave ref con tipos de referencia, está pasando efectivamente una referencia a la referencia. En muchos sentidos, es lo mismo que usar la palabra clave out, pero con la mínima diferencia de que no hay garantía de que el método realmente asigne algo al parámetro ref 'ed.

3

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

1

Ref indica si la función puede tener en sus manos 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 al lado de pasar por valor, pasar por nombre, pasar por necesidad, etc.

Nota al margen: el nombre de clase TestRef es una mala elección en este contexto;).

69

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

He escrito un reasonably long article on the subject para evitar tener que escribir cuidadosamente cada vez que esto sale en grupos de noticias :)

+1

Bueno, me encontré con el problema al actualizar VB6 en el código .Net C#. Hay firmas de función/método que toman ref, out y plain parameters. Entonces, ¿cómo podemos distinguir mejor la diferencia entre un param simple y un ref? – bonCodigo

+1

@bonCodigo: No estoy seguro de lo que quiere decir con "distinguir mejor": es parte de la firma, y ​​tiene que especificar 'ref' en el sitio de la llamada también ... ¿en qué otro lugar le gustaría que se distinguiera? 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 sobre simplificación común). –

+0

no sé por qué el estudio visual aún no muestra explícitamente lo que pasó – MonsterMMORPG

21

En .NET cuando se pasa ningún 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 se encuentra en el 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í, debemos recordar que es una copia, entonces 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 modifica el objeto original.

Una pequeña modificación de su 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; } 
     } 
    } 
+1

Me gusta esta respuesta mejor que la respuesta aceptada. Explica más claramente lo que está sucediendo al pasar una variable de tipo de referencia con la palabra clave ref. ¡Gracias! – Stefan

3

ref imita (o se comporta) como área global sólo para dos ámbitos:

  • de llamadas
  • destinatario de la llamada.
6

Think de variables (por ejemplo foo) de los tipos de referencia (por ejemplo List<T>) como la celebración de identificadores de objeto de la forma "Object # 24601". Supongamos que la instrucción foo = new List<int> {1,5,7,9}; provoca que foo contenga el "Objeto n. ° 24601" (una lista con cuatro elementos). Entonces llamando foo.Length le preguntará objeto # 24601 de su longitud, y responderá 4, por lo foo.Length será igual a 4.

Si foo se pasa a un método sin utilizar ref, ese método podría hacer cambios a objeto # 24601. Como consecuencia de dichos cambios, foo.Length podría no ser igual a 4. Sin embargo, el método en sí no podrá cambiar foo, que continuará conteniendo el "Objeto # 24601".

Pasando foo como un parámetro ref permitirá que el método llamado haga cambios no solo al Objeto # 24601, sino también al foo. El método podría crear un nuevo Objeto n. ° 8675309 y almacenar una referencia en foo. Si lo hace, foo ya no tendrá "Objeto n. ° 24601", sino "Objeto n. ° 8675309".

En la práctica, las variables de tipo de referencia no tienen cadenas de la forma "Objeto n. ° 8675309"; ni siquiera tienen nada que pueda convertirse de manera significativa en un número. Aunque cada variable de tipo de referencia contendrá algunos patrones 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 tenga o conozca una referencia que identificó el objeto original.

Cuestiones relacionadas