¿Cuál es la diferencia entre las palabras clave 'ref' y 'out'?

892

Estoy creando una función donde necesito pasar un objeto para que pueda ser modificado por la función. Cuál es la diferencia entre:

public void myFunction(ref MyClass someClass)

y

public void myFunction(out MyClass someClass)

¿Cuál debería usar y por qué?

TK
fuente
69
Usted: Necesito pasar un objeto para que pueda modificarse Parece MyClassque sería un classtipo, es decir, un tipo de referencia. En ese caso, el objeto que pasa puede ser modificado por myFunctionincluso sin ref/ outpalabra clave. myFunctionrecibirá una nueva referencia que apunta al mismo objeto, y puede modificar ese mismo objeto todo lo que quiera. La diferencia refque haría la palabra clave sería que myFunctionrecibiera la misma referencia al mismo objeto. Eso sería importante solo si myFunctioncambiara la referencia para apuntar a otro objeto.
Jeppe Stig Nielsen
3
Estoy desconcertado por la cantidad de respuestas confusas aquí, cuando @ AnthonyKolesov es bastante perfecto.
o0 '.
Declarar un método de salida es útil cuando desea que un método devuelva múltiples valores. Se puede asignar un argumento a nulo. Esto permite que los métodos devuelvan valores opcionalmente.
Yevgraf Andreyevich Zhivago
Aquí explicado con el ejemplo Es más comprensible :) dotnet-tricks.com/Tutorial/csharp/…
Prageeth godage
2
El comentario de @JeppeStigNielsen es, técnicamente, la (única) respuesta correcta a la pregunta real del OP. Para pasar un objeto a un método para que el método pueda modificar el objeto , simplemente pase el (referencia al) objeto al método por valor. Cambiar el objeto dentro del método mediante el argumento objeto modifica el objeto original , aunque el método contiene su propia variable separada (que hace referencia al mismo objeto).
David R Tribble

Respuestas:

1162

refle dice al compilador que el objeto se inicializa antes de ingresar a la función, mientras que outle dice al compilador que el objeto se inicializará dentro de la función.

Entonces, si bien refes bidireccional, outes exclusivo.

Runa Grimstad
fuente
270
Otra cosa interesante específica de out es que la función tiene que asignarse al parámetro out. No está permitido dejarlo sin asignar.
Daniel Earwicker
77
¿'ref' solo es aplicable al tipo de valor? Dado que el tipo de referencia siempre se pasa por ref.
defectuoso
3
Si. Tipos de valor, incluidas estructuras
Rune Grimstad
17
@faulty: No, ref no solo es aplicable a los tipos de valor. ref / out son como punteros en C / C ++, se ocupan de la ubicación de la memoria del objeto (indirectamente en C #) en lugar del objeto directo.
Thr
52
@faulty: Contraintuitivamente, los tipos de referencia siempre se pasan por valor en C #, a menos que use el especificador de referencia. Si configura myval = somenewval, el efecto solo está en el alcance de esa función. La palabra clave ref le permitiría cambiar myval para que apunte a algún nuevo valor.
JasonTrue
535

El refmodificador significa que:

  1. El valor ya está establecido y
  2. El método puede leerlo y modificarlo.

El outmodificador significa que:

  1. El valor no se establece y el método no puede leerlo hasta que se establece.
  2. El método debe establecerlo antes de regresar.
Anton Kolesov
fuente
30
Esta respuesta explica de manera más clara y concisa las restricciones que impone el compilador al usar la palabra clave out en lugar de la palabra clave ref.
Aprendiz del Dr. Wily
55
Desde MSDN: un parámetro de referencia debe inicializarse antes de su uso, mientras que un parámetro de salida no tiene que inicializarse explícitamente antes de pasarlo y se ignora cualquier valor anterior.
Shiva Kumar
1
Con out, ¿se puede leer en absoluto dentro del método, antes de que se haya establecido con ese método, si se ha inicializado antes de que se llame al método? Quiero decir, ¿puede el método llamado leer lo que el método de llamada le pasó como argumento?
Panzercrisis
3
Panzercrisis, para "out", el método llamado puede leer si ya está configurado. pero debe configurarlo de nuevo.
robert jebakumar2
146

Digamos que Dom aparece en el cubículo de Peter sobre la nota sobre los informes de TPS.

Si Dom fuera un argumento de referencia, tendría una copia impresa del memo.

Si Dom fuera una discusión, haría que Peter imprimiera una nueva copia de la nota para que se la llevara.

Michael Blackburn
fuente
54
El árbitro Dom habría escrito el informe a lápiz para que Peter pudiera modificarlo
Deebster
66
@Deebster, sabes, esa metáfora nunca te hizo nada, ¿por qué debes torturarla así? ;)
Michael Blackburn
21
entretenido pero educativo, stackoverflow necesita más publicaciones como esta
Frank Visaggio
2
En caso de que alguien encuentre esta respuesta solo medio divertida, vea la película "Office Space".
displayName
y el jefe de Dom y Peters se pararía detrás de Dom (como argumento), obligando a ambos a trabajar en imprimirlo de nuevo hasta que Peter le entregue a Domd la copia impresa
Patrick Artner
57

Voy a probar suerte con una explicación:

Creo que entendemos cómo funcionan los tipos de valor, ¿verdad? Los tipos de valor son (int, long, struct, etc.). Cuando los envía a una función sin un comando ref, COPIA los datos . Cualquier cosa que haga a esos datos en la función solo afecta a la copia, no al original. El comando ref envía los datos ACTUALES y cualquier cambio afectará los datos fuera de la función.

De acuerdo con la parte confusa, tipos de referencia:

Vamos a crear un tipo de referencia:

List<string> someobject = new List<string>()

Cuando renueva un objeto , se crean dos partes:

  1. El bloque de memoria que contiene datos para algún objeto .
  2. Una referencia (puntero) a ese bloque de datos.

Ahora, cuando envía algún objeto a un método sin referencia, COPIA el puntero de referencia , NO los datos. Entonces ahora tienes esto:

(outside method) reference1 => someobject
(inside method)  reference2 => someobject

Dos referencias apuntando al mismo objeto. Si modifica una propiedad en algún objeto utilizando referencia2, afectará los mismos datos señalados por referencia1.

 (inside method)  reference2.Add("SomeString");
 (outside method) reference1[0] == "SomeString"   //this is true

Si anula la referencia2 o la señala a datos nuevos, no afectará la referencia1 ni la referencia de datos1 a la que apunta.

(inside method) reference2 = new List<string>();
(outside method) reference1 != null; reference1[0] == "SomeString" //this is true

The references are now pointing like this:
reference2 => new List<string>()
reference1 => someobject

¿Qué sucede cuando envía un objeto por referencia a un método? La referencia real a algún objeto se envía al método. Entonces ahora solo tiene una referencia a los datos:

(outside method) reference1 => someobject;
(inside method)  reference1 => someobject;

Pero ¿qué significa esto? Actúa exactamente igual que enviar algún objeto, no por referencia, excepto por dos cosas principales:

1) Cuando anule la referencia dentro del método, anulará la que está fuera del método.

 (inside method)  reference1 = null;
 (outside method) reference1 == null;  //true

2) Ahora puede apuntar la referencia a una ubicación de datos completamente diferente y la referencia fuera de la función ahora apuntará a la nueva ubicación de datos.

 (inside method)  reference1 = new List<string>();
 (outside method) reference1.Count == 0; //this is true
James Roland
fuente
Después de todo, quiere decir que (en el caso de referencia) solo hay una referencia a los datos pero dos alias para ellos. ¿Derecha?
Sadiq
3
Votado por la explicación clara. Pero creo que esto no responde la pregunta, ya que no explica la diferencia entre refy outparámetros.
Joyce Babu
1
Asombroso. ¿Puedes explicar lo mismo que para la outpalabra clave?
Asif Mushtaq
28

ref está dentro y fuera .

Debería usar outcon preferencia donde sea suficiente para sus requisitos.

Ruben Bartelink
fuente
no del todo, ya que la respuesta aceptada se refiere si es direccional e inútil ignorando los tipos de valor si no se devuelve.
kenny
@kenny: ¿Puedes aclarar un poco por favor, es decir, qué palabras cambiarías para mantener el espíritu de la respuesta pero eliminar la imprecisión que percibes? Mi respuesta no es una suposición descabellada de un novato, pero la prisa (conciso, errores tipográficos) en su comentario parece suponer que sí. El objetivo es proporcionar una forma de pensar sobre la diferencia con el menor número de palabras.
Ruben Bartelink
(Por cierto, estoy familiarizado con los tipos de valor, tipos de referencia, pasar por referencia, pasar por valor, COM y C ++ si le resulta útil hacer referencia a esos conceptos en su aclaración)
Ruben Bartelink
1
Las referencias a objetos se pasan por valor (excepto cuando se usa la palabra clave "ref" o "out"). Piense en los objetos como números de identificación. Si una variable de clase contiene "Object # 1943" y se pasa esa variable por valor a una rutina, esa rutina puede hacer cambios al Object # 1943, pero no puede hacer que la variable apunte a otra cosa que no sea "Object # 1943". Si la variable se pasó por referencia, la rutina podría hacer que el punto variable contenga "Object # 5441".
supercat
1
@supercat: me gusta su explicación de ref vs val (y esta anaología de seguimiento). Creo que Kenny no necesita que le expliquen nada de esto, (relativamente) confuso como lo fueron sus comentarios. Sin embargo, desearía que todos pudiéramos eliminar estos malditos comentarios, ya que están confundiendo a todos. La causa raíz de todas estas tonterías parece ser que Kenny leyó mal mi respuesta y todavía tiene que señalar una sola palabra que debe agregarse / eliminarse / reemplazarse. Ninguno de los tres hemos aprendido nada de la discusión que ya no sabíamos y la otra respuesta tiene una cantidad ridícula de votos a favor.
Ruben Bartelink
18

fuera:

En C #, un método solo puede devolver un valor. Si desea devolver más de un valor, puede usar la palabra clave out. El modificador out regresa como retorno por referencia. La respuesta más simple es que la palabra clave "out" se usa para obtener el valor del método.

  1. No necesita inicializar el valor en la función de llamada.
  2. Debe asignar el valor en la función llamada, de lo contrario, el compilador informará un error.

árbitro:

En C #, cuando pasa un tipo de valor como int, float, double, etc. como argumento al parámetro del método, se pasa por valor. Por lo tanto, si modifica el valor del parámetro, no afecta el argumento en la llamada al método. Pero si marca el parámetro con la palabra clave "ref", se reflejará en la variable real.

  1. Debe inicializar la variable antes de llamar a la función.
  2. No es obligatorio asignar ningún valor al parámetro ref en el método. Si no cambia el valor, ¿cuál es la necesidad de marcarlo como "ref"?
Nazmul Hasan
fuente
"En C #, un método puede devolver solo un valor. Si desea devolver más de un valor, puede usar la palabra clave out". También podemos usar "ref" para devolver el valor. Entonces, ¿podemos usar tanto ref como out si queremos devolver múltiples valores de un método?
Ned
1
En el c # 7 puede devolver múltiples valores con ValueTuples.
Iman Bahrampour
13

Extendiendo el perro, ejemplo de gato. El segundo método con ref cambia el objeto al que hace referencia la persona que llama. Por lo tanto "Gato" !!!

    public static void Foo()
    {
        MyClass myObject = new MyClass();
        myObject.Name = "Dog";
        Bar(myObject);
        Console.WriteLine(myObject.Name); // Writes "Dog". 
        Bar(ref myObject);
        Console.WriteLine(myObject.Name); // Writes "Cat". 
    }

    public static void Bar(MyClass someObject)
    {
        MyClass myTempObject = new MyClass();
        myTempObject.Name = "Cat";
        someObject = myTempObject;
    }

    public static void Bar(ref MyClass someObject)
    {
        MyClass myTempObject = new MyClass();
        myTempObject.Name = "Cat";
        someObject = myTempObject;
    }
BBB
fuente
8

Como está pasando un tipo de referencia (una clase), no es necesario usarlo refporque, por defecto, solo se pasa una referencia al objeto real y, por lo tanto, siempre cambia el objeto detrás de la referencia.

Ejemplo:

public void Foo()
{
    MyClass myObject = new MyClass();
    myObject.Name = "Dog";
    Bar(myObject);
    Console.WriteLine(myObject.Name); // Writes "Cat".
}

public void Bar(MyClass someObject)
{
    someObject.Name = "Cat";
}

Mientras pase una clase, no tiene que usarla refsi desea cambiar el objeto dentro de su método.

Albic
fuente
55
Esto funciona solo si no se crea y devuelve ningún objeto nuevo. Cuando se crea un nuevo objeto, se perdería la referencia al objeto antiguo.
etsuba
8
Esto está mal: intente lo siguiente: agregar someObject = nullpara Barfinalizar la ejecución. Su código se ejecutará bien ya que solo se Baranuló la referencia a la instancia. Ahora cambia Bara Bar(ref MyClass someObject)y ejecutar de nuevo - obtendrá una NullReferenceExceptioncausa Foo's referencia a la instancia ha anulado también.
Keith
8

refy outcomportarse de manera similar, excepto las siguientes diferencias.

  • refLa variable debe inicializarse antes de su uso. outvariable se puede usar sin asignación
  • outEl parámetro debe ser tratado como un valor no asignado por la función que lo utiliza. Entonces, podemos usar el outparámetro inicializado en el código de llamada, pero el valor se perderá cuando se ejecute la función.
usuario de gmail
fuente
6

"Panadero"

Esto se debe a que el primero cambia su referencia de cadena para que apunte a "Baker". Es posible cambiar la referencia porque la pasó a través de la palabra clave ref (=> una referencia a una referencia a una cadena). La segunda llamada obtiene una copia de la referencia a la cadena.

La cadena parece algo especial al principio. Pero string es solo una clase de referencia y si define

string s = "Able";

¡entonces s es una referencia a una clase de cadena que contiene el texto "Capaz"! Otra asignación a la misma variable vía

s = "Baker";

¡no cambia la cadena original, sino que simplemente crea una nueva instancia y señalamos esa instancia!

Puedes probarlo con el siguiente pequeño ejemplo de código:

string s = "Able";
string s2 = s;
s = "Baker";
Console.WriteLine(s2);

¿Qué esperas? Lo que obtendrá seguirá siendo "Capaz" porque simplemente establece la referencia en s a otra instancia mientras s2 apunta a la instancia original.

EDITAR: la cadena también es inmutable, lo que significa que simplemente no hay ningún método o propiedad que modifique una instancia de cadena existente (puede intentar encontrar una en los documentos pero no encontrará ninguna :-)). ¡Todos los métodos de manipulación de cadenas devuelven una nueva instancia de cadena! (Es por eso que a menudo obtienes un mejor rendimiento cuando usas la clase StringBuilder)

mmmmmmmm
fuente
1
Exactamente. Por lo tanto, no es estrictamente cierto decir "Dado que está pasando un tipo de referencia (una clase) no hay necesidad de usar ref".
Paul Mitchell
En teoría, es correcto decirlo porque escribió "para que pueda modificarse", lo cual no es posible en las cadenas. ¡Pero debido a los objetos inmutables "ref" y "out" son muy útiles también para los tipos de referencia! (.Net contiene muchas clases inmutables!)
mmmmmmmm
Sí tienes razón. No pensé en objetos inmutables como cadenas porque la mayoría de los objetos son mutables.
Albic
1
Bueno, esta es una respuesta desconcertante para ver en LQP, sin duda; no tiene nada de malo, excepto que parece ser una respuesta larga y exhaustiva a otro comentario (ya que la pregunta original no menciona a Able y Baker en ninguna de sus revisiones), como si fuera un foro. Supongo que eso no se resolvió realmente todavía cuando.
Nathan Tuggy
6

ref significa que el valor en el parámetro ref ya está establecido, el método puede leerlo y modificarlo. Usar la palabra clave ref es lo mismo que decir que la persona que llama es responsable de inicializar el valor del parámetro.


out le dice al compilador que la inicialización del objeto es responsabilidad de la función, la función debe asignarse al parámetro out. No está permitido dejarlo sin asignar.

Farhan S.
fuente
5

Fuera: una declaración de devolución se puede usar para devolver solo un valor de una función. Sin embargo, utilizando parámetros de salida, puede devolver dos valores de una función. Los parámetros de salida son como parámetros de referencia, excepto que transfieren datos fuera del método en lugar de hacerlo al mismo.

El siguiente ejemplo lo ilustra:

using System;

namespace CalculatorApplication
{
   class NumberManipulator
   {
      public void getValue(out int x )
      {
         int temp = 5;
         x = temp;
      }

      static void Main(string[] args)
      {
         NumberManipulator n = new NumberManipulator();
         /* local variable definition */
         int a = 100;

         Console.WriteLine("Before method call, value of a : {0}", a);

         /* calling a function to get the value */
         n.getValue(out a);

         Console.WriteLine("After method call, value of a : {0}", a);
         Console.ReadLine();

      }
   }
}

ref: Un parámetro de referencia es una referencia a una ubicación de memoria de una variable. Cuando pasa parámetros por referencia, a diferencia de los parámetros de valor, no se crea una nueva ubicación de almacenamiento para estos parámetros. Los parámetros de referencia representan la misma ubicación de memoria que los parámetros reales que se proporcionan al método.

En C #, declara los parámetros de referencia utilizando la palabra clave ref. El siguiente ejemplo demuestra esto:

using System;
namespace CalculatorApplication
{
   class NumberManipulator
   {
      public void swap(ref int x, ref int y)
      {
         int temp;

         temp = x; /* save the value of x */
         x = y;   /* put y into x */
         y = temp; /* put temp into y */
       }

      static void Main(string[] args)
      {
         NumberManipulator n = new NumberManipulator();
         /* local variable definition */
         int a = 100;
         int b = 200;

         Console.WriteLine("Before swap, value of a : {0}", a);
         Console.WriteLine("Before swap, value of b : {0}", b);

         /* calling a function to swap the values */
         n.swap(ref a, ref b);

         Console.WriteLine("After swap, value of a : {0}", a);
         Console.WriteLine("After swap, value of b : {0}", b);

         Console.ReadLine();

      }
   }
}
Faisal Naseer
fuente
4

ref y out funcionan como pasar por referencias y pasar por punteros como en C ++.

Para ref, el argumento debe declararse e inicializarse.

Para salir, el argumento debe declararse pero puede o no inicializarse

        double nbr = 6; // if not initialized we get error
        double dd = doit.square(ref nbr);

        double Half_nbr ; // fine as passed by out, but inside the calling  method you initialize it
        doit.math_routines(nbr, out Half_nbr);
Rueda giratoria
fuente
1
Se puede declarar una variable de línea: out double Half_nbr.
Sebastian Hofmann
4

Tiempo de autoría:

(1) Creamos el método de llamada Main()

(2) crea un objeto List (que es un objeto de tipo de referencia) y lo almacena en la variable myList.

public sealed class Program 
{
    public static Main() 
    {
        List<int> myList = new List<int>();

Durante el tiempo de ejecución:

(3) El tiempo de ejecución asigna una memoria en la pila en el # 00, lo suficientemente ancho como para almacenar una dirección (# 00 = myList, ya que los nombres de las variables son realmente solo alias para ubicaciones de memoria)

(4) Runtime crea un objeto de lista en el montón en la ubicación de memoria #FF (todas estas direcciones son, por ejemplo, sake)

(5) Runtime almacenaría la dirección inicial #FF del objeto en # 00 (o en palabras, almacena la referencia del objeto List en el puntero myList)

Volver al tiempo de autoría:

(6) Luego pasamos el objeto List como argumento myParamListal método llamado modifyMyListy le asignamos un nuevo objeto List

List<int> myList = new List<int>();

List<int> newList = ModifyMyList(myList)

public List<int> ModifyMyList(List<int> myParamList){
     myParamList = new List<int>();
     return myParamList;
}

Durante el tiempo de ejecución:

(7) Runtime inicia la rutina de llamada para el método llamado y, como parte del mismo, verifica el tipo de parámetros.

(8) Al encontrar el tipo de referencia, asigna una memoria en la pila en el # 04 para aliasar la variable del parámetro myParamList.

(9) Luego almacena el valor #FF también en él.

(10) Runtime crea un objeto de lista en el montón en la ubicación de memoria # 004 y reemplaza #FF en # 04 con este valor (o desreferencia el objeto de lista original y señala el nuevo objeto de lista en este método)

La dirección en # 00 no se altera y conserva la referencia a #FF (o el myListpuntero original no se altera).


La palabra clave ref es una directiva del compilador para omitir la generación de código de tiempo de ejecución para (8) y (9), lo que significa que no habrá asignación de montón para los parámetros del método. Utilizará el puntero original # 00 para operar el objeto en #FF. Si el puntero original no se inicializa, el tiempo de ejecución dejará de quejarse de que no puede continuar ya que la variable no está inicializada

La palabra clave out es una directiva del compilador que es casi lo mismo que ref con una ligera modificación en (9) y (10). El compilador espera que el argumento no se inicialice y continuará con (8), (4) y (5) para crear un objeto en el montón y almacenar su dirección inicial en la variable del argumento. No se generará ningún error no inicializado y se perderá cualquier referencia previa almacenada.

supi
fuente
3

Además de permitirle reasignar la variable de otra persona a una instancia diferente de una clase, devolver múltiples valores, etc., usando refo le outpermite a otra persona saber qué necesita de ellos y qué piensa hacer con la variable que proporcionan

  • No es necesario ref o outsi todo lo que vas a hacer es modificar las cosas dentro de la MyClassinstancia que se pasa en el argumento someClass.

    • El método llamando verá cambios como someClass.Message = "Hello World"si se utiliza ref, outo nada
    • Escribir someClass = new MyClass()dentro myFunction(someClass)intercambia el objeto visto por el someClassen el alcance del myFunctionmétodo solamente. El método de llamada aún conoce la MyClassinstancia original que creó y pasó a su método
  • Usted necesita ref o outsi usted planea en el bombeo de someClasssalida para un objeto completamente nuevo y desea que el método de llamada para ver su cambio

    • Escribir someClass = new MyClass()dentro myFunction(out someClass)cambia el objeto visto por el método que llamómyFunction

Existen otros programadores

Y quieren saber qué vas a hacer con sus datos. Imagine que está escribiendo una biblioteca que será utilizada por millones de desarrolladores. Desea que sepan qué va a hacer con sus variables cuando llaman a sus métodos

  • El uso refhace una declaración de "Pase una variable asignada a algún valor cuando llame a mi método. Tenga en cuenta que podría cambiarlo por algo completamente diferente durante el curso de mi método. No espere que su variable apunte al objeto antiguo Cuando termine"

  • El uso outhace una declaración de "Pasar una variable de marcador de posición a mi método. No importa si tiene un valor o no; el compilador me obligará a asignarlo a un nuevo valor. Garantizo absolutamente que el objeto señalado por su variable antes de llamar a mi método, será diferente para cuando termine

Por cierto, en C # 7.2 también hay un inmodificador

Y eso evita que el método cambie la instancia pasada por una instancia diferente. Piense en ello como decir a esos millones de desarrolladores "pásenme su referencia variable original, y prometo no cambiar sus datos cuidadosamente elaborados por otra cosa". intiene algunas peculiaridades, y en algunos casos, como cuando se puede requerir una conversión implícita para hacer que su corto sea compatible con un in intcompilador, temporalmente hará un int, lo ampliará, lo pasará por referencia y terminará. Puede hacer esto porque has declarado que no te meterás con eso.


Microsoft hizo esto con los .TryParsemétodos en los tipos numéricos:

int i = 98234957;
bool success = int.TryParse("123", out i);

Al marcar el parámetro como outestán declarando activamente aquí " definitivamente vamos a cambiar su minuciosamente elaborado valor de 98234957 por algo más"

Por supuesto, tienen que hacerlo, por ejemplo, para analizar tipos de valores porque si no se permitiera al método de análisis intercambiar el tipo de valor por otra cosa, no funcionaría muy bien ... Pero imagine que hubiera algún método ficticio en algunos biblioteca que estás creando:

public void PoorlyNamedMethod(out SomeClass x)

Puedes ver que es un out, y así puedes saber que si pasas horas calculando números, creando el SomeClass perfecto:

SomeClass x = SpendHoursMakingMeAPerfectSomeClass();
//now give it to the library
PoorlyNamedMethod(out x);

Bueno, eso fue una pérdida de tiempo, tomar todas esas horas para hacer esa clase perfecta. Definitivamente será desechado y reemplazado por PoorlyNamedMethod

Caius Jard
fuente
3

Para aquellos que buscan una respuesta concisa.

Ambas palabras clave refy outse utilizan para pasar por alto reference.


Una variable de refpalabra clave debe tener un valor o debe referirse a un objeto o null antes de pasar.


A diferencia ref, una variable de outpalabra clave debe tener un valor o debe referirse a un objeto o null después de su paso, así como no tener que tener un valor o referirse a un objeto antes de pasar.

snr
fuente
2

Para ilustrar las muchas explicaciones excelentes, desarrollé la siguiente aplicación de consola:

using System;
using System.Collections.Generic;

namespace CSharpDemos
{
  class Program
  {
    static void Main(string[] args)
    {
      List<string> StringList = new List<string> { "Hello" };
      List<string> StringListRef = new List<string> { "Hallo" };

      AppendWorld(StringList);
      Console.WriteLine(StringList[0] + StringList[1]);

      HalloWelt(ref StringListRef);
      Console.WriteLine(StringListRef[0] + StringListRef[1]);

      CiaoMondo(out List<string> StringListOut);
      Console.WriteLine(StringListOut[0] + StringListOut[1]);
    }

    static void AppendWorld(List<string> LiStri)
    {
      LiStri.Add(" World!");
      LiStri = new List<string> { "¡Hola", " Mundo!" };
      Console.WriteLine(LiStri[0] + LiStri[1]);
    }

    static void HalloWelt(ref List<string> LiStriRef)
     { LiStriRef = new List<string> { LiStriRef[0], " Welt!" }; }

    static void CiaoMondo(out List<string> LiStriOut)
     { LiStriOut = new List<string> { "Ciao", " Mondo!" }; }
   }
}
/*Output:
¡Hola Mundo!
Hello World!
Hallo Welt!
Ciao Mondo!
*/
  • AppendWorld: Se pasa una copia de StringListnamed LiStri. Al comienzo del método, esta copia hace referencia a la lista original y, por lo tanto, puede usarse para modificar esta lista. Más tarde hace LiStrireferencia a otro List<string>objeto dentro del método que no afecta a la lista original.

  • HalloWelt: LiStriRefes un alias de lo ya inicializado ListStringRef. El List<string>objeto pasado se usa para inicializar uno nuevo, por reflo tanto, era necesario.

  • CiaoMondo: LiStriOutes un alias de ListStringOuty debe inicializarse.

Entonces, si un método simplemente modifica el objeto al que hace referencia la variable pasada, el compilador no le permitirá usarlo outy no debe usarlo refporque confundiría no al compilador sino al lector del código. Si el método hará que el argumento pasado haga referencia a otro objeto, úselo refpara un objeto ya inicializado y outpara métodos que deben inicializar un nuevo objeto para el argumento pasado. Además de eso, refy outcomportarse igual.

Dietrich Baumgarten
fuente
1

Son más o menos lo mismo: la única diferencia es que una variable que pasa como parámetro de salida no necesita inicializarse, y el método que usa el parámetro ref tiene que establecerla en algo.

int x;    Foo(out x); // OK 
int y;    Foo(ref y); // Error

Los parámetros de referencia son para datos que pueden modificarse, los parámetros de salida son para datos que son una salida adicional para la función (por ejemplo, int. TryParse) que ya están utilizando el valor de retorno para algo.

Talha Khan
fuente
1

A continuación, he mostrado un ejemplo usando tanto Ref como out . Ahora, todos estarán libres de ref y out.

En el ejemplo mencionado a continuación, cuando comento // myRefObj = new myClass {Name = "ref outside called !!"}; línea, se obtiene un error diciendo "El uso de variable local no asignada 'myRefObj'" , pero no hay tal error en la salida .

Dónde usar Ref : cuando llamamos a un procedimiento con un parámetro in y se usará el mismo parámetro para almacenar la salida de ese proceso.

Dónde usar Out: cuando llamamos a un procedimiento sin parámetro in y el mismo parámetro se utilizará para devolver el valor de ese proceso. También tenga en cuenta la salida

public partial class refAndOutUse : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        myClass myRefObj;
        myRefObj = new myClass { Name = "ref outside called!!  <br/>" };
        myRefFunction(ref myRefObj);
        Response.Write(myRefObj.Name); //ref inside function

        myClass myOutObj;
        myOutFunction(out myOutObj);
        Response.Write(myOutObj.Name); //out inside function
    }

    void myRefFunction(ref myClass refObj)
    {
        refObj.Name = "ref inside function <br/>";
        Response.Write(refObj.Name); //ref inside function
    }
    void myOutFunction(out myClass outObj)
    {
        outObj = new myClass { Name = "out inside function <br/>" }; 
        Response.Write(outObj.Name); //out inside function
    }
}

public class myClass
{
    public string Name { get; set; }
} 
Ankur Bhutani
fuente
1
 public static void Main(string[] args)
    {
        //int a=10;
        //change(ref a);
        //Console.WriteLine(a);
        // Console.Read();

        int b;
        change2(out b);
        Console.WriteLine(b);
        Console.Read();
    }
    // static void change(ref int a)
    //{
    //    a = 20;
    //}

     static void change2(out int b)
     {
         b = 20;
     }

puede verificar este código, le describirá su diferencia completa cuando use "ref", lo que significa que ya ha inicializado ese int / string

pero cuando usa "out" funciona en ambas condiciones, ya sea que inicialice ese int / string o no, pero debe inicializar ese int / string en esa función

Haris Zia
fuente
1

Ref: la palabra clave ref se usa para pasar un argumento como referencia. Esto significa que cuando el valor de ese parámetro se cambia en el método, se refleja en el método de llamada. Un argumento que se pasa usando una palabra clave ref debe inicializarse en el método de llamada antes de pasarlo al método llamado.

Out: la palabra clave out también se usa para pasar un argumento como la palabra clave ref, pero el argumento se puede pasar sin asignarle ningún valor. Un argumento que se pasa usando una palabra clave out debe inicializarse en el método llamado antes de que regrese al método de llamada.

public class Example
{
 public static void Main() 
 {
 int val1 = 0; //must be initialized 
 int val2; //optional

 Example1(ref val1);
 Console.WriteLine(val1); 

 Example2(out val2);
 Console.WriteLine(val2); 
 }

 static void Example1(ref int value) 
 {
 value = 1;
 }
 static void Example2(out int value) 
 {
 value = 2; 
 }
}

/* Output     1     2     

Ref y fuera en método de sobrecarga

Tanto ref como out no se pueden usar en la sobrecarga de métodos simultáneamente. Sin embargo, ref y out se tratan de manera diferente en tiempo de ejecución, pero se tratan igual en tiempo de compilación (CLR no diferencia entre los dos mientras crea IL para ref y out).

Dejan Ciev
fuente
0

Desde el punto de vista de un método que recibe un parámetro, la diferencia entre refy outes que C # requiere que los métodos deben escribir en cada outparámetro antes de regresar, y no deben hacer nada con dicho parámetro, aparte de pasarlo como outparámetro o escribir en él , hasta que se haya pasado como outparámetro a otro método o se haya escrito directamente. Tenga en cuenta que algunos otros idiomas no imponen tales requisitos; un método virtual o de interfaz que se declara en C # con un outparámetro puede anularse en otro idioma que no imponga restricciones especiales sobre dichos parámetros.

Desde el punto de vista de la persona que llama, C # en muchas circunstancias asumirá que llamar a un método con un outparámetro hará que la variable pasada se escriba sin haber sido leída primero. Esta suposición puede no ser correcta al llamar a métodos escritos en otros idiomas. Por ejemplo:

struct MyStruct
{
   ...
   myStruct(IDictionary<int, MyStruct> d)
   {
     d.TryGetValue(23, out this);
   }
}

Si myDictionaryidentifica una IDictionary<TKey,TValue>implementación escrita en un lenguaje que no sea C #, aunque MyStruct s = new MyStruct(myDictionary);parezca una tarea, podría dejars sin modificar.

Tenga en cuenta que los constructores escritos en VB.NET, a diferencia de los de C #, no hacen suposiciones sobre si los métodos llamados modificarán algún outparámetro y borrarán todos los campos incondicionalmente. El comportamiento extraño aludido anteriormente no ocurrirá con el código escrito completamente en VB o completamente en C #, pero puede ocurrir cuando el código escrito en C # llama a un método escrito en VB.NET.

Super gato
fuente
0

Si desea pasar su parámetro como una referencia, debe inicializarlo antes de pasar el parámetro a la función; de lo contrario, el compilador mismo mostrará el error. Pero en caso de que no haya un parámetro, no necesita inicializar el parámetro del objeto antes de pasarlo al método. Puede inicializar el objeto en el propio método de llamada.

Rakeshkumar Das
fuente
-3

Tenga en cuenta que el parámetro de referencia que se pasa dentro de la función se trabaja directamente.

Por ejemplo,

    public class MyClass
    {
        public string Name { get; set; }
    }

    public void Foo()
    {
        MyClass myObject = new MyClass();
        myObject.Name = "Dog";
        Bar(myObject);
        Console.WriteLine(myObject.Name); // Writes "Dog".
    }

    public void Bar(MyClass someObject)
    {
        MyClass myTempObject = new MyClass();
        myTempObject.Name = "Cat";
        someObject = myTempObject;
    }

Esto escribirá Perro, no Gato. Por lo tanto, debe trabajar directamente en someObject.

Mangesh Pimpalkar
fuente
66
Si bien todo aquí es bastante cierto, en realidad no explica la diferencia entre por valor por referencia o fuera. En el mejor de los casos, explica a medias la diferencia entre los tipos de referencia y valor / inmutable.
Conrad Frix
Si desea que el código escriba cat, pase ese objeto junto con la tecla 'ref' de esta manera: public static void Bar (ref MyClass someObject), Bar (ref myObject);
Daniel Botero Correa
-4

Puede que no sea tan bueno en esto, pero seguramente las cadenas (aunque técnicamente son tipos de referencia y viven en el montón) se pasan por valor, no por referencia.

        string a = "Hello";

        string b = "goodbye";

        b = a; //attempt to make b point to a, won't work.

        a = "testing";

        Console.WriteLine(b); //this will produce "hello", NOT "testing"!!!!

Es por eso que necesita ref si desea que existan cambios fuera del alcance de la función que los hace, de lo contrario no está pasando una referencia.

Por lo que sé, solo necesita ref para estructuras / tipos de valor y la cadena en sí, ya que la cadena es un tipo de referencia que finge que es pero no es un tipo de valor.

Sin embargo, podría estar completamente equivocado aquí, soy nuevo.

Edwin
fuente
55
Bienvenido a Stack Overflow, Edwin. Las cadenas se pasan por referencia, al igual que cualquier otro objeto, que yo sepa. Puede estar confundido porque las cadenas son objetos inmutables, por lo que no es tan obvio que se pasen por referencia. Imagine que la cadena tiene un método llamado Capitalize()que cambiaría el contenido de la cadena a mayúsculas. Si luego reemplazó su línea a = "testing";con a.Capitalize();, entonces su salida sería "HOLA", no "Hola". Una de las ventajas de los tipos inmutables es que puede pasar referencias y no preocuparse de que otro código cambie el valor.
Don Kirkby
2
Hay tres tipos fundamentales de semántica que un tipo puede exponer: semántica de referencia mutable, semántica de valor mutable y semántica inmutable. Considere las variables x e y de un tipo T, que tiene campo o propiedad m, y suponga que x se copia a y. Si T tiene semántica de referencia, ym observará cambios en xm. Si T tiene semántica de valor, se puede cambiar xm sin afectar a ym. Si T tiene semántica inmutable, ni xm ni ym cambiarán. La semántica inmutable se puede simular mediante objetos de referencia o de valor. Las cadenas son objetos de referencia inmutables.
supercat