Cuándo usar in vs ref vs out


383

Alguien me preguntó el otro día cuándo deberían usar la palabra clave parámetro en outlugar de ref. Si bien (creo) entiendo la diferencia entre las palabras clave refy out(que se ha preguntado antes ) y la mejor explicación parece ser que ref== iny out, cuáles son algunos ejemplos (hipotéticos o de código) donde siempre debería usar outy no ref.

Como refes más general, ¿por qué quieres usarlo out? ¿Es solo azúcar sintáctico?


18
Una variable que se pasa usando outno se puede leer antes de asignarla. refno tiene esta restricción Entonces ahí está eso.
Corey Ogburn

17
En resumen, refes para entrada / salida, mientras que outes un parámetro solo de salida.
Tim S.

3
¿Qué es exactamente lo que no entiendes?
tnw

44
También las outvariables tienen que ser asignadas en la función.
Corey Ogburn

Gracias Corey Pero ya no soy eso. Mi punto es que cuál es el beneficio de esto. En realidad, necesito un ejemplo que muestre un escenario en el que podamos usar el parámetro ref para lograr una funcionalidad que no se puede lograr usando el parámetro out y viceversa.
Rajbir Singh

Respuestas:


399

Debe usar a outmenos que lo necesite ref.

Hace una gran diferencia cuando los datos necesitan ser ordenados, por ejemplo, a otro proceso, que puede ser costoso. Por lo tanto, debe evitar calcular el valor inicial cuando el método no lo utiliza.

Más allá de eso, también muestra al lector de la declaración o la llamada si el valor inicial es relevante (y potencialmente preservado), o desechado.

Como una diferencia menor, no es necesario inicializar un parámetro de salida.

Ejemplo para out:

string a, b;
person.GetBothNames(out a, out b);

donde GetBothNames es un método para recuperar dos valores atómicamente, el método no cambiará el comportamiento sean cuales sean ayb. Si la llamada va a un servidor en Hawai, copiar los valores iniciales de aquí a Hawai es una pérdida de ancho de banda. Un fragmento similar usando ref:

string a = String.Empty, b = String.Empty;
person.GetBothNames(ref a, ref b);

podría confundir a los lectores, porque parece que los valores iniciales de a y b son relevantes (aunque el nombre del método indicaría que no lo son).

Ejemplo para ref:

string name = textbox.Text;
bool didModify = validator.SuggestValidName(ref name);

Aquí el valor inicial es relevante para el método.


55
"Ese no es realmente el caso". - ¿Puedes explicar mejor a qué te refieres?
Peter

3
No desea utilizar reflos valores predeterminados.
C.Evenhuis

155
Para la posteridad: otra diferencia que nadie más parece haber mencionado, como se indica aquí ; para un outparámetro, se requiere que el método de llamada asigne un valor antes de que el método regrese. - No tiene que hacer nada con un parámetro de referencia.
brichins

3
@brichins Consulte la sección 'comentarios (Adiciones de la comunidad)' en el enlace mencionado por usted. Es un error que se corrige en la documentación de VS 2008.
Bharat Ram V

13
@brichins, el método llamado es necesario para asignar un valor, no el método de llamada. zverev.eugene esto es lo que se corrigió en la documentación de VS 2008.
Segfault

72

Úselo para denotar que el parámetro no se está utilizando, solo se establece. Esto ayuda a la persona que llama a comprender que siempre está inicializando el parámetro.

Además, ref y out no son solo para tipos de valor. También le permiten restablecer el objeto al que hace referencia un tipo de referencia dentro de un método.


3
+1 No sabía que también se puede usar para tipos de referencia, buena respuesta clara, gracias
Dale

@brichins: No, no puedes. outlos parámetros se tratan como no asignados al ingresar a la función. No podrá inspeccionar su valor hasta que haya asignado definitivamente algún valor; no hay forma de usar el valor que tenía el parámetro cuando se llamó a la función.
Ben Voigt

Es cierto que no puede acceder al valor antes de una asignación interna. Me refería al hecho de que el parámetro en sí mismo puede usarse más adelante en el método, no está bloqueado. Si esto debería hacerse o no es una discusión diferente (sobre el diseño); Solo quería señalar que era posible. Gracias por la aclaración.
brichins

2
@ ดาว: Se puede usar con tipos de referencia porque cuando pasa un parámetro de tipo de referencia, lo que pasa es el valor de la referencia, no el objeto en sí. Por lo tanto, todavía es paso por valor.
Tarik

38

Tiene razón en que, semánticamente, refproporciona la funcionalidad "dentro" y "fuera", mientras que outsolo proporciona la funcionalidad "fuera". Hay algunas cosas a considerar:

  1. outrequiere que el método que acepta el parámetro DEBE, en algún momento antes de regresar, asignar un valor a la variable. Encontrará este patrón en algunas de las clases de almacenamiento de datos de clave / valor Dictionary<K,V>, donde tiene funciones como TryGetValue. Esta función toma unout parámetro que contiene cuál será el valor si se recupera. No tendría sentido que la persona que llama transfiera un valor a esta función, por lo que outse utiliza para garantizar que algún valor estará en la variable después de la llamada, incluso si no son datos "reales" (en el caso de TryGetValuedonde La clave no está presente).
  2. outy los refparámetros se ordenan de manera diferente cuando se trata de código de interoperabilidad

Además, como comentario aparte, es importante tener en cuenta que si bien los tipos de referencia y los tipos de valor difieren en la naturaleza de su valor, están disponibles en ambos lados, pero eso se debe a que la variable real es solo un puntero a otra ubicación de memoria; el contenido de la variable, la ubicación de la memoria, en realidad no cambió. cada variable en su aplicación apunta a una ubicación de memoria que contiene un valor , incluso para los tipos de referencia. Simplemente sucede que, con los tipos de referencia, el valor contenido en esa ubicación de memoria es otroubicación de memoria Cuando pasa valores a una función (o realiza cualquier otra asignación de variable), el valor de esa variable se copia en la otra variable. Para los tipos de valor, eso significa que se copia todo el contenido del tipo. Para los tipos de referencia, eso significa que se copia la ubicación de la memoria. De cualquier manera, crea una copia de los datos contenidos en la variable. La única relevancia real que tiene esto tiene que ver con la semántica de asignación; cuando se asigna una variable o se pasa por valor (el valor predeterminado), cuando se realiza una nueva asignación a la variable original (o nueva), no afecta a la otra variable. En el caso de los tipos de referencia, sí, los cambios realizados en la instancia

Al pasar con la refpalabra clave se dice que tanto la variable original como el parámetro de la función en realidad apuntarán a la misma ubicación de memoria. Esto, nuevamente, afecta solo la semántica de asignación. Si se asigna un nuevo valor a una de las variables, como el otro apunta a la misma ubicación de memoria, el nuevo valor se reflejará en el otro lado.


1
Tenga en cuenta que el requisito de que el método llamado asigne un valor a un parámetro de salida es impuesto por el compilador de c # y no por el IL subyacente. Por lo tanto, una biblioteca escrita en VB.NET puede no ajustarse a esa convención.
jmoreno

Parece que la referencia es en realidad el equivalente del símbolo de desreferenciación en C ++ (*). La referencia de paso en C # debe ser equivalente a lo que C / C ++ se refiere como punteros dobles (puntero a puntero), por lo que ref debe desreferenciar el primer puntero, permitiendo que el método llamado acceda a la ubicación de la memoria del objeto real en contexto.
Venir el

De hecho, sugeriría un TryGetValueuso correcto refy no outexplícito en el caso de no encontrar la clave.
NetMage

27

Depende del contexto de compilación (ver el ejemplo a continuación).

outy refambos denotan el paso de variables por referencia, pero refrequieren que la variable se inicialice antes de pasar, lo que puede ser una diferencia importante en el contexto de Marshaling (Interop: UmanagedToManagedTransition o viceversa)

MSDN advierte :

No confunda el concepto de pasar por referencia con el concepto de tipos de referencia. Los dos conceptos no son lo mismo. Un parámetro de método puede ser modificado por ref independientemente de si es un tipo de valor o un tipo de referencia. No hay boxeo de un tipo de valor cuando se pasa por referencia.

De los documentos oficiales de MSDN:

La palabra clave out hace que los argumentos se pasen por referencia. Esto es similar a la palabra clave ref, excepto que ref requiere que la variable se inicialice antes de pasar

La palabra clave ref hace que se pase un argumento por referencia, no por valor. El efecto de pasar por referencia es que cualquier cambio en el parámetro en el método se refleja en la variable de argumento subyacente en el método de llamada. El valor de un parámetro de referencia es siempre el mismo que el valor de la variable de argumento subyacente.

Podemos verificar que la salida y la referencia son las mismas cuando se asigna el argumento:

Ejemplo de CIL :

Considere el siguiente ejemplo

static class outRefTest{
    public static int myfunc(int x){x=0; return x; }
    public static void myfuncOut(out int x){x=0;}
    public static void myfuncRef(ref int x){x=0;}
    public static void myfuncRefEmpty(ref int x){}
    // Define other methods and classes here
}

en CIL, las instrucciones de myfuncOuty myfuncRefson idénticas a las esperadas.

outRefTest.myfunc:
IL_0000:  nop         
IL_0001:  ldc.i4.0    
IL_0002:  starg.s     00 
IL_0004:  ldarg.0     
IL_0005:  stloc.0     
IL_0006:  br.s        IL_0008
IL_0008:  ldloc.0     
IL_0009:  ret         

outRefTest.myfuncOut:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldc.i4.0    
IL_0003:  stind.i4    
IL_0004:  ret         

outRefTest.myfuncRef:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldc.i4.0    
IL_0003:  stind.i4    
IL_0004:  ret         

outRefTest.myfuncRefEmpty:
IL_0000:  nop         
IL_0001:  ret         

nop : sin operación, ldloc : carga local, stloc : pila local, ldarg : argumento de carga, bs.s : rama a destino ...

(Ver: Lista de instrucciones CIL )


23

A continuación hay algunas notas que extraje de este artículo de codeproject en C # Out Vs Ref

  1. Debe usarse solo cuando esperamos múltiples salidas de una función o un método. Una reflexión sobre las estructuras también puede ser una buena opción para lo mismo.
  2. REF y OUT son palabras clave que dictan cómo se pasan los datos de la persona que llama a la persona que llama y viceversa.
  3. En REF los datos pasan en dos direcciones. De la persona que llama a la persona que llama y viceversa.
  4. Los datos de entrada solo pasan de una persona a otra persona. En este caso, si la persona que llama intentó enviar datos a la persona que llama, será ignorada / rechazada.

Si eres una persona visual, mira este video de yourtube que demuestra la diferencia prácticamente https://www.youtube.com/watch?v=lYdcY5zulXA

La imagen de abajo muestra las diferencias más visualmente

C # Out Vs Ref


1
one-way, los two-waytérminos pueden ser mal utilizados aquí. En realidad, ambos son bidireccionales, sin embargo, sus comportamientos conceptuales difieren en las referencias y valores de los
parámetros

17

Debe usarlo refsi planea leer y escribir en el parámetro. Debe usarlo outsi solo planea escribir. En efecto, outes para cuando necesitaría más de un valor de retorno, o cuando no desea utilizar el mecanismo de retorno normal para la salida (pero esto debería ser raro).

Existen mecanismos de lenguaje que ayudan a estos casos de uso. Reflos parámetros deben haberse inicializado antes de pasar a un método (haciendo hincapié en el hecho de que son de lectura-escritura), y los outparámetros no pueden leerse antes de que se les asigne un valor, y se garantiza que se han escrito al final de el método (enfatizando el hecho de que son solo de escritura). Contravenir estos principios da como resultado un error en tiempo de compilación.

int x;
Foo(ref x); // error: x is uninitialized

void Bar(out int x) {}  // error: x was not written to

Por ejemplo, int.TryParsedevuelve boolay acepta un out intparámetro:

int value;
if (int.TryParse(numericString, out value))
{
    /* numericString was parsed into value, now do stuff */
}
else
{
    /* numericString couldn't be parsed */
}

Este es un claro ejemplo de una situación en la que necesita generar dos valores: el resultado numérico y si la conversión fue exitosa o no. Los autores del CLR decidieron optar por outaquí ya que no les importa lo que intpodría haber sido antes.

Para ref, puedes mirar Interlocked.Increment:

int x = 4;
Interlocked.Increment(ref x);

Interlocked.Incrementincrementa atómicamente el valor de x. Como necesita leer xpara incrementarlo, esta es una situación en la que refes más apropiado. Te preocupas totalmente por lo que xera antes de que fuera pasado Increment.

En la próxima versión de C #, incluso será posible declarar variables en los outparámetros, agregando aún más énfasis en su naturaleza de solo salida:

if (int.TryParse(numericString, out int value))
{
    // 'value' exists and was declared in the `if` statement
}
else
{
    // conversion didn't work, 'value' doesn't exist here
}

Gracias zneak por tu respuesta. Pero, ¿puede explicarme por qué no podría usarlo para leer y escribir un parámetro?
Rajbir Singh

@RajbirSingh, porque los outparámetros no necesariamente se han inicializado, por lo que el compilador no le permitirá leer un outparámetro hasta que haya escrito algo en él.
zneak

Zneak, estuve de acuerdo contigo. Pero en el ejemplo a continuación se puede usar un parámetro de salida como lectura y escritura: string name = "myName"; Private Outid OutMethod (out string nameOut) {if (nameOut == "myName") {nameOut = "Rajbir Singh in out method"; }}
Rajbir Singh

1
@RajbirSingh, su ejemplo no se compila. No puede leer nameOutsu ifdeclaración porque no se le asignó nada antes.
zneak

Gracias @zneak. Tienes toda la razón. No se compila. Muchas gracias por mi ayuda y ahora tiene sentido para mí :)
Rajbir Singh

7

outes una versión más restrictiva de ref.

En el cuerpo de un método, debe asignar a todos los outparámetros antes de abandonar el método. También outse ignoran los valores asignados a un parámetro, mientras que se refrequiere que se asignen.

Entonces outte permite hacer:

int a, b, c = foo(out a, out b);

donde refrequeriría que se asignen ayb.


En todo caso, outes la versión menos restringida. reftiene "Condición previa: variable se asignaron claramente, posterior: variable se asignaron claramente", mientras que outsólo ha `Condición posterior:. variable es definitivamente asignado" (Y como era de esperar, se requiere más de una implementación de la función con un menor número de condiciones previas)
Ben Voigt

@BenVoigt: Supongo que eso depende de qué dirección estés mirando :) Creo que quise decir restricción en términos de flexibilidad de codificación (?).
leppie

7

Como suena:

a cabo = Sólo initialize / llenar un parámetro (el parámetro debe estar vacío) devuélvalo a cabo llanura

ref = referencia, parámetros estándar (tal vez con el valor), pero la función puede modifiy ella.


La variable de parámetro out puede obtener un valor, antes de pasarlo a un método.
Bence Végert

6

Puede usar la outpalabra clave contextual en dos contextos (cada uno es un enlace a información detallada), como un modificador de parámetros o en declaraciones de parámetros de tipo genérico en interfaces y delegados. Este tema trata sobre el modificador de parámetros, pero puede ver este otro tema para obtener información sobre las declaraciones de parámetros de tipo genérico.

La outpalabra clave hace que los argumentos se pasen por referencia. Esto es como la refpalabra clave, excepto que refrequiere que la variable se inicialice antes de pasarla. Para usar un outparámetro, tanto la definición del método como el método de llamada deben usar explícitamente la outpalabra clave. Por ejemplo: C #

class OutExample
{
    static void Method(out int i)
    {
        i = 44;
    }
    static void Main()
    {
        int value;
        Method(out value);
        // value is now 44
    }
}

Aunque las variables pasaron como out argumentos no tienen que inicializarse antes de pasarlas, se requiere que el método llamado asigne un valor antes de que el método regrese.

Aunque las palabras clave refy outprovocan un comportamiento de tiempo de ejecución diferente, no se consideran parte de la firma del método en tiempo de compilación. Por lo tanto, los métodos no se pueden sobrecargar si la única diferencia es que un método toma un refargumento y el otro toma un outargumento. El siguiente código, por ejemplo, no se compilará: C #

class CS0663_Example
{
    // Compiler error CS0663: "Cannot define overloaded 
    // methods that differ only on ref and out".
    public void SampleMethod(out int i) { }
    public void SampleMethod(ref int i) { }
}

Sin embargo, se puede realizar una sobrecarga si un método toma un argumento refo outy el otro no usa ninguno de estos, de la siguiente manera: C #

class OutOverloadExample
{
    public void SampleMethod(int i) { }
    public void SampleMethod(out int i) { i = 5; }
}

Las propiedades no son variables y, por lo tanto, no se pueden pasar como outparámetros.

Para obtener información sobre cómo pasar matrices, consulte Pasar matrices usando ref yout (Guía de programación de C #).

No puede usar las palabras clave refy outpara los siguientes tipos de métodos:

Async methods, which you define by using the async modifier.

Iterator methods, which include a yield return or yield break statement.

Ejemplo

Declarar un outmétodo es útil cuando desea que un método devuelva varios valores. El siguiente ejemplo se usa outpara devolver tres variables con una sola llamada al método. Tenga en cuenta que el tercer argumento se asigna a nulo. Esto permite que los métodos devuelvan valores opcionalmente. C#

class OutReturnExample
{
    static void Method(out int i, out string s1, out string s2)
    {
        i = 44;
        s1 = "I've been returned";
        s2 = null;
    }
    static void Main()
    {
        int value;
        string str1, str2;
        Method(out value, out str1, out str2);
        // value is now 44
        // str1 is now "I've been returned"
        // str2 is (still) null;
    }
}

6

¿Cómo usar ino outo refen C #?

  • Todas las palabras clave C#tienen la misma funcionalidad pero con algunos límites .
  • in Los argumentos no pueden ser modificados por el método llamado.
  • ref Los argumentos pueden ser modificados.
  • ref debe inicializarse antes de ser utilizado por la persona que llama, puede leerse y actualizarse en el método.
  • out los argumentos deben ser modificados por la persona que llama.
  • out los argumentos deben inicializarse en el método
  • Las variables pasadas como inargumentos deben inicializarse antes de pasarlas en una llamada al método. Sin embargo, el método llamado puede no asignar un valor o modificar el argumento.

No se pueden utilizar los in, refy outpalabras clave para los siguientes tipos de métodos:

  • Métodos asíncronos , que usted define utilizando el asyncmodificador.
  • Métodos de iterador , que incluyen una declaración yield returno yield break.

5

Solo para aclarar en el comentario de OP que el uso en ref y out es una "referencia a un tipo de valor o estructura declarada fuera del método", que ya se ha establecido de forma incorrecta.

Considere el uso de ref en un StringBuilder, que es un tipo de referencia:

private void Nullify(StringBuilder sb, string message)
{
    sb.Append(message);
    sb = null;
}

// -- snip --

StringBuilder sb = new StringBuilder();
string message = "Hi Guy";
Nullify(sb, message);
System.Console.WriteLine(sb.ToString());

// Output
// Hi Guy

Según lo dispuesto a esto:

private void Nullify(ref StringBuilder sb, string message)
{
    sb.Append(message);
    sb = null;
}

// -- snip --

StringBuilder sb = new StringBuilder();
string message = "Hi Guy";
Nullify(ref sb, message);
System.Console.WriteLine(sb.ToString());

// Output
// NullReferenceException

4

Un argumento pasado como ref debe inicializarse antes de pasar al método, mientras que el parámetro out no necesita inicializarse antes de pasar a un método.


4

¿Por qué quieres usarlo?

¡Para que otros sepan que la variable se inicializará cuando regrese del método llamado!

Como se mencionó anteriormente: "para un parámetro de salida, el método de llamada debe asignar un valor antes de que el método regrese ".

ejemplo:

Car car;
SetUpCar(out car);
car.drive();  // You know car is initialized.

4

Básicamente ambos refy outpara pasar objeto / valor entre métodos

La palabra clave out hace que los argumentos se pasen por referencia. Esto es como la palabra clave ref, excepto que ref requiere que la variable se inicialice antes de pasarla.

out : El argumento no se inicializa y debe inicializarse en el método

ref : El argumento ya está inicializado y se puede leer y actualizar en el método.

¿Cuál es el uso de "ref" para los tipos de referencia?

Puede cambiar la referencia dada a una instancia diferente.

¿Sabías?

  1. Aunque las palabras clave ref y out provocan un comportamiento de tiempo de ejecución diferente, no se consideran parte de la firma del método en tiempo de compilación. Por lo tanto, los métodos no se pueden sobrecargar si la única diferencia es que un método toma un argumento ref y el otro saca un argumento out.

  2. No puede usar las palabras clave ref y out para los siguientes tipos de métodos:

    • Métodos asíncronos, que usted define utilizando el modificador asíncrono.
    • Métodos de iterador, que incluyen una declaración de rendimiento o ruptura de rendimiento.
  3. Las propiedades no son variables y, por lo tanto, no se pueden pasar como parámetros de salida.


4

Notas adicionales con respecto a C # 7:
en C # 7 no hay necesidad de predeclarar variables usando. Entonces un código como este:

public void PrintCoordinates(Point p)
{
  int x, y; // have to "predeclare"
  p.GetCoordinates(out x, out y);
  WriteLine($"({x}, {y})");
}

Se puede escribir así:

public void PrintCoordinates(Point p)
{
  p.GetCoordinates(out int x, out int y);
  WriteLine($"({x}, {y})");
}

Fuente: Novedades en C # 7.


4

Todavía siento la necesidad de un buen resumen, esto es lo que se me ocurrió.

Resumen,

Cuando estamos dentro de la función , así es como especificamos el control de acceso a datos variables ,

in = R

out = debe W antes de R

ref = R + W


Explicación,

in

La función solo puede LEER esa variable.

out

La variable no debe inicializarse primero porque, la
función DEBE ESCRIBIRLA antes de LEER .

ref

La función puede LEER / ESCRIBIR a esa variable.


¿Por qué se llama así?

Centrándose en dónde se modifican los datos,

in

Los datos solo deben establecerse antes de ingresar la función (in).

out

Los datos solo deben establecerse antes de dejar la función (fuera).

ref

Los datos deben establecerse antes de ingresar la función (in).
Los datos se pueden configurar antes de dejar la función (fuera).


quizás (in / out / ref) debería renombrarse a (r / wr / rw). o tal vez no, in / out es una metáfora más agradable.
jugar el

0

Cabe señalar que ines una palabra clave válida a partir de C # ver 7.2 :

El modificador de parámetro in está disponible en C # 7.2 y posterior. Las versiones anteriores generan el error del compilador CS8107 ("La función 'referencias de solo lectura' no está disponible en C # 7.0. Utilice la versión de idioma 7.2 o superior"). Para configurar la versión de idioma del compilador, consulte Seleccionar la versión de idioma de C #.

...

La palabra clave in hace que los argumentos se pasen por referencia. Hace que el parámetro formal sea un alias para el argumento, que debe ser una variable. En otras palabras, cualquier operación en el parámetro se realiza en el argumento. Es como las palabras clave ref o out, excepto que los argumentos no pueden modificarse mediante el método llamado. Mientras que los argumentos ref pueden ser modificados, los argumentos out deben ser modificados por el método llamado, y esas modificaciones son observables en el contexto de la llamada.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.