Desde el principio, C # admitió pasar argumentos por valor o por referencia. Pero antes de la versión 7, el compilador de C # solo admitía una forma de devolver un valor de un método (o propiedad): devolución por valor. En C # 7, la situación ha cambiado con la introducción de dos nuevas características: devoluciones de ref y locales de ref. Más información sobre ellos y su desempeño, debajo del corte.

Razones
Existen muchas diferencias entre las matrices y otras colecciones en términos de Common Language Runtime. Desde el principio, CLR admite matrices y se pueden considerar como una funcionalidad integrada. El entorno CLR y el compilador JIT pueden funcionar con matrices, y también tienen una característica más: el indexador de matrices devuelve elementos por referencia y no por valor.
Para demostrar esto, tendremos que recurrir al método prohibido: use el tipo de valor mutable:
public struct Mutable { private int _x; public Mutable(int x) => _x = x; public int X => _x; public void IncrementX() { _x++; } } [Test] public void CheckMutability() { var ma = new[] {new Mutable(1)}; ma[0].IncrementX();
La prueba tendrá éxito porque el indexador de matriz es significativamente diferente del indexador de lista.
El compilador de C # da una instrucción especial al indexador de matriz: ldelema, que devuelve un enlace administrado a un elemento de esta matriz. Esencialmente, un indexador de matriz devuelve un elemento por referencia. Sin embargo, List no puede comportarse de la misma manera, porque en C # no fue posible * devolver un alias de estado interno. Por lo tanto, el indexador de Lista devuelve un elemento por valor, es decir, devuelve una copia de este elemento.
* Como veremos pronto, el indexador de listas aún no puede devolver un elemento por referencia.
Esto significa que ma [0] .IncrementX () llama al método que modifica el primer elemento de la matriz, mientras que ml [0] .IncrementX () llama al método que modifica la copia del elemento sin afectar la lista original.
Valores devueltos y variables locales de referencia: conceptos básicos
El significado de estas funciones es muy simple: declarar el valor de referencia devuelto le permite devolver el alias de una variable existente, y la variable local de referencia puede almacenar dicho alias.
1. Un ejemplo simple:
[Test] public void RefLocalsAndRefReturnsBasics() { int[] array = { 1, 2 };
2. Valores de referencia devueltos y modificador de solo lectura
El valor de referencia devuelto puede devolver el alias del campo de instancia y, a partir de la versión 7.2 de C #, puede devolver el alias sin poder escribir en el objeto correspondiente utilizando el modificador de solo lectura de referencia:
class EncapsulationWentWrong { private readonly Guid _guid; private int _x; public EncapsulationWentWrong(int x) => _x = x;
- Los métodos y propiedades pueden devolver un "alias" del estado interno. En este caso, el método de la tarea no debe definirse para la propiedad.
- La devolución por referencia rompe la encapsulación, ya que el cliente obtiene el control total sobre el estado interno del objeto.
- La devolución a través de un enlace de solo lectura evita la copia innecesaria de tipos de valores, al tiempo que no permite que el cliente cambie el estado interno.
- Los enlaces de solo lectura se pueden usar para tipos de referencia, aunque esto no tiene mucho sentido en casos no estándar.
3. Restricciones existentes. Devolver un alias puede ser peligroso: usar un alias para una variable colocada en la pila después de que se complete el método bloqueará la aplicación. Para que esta función sea segura, el compilador de C # aplica varias restricciones:
- No se puede devolver el enlace a la variable local.
- No se puede devolver una referencia a esto en las estructuras.
- Puede devolver un enlace a una variable ubicada en el montón (por ejemplo, a un miembro de la clase).
- Puede devolver un enlace a los parámetros de ref / out.
Para obtener más información, le recomendamos que consulte la excelente publicación
Safe to return rules para devoluciones de referencia . El autor del artículo, Vladimir Sadov, es el creador de la función de referencia de retorno para el compilador de C #.
Ahora que tenemos una idea general de los valores de referencia devueltos y las variables locales referenciadas, veamos cómo se pueden usar.
Uso de valores de referencia devueltos en indexadores
Para probar el impacto de estas funciones en el rendimiento, crearemos una colección única e inmutable llamada NaiveImmutableList <T> y la compararemos con T [] y List para estructuras de diferentes tamaños (4, 16, 32 y 48).
public class NaiveImmutableList<T> { private readonly int _length; private readonly T[] _data; public NaiveImmutableList(params T[] data) => (_data, _length) = (data, data.Length); public ref readonly T this[int idx]
Se realiza una prueba de rendimiento para todas las colecciones y suma todos los N valores de propiedad para cada elemento:
private const int elementsCount = 100_000; private static LargeStruct_48[] CreateArray_48() => Enumerable.Range(1, elementsCount).Select(v => new LargeStruct_48(v)).ToArray(); private readonly LargeStruct_48[] _array48 = CreateArray_48(); [BenchmarkCategory("BigStruct_48")] [Benchmark(Baseline = true)] public int TestArray_48() { int result = 0;
Los resultados son los siguientes:

Al parecer, algo está mal! El rendimiento de nuestra colección NaiveImmutableList <T> es el mismo que el de la Lista. Que paso
Valores devueltos con modificador de solo lectura: cómo funciona
Como puede ver, el indexador NaiveImmutableList <T> devuelve un enlace de solo lectura utilizando el modificador de solo lectura de referencia. Esto está totalmente justificado, ya que queremos limitar la capacidad de los clientes para cambiar el estado subyacente de una colección inmutable. Sin embargo, las estructuras que utilizamos en la prueba de rendimiento no solo son legibles.
Esta prueba nos ayudará a comprender el comportamiento básico:
[Test] public void CheckMutabilityForNaiveImmutableList() { var ml = new NaiveImmutableList<Mutable>(new Mutable(1)); ml[0].IncrementX();
¡La prueba falló! Pero por que? Debido a que la estructura de los "enlaces de solo lectura" es similar a la estructura de los modificadores y los campos de solo lectura con respecto a las estructuras: el compilador genera una copia protectora cada vez que se utiliza un elemento de estructura. Esto significa que ml [0]. todavía crea una copia del primer elemento, pero el indexador no lo hace: la copia se crea en el punto de la llamada.
Este comportamiento en realidad tiene sentido. El compilador de C # admite pasar argumentos por valor, por referencia y por "enlace de solo lectura" utilizando el modificador in (para obtener más información, consulte
El modificador in y las estructuras de solo lectura en C # ("El modificador in y las estructuras de solo lectura en C # ")). Ahora el compilador admite tres formas diferentes de devolver un valor de un método: por valor, por referencia y por enlace de solo lectura.
Los enlaces de solo lectura son tan similares a los enlaces normales que el compilador usa el mismo InAttribute para distinguir entre sus valores de retorno:
private int _n; public ref readonly int ByReadonlyRef() => ref _n;
En este caso, el método ByReadonlyRef compila eficientemente en:
[InAttribute] [return: IsReadOnly] public int* ByReadonlyRef() { return ref this._n; }
La similitud entre el modificador in y el enlace de solo lectura significa que estas funciones no son muy adecuadas para estructuras regulares y pueden causar problemas de rendimiento. Considere un ejemplo:
public struct BigStruct {
Además de la sintaxis inusual al declarar una variable para bigStruct, el código se ve bien. El objetivo es claro: BigStruct regresa por referencia por razones de rendimiento. Desafortunadamente, dado que la estructura de BigStruct se puede escribir, se crea una copia protectora cada vez que se accede al elemento.
Uso de valores de referencia devueltos en indexadores. Intento número 2
Probemos el mismo conjunto de pruebas para estructuras de solo lectura de diferentes tamaños:

Ahora los resultados tienen mucho más sentido. El tiempo de procesamiento sigue aumentando para estructuras grandes, pero esto se espera, ya que el procesamiento de más de 100 mil estructuras más grandes lleva más tiempo. Pero ahora el tiempo de ejecución para NaiveimmutableList <T> está muy cerca del tiempo T [] y mucho mejor que en el caso de List.
Conclusión
- Los valores de referencia devueltos deben manejarse con cuidado porque pueden romper la encapsulación.
- Los valores de referencia devueltos con modificador de solo lectura solo son efectivos para estructuras de solo lectura. En el caso de estructuras convencionales, pueden ocurrir problemas de rendimiento.
- Cuando se trabaja con estructuras grabables, los valores de referencia devueltos con el modificador de solo lectura crean una copia protectora cada vez que se usa la variable, lo que puede causar problemas de rendimiento.
Los valores de referencia devueltos y las variables locales referenciadas son funciones útiles para los creadores de bibliotecas y los desarrolladores de códigos de infraestructura. Sin embargo, son muy peligrosos de usar en el código de la biblioteca: para usar una colección que devuelva elementos de manera efectiva utilizando un enlace de solo lectura, cada usuario de la biblioteca debe recordar: un enlace de solo lectura a una estructura grabable crea una copia protectora "en el punto de la llamada ". En el mejor de los casos, esto negará un posible aumento de la productividad y, en el peor de los casos, provocará un grave deterioro si al mismo tiempo se realiza un gran número de solicitudes a una variable local de referencia, de solo lectura.
Los enlaces de solo lectura de PS aparecerán en BCL. Los métodos de referencia de solo lectura para acceder a elementos en colecciones inmutables se presentaron en la siguiente solicitud para incluir los cambios en corefx repo (
Implementación de la propuesta de la API de ItemRef ("Propuesta para incluir la API de ItemRef")). Por lo tanto, es muy importante que todos comprendan las características del uso de estas funciones y cómo y cuándo deben aplicarse.