generate - ref vs out parameter c#
Cuándo usar en vs ref vs fuera (15)
¿Por qué alguna vez quieres usar?
¡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, se requiere que el método de llamada asigne un valor antes de que el método regrese ".
ejemplo:
Car car;
SetUpCar(out car);
car.drive(); // You know car is initialized.
Alguien me preguntó el otro día cuándo deberían usar la palabra clave del parámetro out
lugar de ref
. Mientras que (creo) entiendo la diferencia entre las palabras clave ref
y out
(que se ha preguntado anteriormente ) y la mejor explicación parece ser que ref
== in
y out
, ¿cuáles son algunos ejemplos (hipotéticos o de código) donde siempre debería? out
y no ref
.
Ya que ref
es más general, ¿por qué quieres usarlo? ¿Es solo azúcar sintáctica?
¿Cómo usar in
o out
o ref
en C #?
- Todas las palabras clave en
C#
tienen la misma funcionalidad pero con algunos límites . -
in
argumentos no pueden ser modificados por el método llamado. -
ref
argumentos deref
pueden ser modificados. -
ref
debe inicializarse antes de que la persona que llama la use. Puede leerse y actualizarse en el método. -
out
argumentos deout
deben ser modificados por la persona que llama. -
out
argumentos deben ser inicializados en el método. - Las variables pasadas como
in
argumentos deben inicializarse antes de pasar en una llamada de método. Sin embargo, el método llamado no puede asignar un valor o modificar el argumento.
No puede usar las palabras clave de out
, ref
y out
para los siguientes tipos de métodos:
- Métodos
async
, que define utilizando el modificadorasync
. - Métodos de iterador , que incluyen una
yield return
yield break
oyield break
.
A continuación se muestran algunas notas que saqué de este artículo de código de proyecto en C # Out Vs Ref
- Se debe usar solo cuando estamos esperando varias salidas de una función o un método. Un pensamiento sobre estructuras puede ser también una buena opción para el mismo.
- 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.
- En REF los datos pasan de dos maneras. De la persona que llama a la persona que llama y viceversa.
- Los datos de entrada solo pasan de una manera de la persona a la que se llama. En este caso, si la persona que llama trató de enviar datos a la persona que llama, se pasará por alto / rechazará.
Si usted es una persona visual, vea este video de su tubo que demuestra la diferencia prácticamente https://www.youtube.com/watch?v=lYdcY5zulXA
La imagen de abajo muestra las diferencias más visualmente.
Básicamente, tanto ref
como out
para 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?
Aunque las palabras clave ref y out causan un comportamiento diferente en el tiempo de ejecución, no se consideran parte de la firma del método en el momento de la 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 elimina un argumento.
No puede usar las palabras clave ref y out para los siguientes tipos de métodos:
- Métodos asíncronos, que define utilizando el modificador asíncrono.
- Métodos de iterador, que incluyen una declaración de rendimiento o interrupción del rendimiento.
Las propiedades no son variables y, por lo tanto, no se pueden pasar como parámetros.
Debe usar ref
si planea leer y escribir en el parámetro. Necesitas usarlo si solo planeas escribir. En efecto, out
es para cuando necesite más de un valor de retorno, o cuando no quiera usar el mecanismo de retorno normal para la salida (pero esto debería ser raro).
Existen mecánicas del lenguaje que ayudan en estos casos de uso. Ref
parámetros de Ref
deben haberse inicializado antes de pasarlos a un método (poniendo énfasis en el hecho de que son de lectura y escritura), y out
parámetros de out
no se pueden leer antes de que se les asigne un valor, y se garantiza que se escribieron en el Fin del método (poniendo énfasis en el hecho de que son de escritura solamente). 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.TryParse
devuelve un bool
y acepta un parámetro out int
:
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 de la CLR decidieron optar por out
aquí ya que no les importa lo que podría haber sido antes el int
.
Para la ref
, puede mirar en Interlocked.Increment
. Interlocked.Increment
:
int x = 4;
Interlocked.Increment(ref x);
Interlocked.Increment
incrementa atómicamente el valor de x
. Ya que necesita leer x
para incrementarlo, esta es una situación donde ref
es más apropiado. Te importa totalmente lo que era x
antes de que se pasara a Increment
.
En la próxima versión de C #, incluso será posible declarar variables en out
parámetros de out
, 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
}
Deberías agotarlo out
menos que necesites ref
.
Hace una gran diferencia cuando los datos deben ser agrupados, por ejemplo, a otro proceso, que puede ser costoso. Por lo tanto, debes 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 conservado) o desechado.
Como una diferencia menor, un parámetro de salida no necesita ser inicializado.
Ejemplo para out
:
string a, b;
person.GetBothNames(out a, out b);
donde GetBothNames es un método para recuperar dos valores de forma atómica, el método no cambiará el comportamiento, sea lo que sean a y b. 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 ayb 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.
Depende del contexto de compilación (Ver Ejemplo a continuación).
out
y ref
ambas indican la variable pasando por referencia, pero ref
requiere que la variable se inicialice antes de pasar, lo que puede ser una diferencia importante en el contexto de Marshaling (Interop: UmanagedToManagedTransition o viceversa)
Do not confuse the concept of passing by reference with the concept of reference types. The two concepts are not the same. A method parameter can be modified by ref regardless of whether it is a value type or a reference type. There is no boxing of a value type when it is passed by reference.
De los documentos oficiales de MSDN:
-
out
:
The out keyword causes arguments to be passed by reference. This is similar to the ref keyword, except that ref requires that the variable be initialized before being passed
-
ref
:
The ref keyword causes an argument to be passed by reference, not by value. The effect of passing by reference is that any change to the parameter in the method is reflected in the underlying argument variable in the calling method. The value of a reference parameter is always the same as the value of the underlying argument variable.
Podemos verificar que el out y el ref son de hecho iguales cuando se asigna el argumento:
Ejemplo 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 myfuncOut
y myfuncRef
son 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 : no operación, ldloc : carga local, stloc : pila local, ldarg : argumento de carga, bs.s : rama a destino ...
(Ver: Lista de instrucciones CIL )
Notas adicionales con respecto a C # 7:
En C # 7 no hay necesidad de predecir variables usando. Así que 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})");
}
Puede escribirse así:
public void PrintCoordinates(Point p)
{
p.GetCoordinates(out int x, out int y);
WriteLine($"({x}, {y})");
}
Fuente: Novedades en C # 7.
Puede usar la palabra clave out
contexto en dos contextos (cada uno es un enlace a información detallada), como 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 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. Para usar un parámetro de out
, tanto la definición del método como el método de llamada deben usar explícitamente la palabra clave de out
. 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 pasadas como argumentos no tienen que ser inicializadas antes de ser pasadas, el método llamado debe asignar un valor antes de que el método regrese.
Aunque las palabras clave ref
y out
causan un comportamiento diferente en el tiempo de ejecución, no se consideran parte de la firma del método en el momento de la 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 elimina un argumento. 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 ref
o out
y el otro no lo hace, como esto: 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 parámetros.
Para obtener información sobre cómo pasar matrices, consulte Pasar matrices utilizando ref
y out
(Guía de programación C #).
No puede usar las palabras clave ref
y out
para 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 método de out
es útil cuando desea que un método devuelva varios valores. El siguiente ejemplo se utiliza para devolver tres variables con una sola llamada de método. Tenga en cuenta que el tercer argumento se asigna a nulo. Esto permite que los métodos devuelvan valores opcionalmente. DO#
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;
}
}
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 en incorrecto.
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
Como se adjunta 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
Tiene razón en que, semánticamente, ref
proporciona funcionalidad tanto "in" como "out", mientras que out
solo proporciona funcionalidad "out". Hay algunas cosas a considerar:
-
out
requiere 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 clave / valor comoDictionary<K,V>
, donde tiene funciones comoTryGetValue
. Esta función toma un parámetro deout
que contiene el valor que tendrá si se recupera. No tendría sentido que la persona que llama pase un valor a esta función, por lo queout
se usa para garantizar que algún valor estará en la variable después de la llamada, incluso si no son datos "reales" (en el caso deTryGetValue
donde la clave no está presente). -
out
parámetros deout
yref
se calculan de manera diferente al tratar con el código de interoperabilidad
Además, como punto de partida, es importante tener en cuenta que si bien los tipos de referencia y los tipos de valor difieren en la naturaleza de su valor, cada variable en su aplicación apunta a una ubicación de la 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 la memoria es otra ubicación de la 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 la ubicación de la memoria se copia. De cualquier manera, crea una copia de los datos contenidos en la variable. La única relevancia real que esto tiene se relaciona con la semántica de la tarea; 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 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ó.
Al pasar con la palabra clave ref
dice que tanto la variable original como el parámetro de función apuntarán a la misma ubicación de memoria. Esto, de nuevo, afecta solo a la semántica de asignación. Si se asigna un nuevo valor a una de las variables, como los otros puntos apuntan a la misma ubicación de memoria, el nuevo valor se reflejará en el otro lado.
Un argumento pasado como referencia debe inicializarse antes de pasar al método, mientras que el parámetro out no necesita ser inicializado antes de pasar a un método.
Use para denotar que el parámetro no está siendo usado, solo establecido. 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.
out
es una versión más restringida de ref
.
En el cuerpo del método, debe asignar a todos out
parámetros antes de abandonar el método. También se ignoran los valores asignados a un parámetro de out
, mientras que ref
requiere que se asignen.
Así que out
te permite hacer:
int a, b, c = foo(out a, out b);
donde ref
requeriría a y b para ser asignado.
Como suena:
out = solo inicializar / rellenar un parámetro (el parámetro debe estar vacío) devolverlo sin formato
ref = referencia, parámetro estándar (quizás con valor), pero la función puede modificarlo.