¿Por qué C # no admite el retorno de referencias?

141

He leído que .NET admite el retorno de referencias, pero C # no. ¿Hay alguna razón especial? Por qué no puedo hacer algo como:

static ref int Max(ref int x, ref int y) 
{ 
  if (x > y) 
    return ref x; 
  else 
    return ref y; 
} 
Tom Sarduy
fuente
8
um ... cita por favor?
RPM1984
55
C # tiene valores y tipos de referencia y ambos pueden devolverse, ¿qué quiere decir?
volver a ejecutar el
Estoy hablando de algo comoreturn ref x
Tom Sarduy
2
Después de 4 años, puedes hacer exactamente esto con C# 7:)
Arghya C

Respuestas:

188

Esta pregunta fue el tema de mi blog el 23 de junio de 2011 . Gracias por la gran pregunta!

El equipo de C # está considerando esto para C # 7. Vea https://github.com/dotnet/roslyn/issues/5233 para más detalles.

ACTUALIZACIÓN: ¡La función llegó a C # 7!


Estás en lo correcto; .NET admite métodos que devuelven referencias administradas a variables. .NET también admite variables locales que contienen referencias administradas a otras variables. (Sin embargo, tenga en cuenta que .NET no admite campos o matrices que contengan referencias administradas a otras variables porque eso complica demasiado la historia de recolección de basura. Además, los tipos de "referencia administrada a variables" no son convertibles en objetos y, por lo tanto, no pueden usarse como escriba argumentos para tipos o métodos genéricos).

El comentarista "RPM1984" por alguna razón solicitó una cita para este hecho. RPM1984 Le animo a leer la especificación CLI Partición I Sección 8.2.1.1, "Punteros administrados y tipos relacionados" para obtener información sobre esta característica de .NET.

Es completamente posible crear una versión de C # que admita ambas funciones. Entonces podrías hacer cosas como

static ref int Max(ref int x, ref int y) 
{ 
  if (x > y) 
    return ref x; 
  else 
    return ref y; 
} 

y luego llamarlo con

int a = 123;
int b = 456; 
ref int c = ref Max(ref a, ref b); 
c += 100;
Console.WriteLine(b); // 556!

Sé empíricamente que es posible construir una versión de C # que admita estas características porque lo he hecho . Los programadores avanzados, particularmente las personas que portan código C ++ no administrado, a menudo nos solicitan más capacidad similar a C ++ para hacer cosas con referencias sin tener que sacar el gran martillo de usar punteros y fijar memoria en todo el lugar. Al usar referencias administradas, obtiene estos beneficios sin pagar el costo de arruinar el rendimiento de su recolección de basura.

Hemos considerado esta característica y, de hecho, la hemos implementado lo suficiente como para mostrarla a otros equipos internos para obtener sus comentarios. Sin embargo, en este momento, según nuestra investigación , creemos que la función no tiene un atractivo suficientemente amplio o casos de uso convincentes para convertirla en una función de lenguaje real compatible . Tenemos otras prioridades más altas y una cantidad limitada de tiempo y esfuerzo disponible, por lo que no vamos a hacer esta función en el corto plazo.

Además, hacerlo correctamente requeriría algunos cambios en el CLR. En este momento, el CLR trata los métodos de devolución de devolución como legales pero no verificables porque no tenemos un detector que detecte esta situación:

ref int M1(ref int x)
{
    return ref x;
}

ref int M2()
{
    int y = 123;
    return ref M1(ref y); // Trouble!
}

int M3()
{
    ref int z = ref M2();
    return z;
}

M3 devuelve el contenido de la variable local de M2, ¡pero la vida útil de esa variable ha terminado! Es posible escribir un detector que determine los usos de devoluciones de referencia que claramente no violan la seguridad de la pila. Lo que haríamos sería escribir dicho detector, y si el detector no pudiera demostrar la seguridad de la pila, entonces no permitiríamos el uso de devoluciones de referencia en esa parte del programa. No es una gran cantidad de trabajo de desarrollo hacerlo, pero es una gran carga para los equipos de prueba asegurarse de que realmente tenemos todos los casos. Es solo otra cosa que aumenta el costo de la función hasta el punto de que en este momento los beneficios no superan los costos.

Si puede describirme por qué quiere esta función, realmente lo agradecería . Cuanta más información tengamos de clientes reales sobre por qué lo quieren, más probabilidades habrá de que llegue al producto algún día. Es una pequeña característica linda y me gustaría poder llegar a los clientes de alguna manera si hay suficiente interés.

(Ver también preguntas relacionadas ¿Es posible devolver una referencia a una variable en C #? Y ¿Puedo utilizar una referencia dentro de una función C # como C ++? )

Eric Lippert
fuente
44
@EricLippert: No tengo ningún ejemplo convincente, es algo que me preguntaba. Respuesta excelente y convincente
Tom Sarduy
1
@Eric: En su ejemplo, ¿no sería más adecuado mantenerse con y vida después de regresar M2? Esperaría que esta característica funcione como lambdas capturando locales. ¿O es el comportamiento que propuso porque es cómo CLR maneja ese escenario?
Fede
3
@Eric: en mi humilde opinión, la capacidad de que las propiedades devuelvan referencias a tipos de valores es una omisión importante en los idiomas .net. Si Arr es una matriz de un tipo de valor (por ejemplo, Punto), se puede decir, por ejemplo, Arr (3) .X = 9 y saber que no se ha cambiado el valor de Arr (9) .X o incluso SomeOtherArray (2). X; si Arr fuera en cambio una matriz de algún tipo de referencia, no existirían tales garantías. El hecho de que el operador de indexación de una matriz devuelva una referencia es extremadamente útil; Considero muy desafortunado que ningún otro tipo de colección pueda proporcionar tal funcionalidad.
supercat
44
Eric, ¿cuál es la mejor manera de proporcionar comentarios sobre los escenarios (como sugieres en el último párrafo) para maximizar las posibilidades de que se vea? MS Connect? UserVoice (con su límite arbitrario de 10 mensajes / votos)? ¿Algo más?
Roman Starkov
1
@ThunderGr: Esa es la filosofía de C # para "inseguro": si escribe código que posiblemente no sea seguro para la memoria, C # insiste en que lo marque como "inseguro", de modo que asuma la responsabilidad de la seguridad de la memoria. C # ya tiene la versión insegura de la función, siempre que la variable en cuestión sea de tipo no administrado. La pregunta es si el equipo de C # debe hacer una versión insegura que maneje los tipos administrados. Si hacerlo facilita que el desarrollador escriba errores terribles, no va a suceder. C # no es C ++, un lenguaje que facilita la escritura de errores terribles. C # es seguro por diseño.
Eric Lippert
21

Estás hablando de métodos que devuelven una referencia a un tipo de valor. El único ejemplo incorporado en C # que conozco es el array-accessor de un tipo de valor:

public struct Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

y ahora crea una matriz de esa estructura:

var points = new Point[10];
points[0].X = 1;
points[0].Y = 2;

En este caso points[0], el indexador de matrices está devolviendo una referencia a struct. Es imposible escribir su propio indexador (por ejemplo, para una colección personalizada), que tiene el mismo comportamiento de "devolver una referencia".

No diseñé el lenguaje C #, así que no sé todo el razonamiento detrás de no admitirlo, pero creo que la respuesta corta podría ser: podemos llevarnos bien sin él.

Rick Sladkey
fuente
8
Tom pregunta acerca de los métodos que devuelven una referencia a una variable . La variable no necesita ser de tipo de valor, aunque, por supuesto, eso es lo que las personas quieren cuando quieren métodos de devolución de ref. De lo contrario, gran análisis; tiene razón en que el único lugar en el lenguaje C # donde una expresión compleja produce una referencia a una variable que el usuario puede manipular es el indexador de matriz. (Y, por supuesto, el operador de acceso miembro de entre un receptor y un campo, pero eso es bastante obvio un acceso a una variable "".)
Eric Lippert
1

Siempre puedes hacer algo como:

public delegate void MyByRefConsumer<T>(ref T val);

public void DoSomethingWithValueType(MyByRefConsumer<int> c)
{
        int x = 2;
        c(ref x);
        //Handle potentially changed x...
}
Eladian
fuente
1

C # 7.0 tiene soporte para devolver referencias. Mira mi respuesta aquí .

CodificaciónYoshi
fuente