2009-12-30 30 views
12

Entiendo (o al menos creo que lo hago) lo que significa pasar una instancia de una clase a un método por ref versus no pasar por ref. ¿Cuándo o en qué circunstancias se debe pasar una instancia de clase por ref? ¿Existe una mejor práctica cuando se usa la palabra clave ref para instancias de clase?C# ref uso de la palabra clave

+0

Esto sería útil si alguna vez tuvo que revertir una lista vinculada fuera de una entrevista en microsoft. void Reverse (ref Node Root) y cuando configura Root, la Raíz de la persona que llama se actualizará. –

+0

Como antiguo programador de lenguaje ensamblador, veo su punto. – CiaoRoma

+1

Las instancias de clase siempre se pasan por referencia (mientras que la referencia se pasa por valor) La única razón para usar ref es manipular la referencia a la instancia de clase dentro de un método. –

Respuesta

18

La explicación más clara que he funcionado a través de los parámetros de salida y ref ... es Jon Skeet.

Parameter Passing in C#

Él no entra en las "mejores prácticas", pero si usted entiende los ejemplos que le ha dado, usted sabrá cuando es necesario utilizarlas.

+0

Todas las respuestas son muy útiles; sin embargo, estoy aceptando la respuesta de womp ya que aborda específicamente mi pregunta sobre "mejores prácticas". ¡Muchas gracias a todos! – CiaoRoma

+0

No tengo ninguna duda de que Jon Skeet explica esto perfectamente en su sitio, pero esta respuesta no es más que un enlace y, como tal, se volverá inútil cuando el sitio detrás del enlace disminuya. – magnattic

2

La palabra clave ref le permite pasar un argumento por referencia. Para los tipos de referencia, esto significa que se pasa la referencia real a un objeto (en lugar de una copia de esa referencia). Para los tipos de valor, esto significa que se pasa una referencia a la variable que contiene el valor de ese tipo.

Esto se utiliza para los métodos que necesitan devolver más de un resultado pero no devuelven un tipo complejo para encapsular esos resultados. Le permite pasar una referencia a un objeto en el método para que el método pueda modificar ese objeto.

Lo importante que debe recordar es que los tipos de referencia normalmente no se pasan por referencia, sino que se pasa una copia de una referencia. Esto significa que no está trabajando con la referencia real que se le pasó. Cuando usa ref en una instancia de clase, está pasando la referencia real, por lo que todas las modificaciones a la misma (como establecerlo en null) se aplicarán a la referencia original.

+0

Para devolver más de un valor, debe usar 'out'. –

+0

Que yo entiendo, pero ¿cuándo o bajo qué circunstancias necesitaría o querría usar la palabra clave ref para instancias de clase? Eso es lo que realmente me tiene perplejo. – CiaoRoma

+0

Acabo de leer la respuesta de Shoham. Gracias por sus comentarios. – CiaoRoma

6

En pocas palabras, pasaría un valor como un parámetro ref si desea que la función que está llamando pueda alterar el valor de esa variable.

Esto no es lo mismo que pasar una referencia tipo como parámetro de una función. En esos casos, aún está pasando por valor, pero el valor es como referencia. En el caso de pasar por ref, se envía una referencia real a la variable; esencialmente, usted y la función a la que llama "comparten" la misma variable.

considerar lo siguiente:

public void Foo(ref int bar) 
{ 
    bar = 5; 
} 

... 

int baz = 2; 

Foo(ref baz); 

En este caso, la variable baz tiene un valor de 5, ya que se pasa por referencia. La semántica es completamente clara para los tipos de valores, pero no tan claros para los tipos de referencia.

public class MyClass 
{ 
    public int PropName { get; set; } 
} 

public void Foo(MyClass bar) 
{ 
    bar.PropName = 5; 
} 

... 

MyClass baz = new MyClass(); 

baz.PropName = 2; 

Foo(baz); 

Como se esperaba, baz.PropName habrá 5, ya que MyClass es un tipo de referencia. Pero vamos a hacer esto:

public void Foo(MyClass bar) 
{ 
    bar = new MyClass(); 

    bar.PropName = 5; 
} 

Con el mismo código de llamada, baz.PropName permanecerán 2. Esto se debe a que a pesar de que MyClass es un tipo de referencia, Foo tiene su propia variable para bar; bar y baz acaba de comenzar con el mismo valor, pero una vez que Foo asigna un nuevo valor, son solo dos variables diferentes. Sin embargo, si hacemos esto:

public void Foo(ref MyClass bar) 
{ 
    bar = new MyClass(); 

    bar.PropName = 5; 
} 

... 

MyClass baz = new MyClass(); 

baz.PropName = 2; 

Foo(ref baz); 

vamos a terminar con PropName siendo 5, desde que pasamos baz por referencia, por lo que las dos funciones "compartir" la misma variable.

+0

+1 ¡Excelentes y claros ejemplos! – Nate

0

Al pasar tipos de referencia (tipos no válidos) a un método, solo la referencia se pasa en ambos casos.Sin embargo, cuando se utiliza la palabra clave ref, el método puede ser llamado cambio la referencia.

Por ejemplo:

public void MyMethod(ref MyClass obj) 
{ 
    obj = new MyClass(); 
} 

en otra parte:

MyClass x = y; // y is an instance of MyClass 

// x points to y 

MyMethod(ref x); 

// x points to a new instance of MyClass 

al llamar MyMethod (ref x), x apuntará al objeto recién creado después de la llamada al método. x ya no apunta al objeto original.

+0

Este ejemplo necesita algo de trabajo; en su lugar, usaría la palabra clave out. –

+1

Este ejemplo simplemente ilustra que el método llamado puede cambiar el valor. Al usar la palabra clave out, el método llamado no puede usar el parámetro que se pasó. Solo puede establecerlo. –

11

Cuando puede reemplazar el objeto original, debe enviarlo como ref. Si solo es para salida y puede ser sin inicializar antes de llamar a la función, usará out.

+0

+1 para mencionar puede ser no inicializado y ref no puede. –

0

La mayoría de los casos de uso para pasar una variable de referencia por referencia implica la inicialización y la salida es más apropiada que la ref. Y compilan a la misma cosa (el compilador impone diferentes restricciones: las variables de referencia se inicializan antes de pasarlas y las variables de salida se inicializan en el método). Por lo tanto, el único caso en el que puedo pensar en dónde esto sería útil es en el que debe verificar una variable de referencia instanciada y es posible que deba reiniciarse en determinadas circunstancias.

Esto también podría ser necesario modificar una clase inmutable (como cadena) como señala R. Asaf

0

he encontrado que es fácil de ejecutar en problemas usando la palabra clave ref.

El método siguiente será modificar f incluso sin la palabra clave ref en la firma del método porque f es un tipo de referencia:

public void TrySet(Foo f,string s) 
{ 
    f.Bar = s; 
} 

En este segundo caso, sin embargo, la Foo original se ve afectado solamente por la primera línea de código, el resto del método de alguna manera crea y afecta solo una nueva variable local.

public void TryNew(Foo f, string s) 
{ 
    f.Bar = ""; //original f is modified 
    f = new Foo(); //new f is created 
    f.Bar = s; //new f is modified, no effect on original f 
} 

Sería bueno si el compilador le dio una advertencia en ese caso. Básicamente, lo que está haciendo es reemplazar la referencia que recibió por otra que hace referencia a un área de memoria diferente.

Es en realidad se desea reemplazar el objeto con una nueva instancia, utilizar la palabra clave ref:

public void TryNew(ref Foo f, string s)... 

Pero no están disparando en el pie?Si la persona que llama no es consciente de que se crea un nuevo objeto, el siguiente código probablemente no funciona como es debido:

Foo f = SomeClass.AFoo; 
TryNew(ref f, "some string"); //this will clear SomeClass.AFoo.Bar and then create a new distinct object 

Y si se intenta "fijar" el problema añadiendo la línea:

SomeClass.AFoo = f; 

Si el código contiene una referencia a SomeClass.AFoo en otro lugar, esa referencia se volverá inválida ...

Como regla general, probablemente deba evitar el uso de la nueva palabra clave para modificar un objeto que haya leído de otra clase o recibido como un parámetro en un método.

cuanto al uso de la palabra clave ref con los tipos de referencia, lo que puedo sugerir este enfoque:

1) No lo utilice si simplemente definiendo los valores del tipo de referencia, sino ser explícitos en sus funciones o parámetros nombres y en los comentarios:

public void SetFoo(Foo fooToSet, string s) 
{ 
    fooToSet.Bar = s; 
} 

2) Cuando hay una razón legítima para reemplazar el parámetro de entrada con una nueva, distinta ejemplo, utilizar una función con un valor de retorno en su lugar:

public Foo TryNew(string s) 
{ 
    Foo f = new Foo(); 
    f.Bar = s; 
    return f; 
} 

Pero el uso de esta función todavía puede tener consecuencias no deseadas con el escenario SomeClass.AFoo:

SomeClass.AFoo = TryNew("some string");//stores a different object in SomeClass.AFoo 

3) En algunos casos, como el ejemplo de cadena here es práctico el uso de params árbitro intercambio, pero al igual que en el caso 2 asegúrese de que el intercambio de las direcciones del objeto no afecte al resto de su código.

Dado que administra la asignación de memoria para usted, C# hace que sea muy fácil olvidar todo acerca de la administración de memoria, pero realmente ayuda a comprender cómo funcionan los punteros y las referencias. De lo contrario, puede introducir errores sutiles que son difíciles de encontrar.

Por último, este suele ser el caso en el que uno quisiera usar una función como memcpy, pero no hay tal cosa en C# que yo sepa.