¿Cuándo es una buena idea utilizar la palabra clave ref de C #?

Cuanto más veo el ref usado en el código de producción, más mal uso encuentro y más dolor me causa. He llegado a odiar esta palabra clave, porque desde el punto de vista de la construcción del marco, parece una tontería. ¿Cuándo sería una buena idea comunicar a los usuarios de su código la noción de tal vez cambiar una referencia / valor de un objeto debajo de ellos?

Por el contrario, amo las palabras clave y me encantan aún más cuando no se utilizan palabras clave en absoluto, en ambos casos debido a las garantías que se le otorgan al usarlas. Por otro lado, Ref no ofrece garantías, salvo que se te forzará a inicializar el parámetro antes de pasarlo, aunque no se haya cambiado nada.

Aunque no soy un desarrollador sabio; Estoy seguro de que tiene usos prácticamente aplicables. Solo me gustaría saber qué son.

Las Pautas de diseño del marco (un libro de Krzysztof Cwalina y Brad Abrams) recomiendan evitar los parámetros de ref y out .

EVITE usar parámetros de out o ref .

El uso de parámetros de ref o de ref requiere experiencia con indicadores, la comprensión de la diferencia entre los tipos de valores y los tipos de referencia, y el manejo de métodos con múltiples valores de retorno. Además, la diferencia entre out parámetros out y ref no se entiende ampliamente. Los arquitectos de frameworks que diseñan para una audiencia general no deben esperar que los usuarios dominen el trabajo out parámetros out o ref .

Las Directrices de diseño del marco citan el método canónico de Swap como una excepción válida:

 void Swap(ref T obj1, ref T obj2) { T temp = obj1; obj1 = obj2; obj2 = temp; } 

pero al mismo tiempo un comentario comenta

El intercambio siempre aparece en estas discusiones, pero no he escrito un código que realmente necesite un método de intercambio desde la universidad. A menos que tengas una muy buena razón, evita out y ref todo.

La mayoría de los métodos de Interlocked usan parámetros de ref (estoy seguro de que usted está de acuerdo).

Intento evitarlo en API públicas, pero definitivamente tiene usos. Los tipos de valores mutables son importantes, especialmente en cosas como CF (donde las estructuras mutables son más comunes, debido a los requisitos de la plataforma). Sin embargo, tal vez el momento más común en que lo uso es cuando refacto partes de un algoritmo complejo en unos pocos métodos, donde un objeto de estado es excesivo y necesito pasar varios valores:

es decir

 var x = ..... var y = ..... // some local code... var z = DoSomethingSpecific(ref x, ref y); // needs and updates x/y // more local code... 

etc. Donde DoSomethingSpecific es un método privado, simplemente se trasladó para mantener la responsabilidad del método manejable.

Cada vez que desee cambiar el valor de un tipo de valor , esto sucede mucho en los casos en que desee actualizar de manera eficiente un par de valores relacionados (es decir, en lugar de devolver una estructura que contenga dos entradas, pase (ref int x, ref int y))

Tal vez cuando tienes una estructura (que es un tipo de valor):

 struct Foo { int i; public void Test() { i++; } } static void update(ref Foo foo) { foo.Test(); } 

y

 Foo b = new Foo(); update(ref b); 

Aquí deberías usar dos parámetros out como:

 static void update(Foo foo, out Foo outFoo) //Yes I know you could return one foo instead of a out but look below { foo.Test(); outFoo = foo; } 

imaginando que el método tiene más de un Foo , obtendría dos veces los parámetros sin out y sin ref . Una alternativa es devolver una N-tupla. No tengo un ejemplo del mundo real sobre cuándo usar esto.

Agregar: diferentes métodos .TryParse también podrían haberse evitado si devolvían Nullable lugar, que esencialmente es una tupla de boolean * T

Es útil cuando necesitas algoritmos eficientes en el sitio en bignums.

Hipotéticamente, supongo que podría usar muchos argumentos de ref / out si tuviera la intención de imitar la architecture de un viejo software de procedimientos, por ejemplo, motores de juegos antiguos, etc. He escaneado el código fuente de uno, creo que fue Duke Nukem 3D, y es de procedimiento con muchas subrutinas que modifican variables en su lugar, y casi ninguna función. Obviamente, es poco probable que programes así para una aplicación de producción real a menos que tengas un objective específico en mente.

¿Qué tal si uno desea pasar una matriz a una función que podría o no cambiar su tamaño y hacer algo más con ella? A menudo, uno envolvería la matriz en otro objeto, pero si uno desea manejar la matriz directamente pasando por referencia parecería el enfoque más natural.

Estoy usando ref bastante a menudo. Solo piense en funciones con valores de retorno múltiples. No tiene sentido crear un objeto de retorno (objeto auxiliar) o incluso usar tablas hash para este propósito.

Ejemplo:

  getTreeNodeValues(ref selectedValue, ref selectedText); 

Editar:

Es mejor usarlo aquí, como se comentó.

  getTreeNodeValues(out selectedValue, out selectedText); 

Lo estoy usando para procesar objetos:

 MyCar car = new MyCar { Name="TestCar"; Wieght=1000; } UpdateWeight(ref car, 2000); 

Otro ejemplo útil además de swap <> es este:

 Prompter.getString("Name ? ", ref firstName); Prompter.getString("Lastname ? ", ref lastName); Prompter.getString("Birthday ? ", ref firstName); Prompter.getInt("Id ? ", ref id); Prompter.getChar("Id type:  \n? ", ref c); public static class Prompter { public static void getKey(string msg, ref string key) { Console.Write(msg); ConsoleKeyInfo cki = Console.ReadKey(); string k = cki.Key.ToString(); if (k.Length == 1) key = k; } public static void getChar(string msg, ref char key) { Console.Write(msg); key = Console.ReadKey().KeyChar; Console.WriteLine(); } public static void getString(string msg, ref string s) { Console.Write(msg); string input = Console.ReadLine(); if (input.Length != 0) s = input; } public static void getInt(string msg, ref int i) { int result; string s; Console.Write(msg); s = Console.ReadLine(); int.TryParse(s, out result); if (result != 0) i = result; } // not implemented yet public static string getDate(string msg) { // I should use DateTime.ParseExact(dateString, format, provider); throw new NotImplementedException(); } } 

Usar aquí no es una opción