Valores devueltos y variables locales de tipo refRef returns and ref locals

A partir de C# 7.0, C# admite los valores devueltos de referencia (valores devueltos de tipo ref).Starting with C# 7.0, C# supports reference return values (ref returns). Un valor devuelto de referencia permite que un método devuelva una referencia a una variable, en lugar de un valor, al autor de una llamada.A reference return value allows a method to return a reference to a variable, rather than a value, back to a caller. El autor de la llamada puede tratar la variable devuelta como si se hubiera devuelto por valor o por referencia.The caller can then choose to treat the returned variable as if it were returned by value or by reference. El autor de la llamada puede crear una variable que sea una referencia al valor devuelto, lo que se conoce como una referencia local.The caller can create a new variable that is itself a reference to the returned value, called a ref local.

¿Qué es un valor devuelto de referencia?What is a reference return value?

La mayoría de los desarrolladores están familiarizados con pasar un argumento a un método llamado por referencia.Most developers are familiar with passing an argument to a called method by reference. Una lista de argumentos del método llamado incluye una variable que pasa la referencia.A called method's argument list includes a variable passed by reference. El autor de la llamada observa los cambios que ha realizado el método llamado a su valor.Any changes made to its value by the called method are observed by the caller. Un valor devuelto de referencia significa que un método devuelve una referencia (o un alias) a alguna variable.A reference return value means that a method returns a reference (or an alias) to some variable. El ámbito de esa variable debe incluir el método.That variable's scope must include the method. La duración de la variable debe extenderse más allá de la devolución del método.That variable's lifetime must extend beyond the return of the method. Las modificaciones en el valor del método devuelto por el autor de la llamada se realizan en la variable devuelta por el método.Modifications to the method's return value by the caller are made to the variable that is returned by the method.

Declarar que un método devuelve un valor devuelto de referencia indica que el método devuelve un alias a una variable.Declaring that a method returns a reference return value indicates that the method returns an alias to a variable. El diseño suele pretender que el código de llamada acceda a dicha variable con el alias, incluso para modificarla.The design intent is often that the calling code should have access to that variable through the alias, including to modify it. Por tanto, los métodos devueltos por referencia no pueden tener el tipo de valor devuelto void.It follows that methods returning by reference can't have the return type void.

Hay algunas restricciones en la expresión que un método puede devolver como un valor devuelto de referencia.There are some restrictions on the expression that a method can return as a reference return value. Entre las restricciones se incluyen:Restrictions include:

  • El valor devuelto debe tener una duración que se extienda más allá de la ejecución del método.The return value must have a lifetime that extends beyond the execution of the method. En otras palabras, no puede tratarse de una variable local del método que la devuelve.In other words, it cannot be a local variable in the method that returns it. Puede ser una instancia o un campo estático de una clase, o puede ser un argumento pasado al método.It can be an instance or static field of a class, or it can be an argument passed to the method. Al intentar devolver una variable local, se genera el error del compilador CS8168, "No se puede devolver por referencia la variable local 'obj' porque no es de tipo ref".Attempting to return a local variable generates compiler error CS8168, "Cannot return local 'obj' by reference because it is not a ref local."

  • El valor devuelto no puede ser el literal null.The return value cannot be the literal null. Si devuelve null, se genera el error del compilador CS8156, "No se puede usar una expresión en este contexto porque no se puede devolver por referencia".Returning null generates compiler error CS8156, "An expression cannot be used in this context because it may not be returned by reference."

    Un método con un valor devuelto de referencia puede devolver un alias a una variable cuyo valor es actualmente el valor NULL (sin instancias) o un tipo de valor que admite un valor NULL para un tipo de valor.A method with a ref return can return an alias to a variable whose value is currently the null (uninstantiated) value or a nullable value type for a value type.

  • El valor devuelto no puede ser una constante, un miembro de enumeración, el valor devuelto por valor desde una propiedad o un método de class o struct.The return value cannot be a constant, an enumeration member, the by-value return value from a property, or a method of a class or struct. Si infringe esta regla, se genera el error del compilador CS8156, "No se puede usar una expresión en este contexto porque no se puede devolver por referencia".Violating this rule generates compiler error CS8156, "An expression cannot be used in this context because it may not be returned by reference."

Además, los valores devueltos por referencia no se permiten en métodos asincrónicos.In addition, reference return values are not allowed on async methods. Un método asincrónico puede volver antes de que haya terminado de ejecutarse, mientras que su valor devuelto aún no se conoce.An asynchronous method may return before it has finished execution, while its return value is still unknown.

Definir un valor devuelto de tipo refDefining a ref return value

Un método que devuelve un valor devuelto de referencia debe cumplir las dos condiciones siguientes:A method that returns a reference return value must satisfy the following two conditions:

  • La firma del método incluye la palabra clave ref delante del tipo de valor devuelto.The method signature includes the ref keyword in front of the return type.
  • Cada instrucción return del cuerpo del método incluye la palabra clave ref delante del nombre de la instancia devuelta.Each return statement in the method body includes the ref keyword in front of the name of the returned instance.

En el método siguiente se muestra un método que cumple estas condiciones y devuelve una referencia a un objeto Person denominado p:The following example shows a method that satisfies those conditions and returns a reference to a Person object named p:

public ref Person GetContactInformation(string fname, string lname)
{
    // ...method implementation...
    return ref p;
}

Usar un valor devuelto de tipo refConsuming a ref return value

El valor devuelto de tipo ref es un alias para otra variable en el ámbito del método llamado.The ref return value is an alias to another variable in the called method's scope. Puede interpretar cualquier uso del valor devuelto tipo ref como si se usara la variable a la que se asigna el alias:You can interpret any use of the ref return as using the variable it aliases:

  • Al asignar su valor, se asigna un valor a la variable a la que se asigna el alias.When you assign its value, you are assigning a value to the variable it aliases.
  • Al leer su valor, se lee un valor a la variable a la que se asigna el alias.When you read its value, you are reading the value of the variable it aliases.
  • Si la devolución se realiza por referencia, entonces devuelve un alias a esa misma variable.If you return it by reference, you are returning an alias to that same variable.
  • Si pasa el valor a otro método por referencia, pasará una referencia a la variable a la que se asigna el alias.If you pass it to another method by reference, you are passing a reference to the variable it aliases.
  • Al asignar un alias local tipo ref, crea un alias para la misma variable.When you make a ref local alias, you make a new alias to the same variable.

Variables locales de tipo refRef locals

Suponga que el método GetContactInformation se declara como un valor devuelto tipo ref:Assume the GetContactInformation method is declared as a ref return:

public ref Person GetContactInformation(string fname, string lname)

Una asignación por valor lee el valor de una variable y lo asigna a una nueva variable:A by-value assignment reads the value of a variable and assigns it to a new variable:

Person p = contacts.GetContactInformation("Brandie", "Best");

La asignación anterior declara p como una variable local.The preceding assignment declares p as a local variable. Su valor inicial se copia a partir de la lectura del valor devuelto por GetContactInformation.Its initial value is copied from reading the value returned by GetContactInformation. Las asignaciones futuras a p no cambiarán el valor de la variable devuelta por GetContactInformation.Any future assignments to p will not change the value of the variable returned by GetContactInformation. La variable p ya no es un alias de la variable devuelta.The variable p is no longer an alias to the variable returned.

Se declara una variable local tipo ref para copiar el alias en el valor original.You declare a ref local variable to copy the alias to the original value. En la siguiente asignación, p es un alias para la variable devuelta desde GetContactInformation.In the following assignment, p is an alias to the variable returned from GetContactInformation.

ref Person p = ref contacts.GetContactInformation("Brandie", "Best");

El uso posterior de p es lo mismo que usar la variable devuelta por GetContactInformation, porque p es un alias para dicha variable.Subsequent usage of p is the same as using the variable returned by GetContactInformation because p is an alias for that variable. Los cambios realizados en p también modifican la variable devuelta desde GetContactInformation.Changes to p also change the variable returned from GetContactInformation.

La palabra clave ref se usa antes de la declaración de variable local y antes de la llamada al método.The ref keyword is used both before the local variable declaration and before the method call.

Puede acceder a un valor por referencia de la misma manera.You can access a value by reference in the same way. En algunos casos, acceder a un valor por referencia aumenta el rendimiento, ya que evita una operación de copia potencialmente cara.In some cases, accessing a value by reference increases performance by avoiding a potentially expensive copy operation. Por ejemplo, en la instrucción siguiente se muestra cómo es posible definir un valor local de referencia que se usa para hacer referencia a un valor.For example, the following statement shows how one can define a ref local value that is used to reference a value.

ref VeryLargeStruct reflocal = ref veryLargeStruct;

La palabra clave ref se usa antes de la declaración de variable local y antes del valor en el segundo ejemplo.The ref keyword is used both before the local variable declaration and before the value in the second example. Si no se incluyen ambas palabras clave ref en la asignación y declaración de variable en ambos ejemplos, se produce el error del compilador CS8172, "No se puede inicializar una variable por referencia con un valor".Failure to include both ref keywords in the variable declaration and assignment in both examples results in compiler error CS8172, "Cannot initialize a by-reference variable with a value."

Antes de C# 7.3, las variables locales de tipo ref no se podían reasignar para hacer referencia a otro almacenamiento después de haberse inicializado.Prior to C# 7.3, ref local variables couldn't be reassigned to refer to different storage after being initialized. Esta restricción se ha quitado.That restriction has been removed. En el ejemplo siguiente, se muestra una reasignación:The following example shows a reassignment:

ref VeryLargeStruct reflocal = ref veryLargeStruct; // initialization
refLocal = ref anotherVeryLargeStruct; // reassigned, refLocal refers to different storage.

Las variables locales de tipo ref todavía deben inicializarse cuando se declaran.Ref local variables must still be initialized when they are declared.

Valores devueltos y variables locales de tipo ref: un ejemploRef returns and ref locals: an example

En el ejemplo siguiente, se define una clase NumberStore que almacena una matriz de valores enteros.The following example defines a NumberStore class that stores an array of integer values. El método FindNumber devuelve por referencia el primer número que es mayor o igual que el número que se pasa como argumento.The FindNumber method returns by reference the first number that is greater than or equal to the number passed as an argument. Si ningún número es mayor o igual que el argumento, el método devuelve el número en el índice 0.If no number is greater than or equal to the argument, the method returns the number in index 0.

using System;

class NumberStore
{
    int[] numbers = { 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023 };

    public ref int FindNumber(int target)
    {
        for (int ctr = 0; ctr < numbers.Length; ctr++)
        {
            if (numbers[ctr] >= target)
                return ref numbers[ctr];
        }
        return ref numbers[0];
    }

    public override string ToString() => string.Join(" ", numbers);
}

En el ejemplo siguiente, se llama al método NumberStore.FindNumber para recuperar el primer valor que es mayor o igual que 16.The following example calls the NumberStore.FindNumber method to retrieve the first value that is greater than or equal to 16. Después, el autor de la llamada duplica el valor devuelto por el método.The caller then doubles the value returned by the method. En el resultado del ejemplo se muestra el cambio reflejado en el valor de los elementos de matriz de la instancia NumberStore.The output from the example shows the change reflected in the value of the array elements of the NumberStore instance.

var store = new NumberStore();
Console.WriteLine($"Original sequence: {store.ToString()}");
int number = 16;
ref var value = ref store.FindNumber(number);
value *= 2;
Console.WriteLine($"New sequence:      {store.ToString()}");
// The example displays the following output:
//       Original sequence: 1 3 7 15 31 63 127 255 511 1023
//       New sequence:      1 3 7 15 62 63 127 255 511 1023

Sin que se admitan los valores devueltos de referencia, este tipo de operación se realiza al devolver el índice del elemento de matriz junto con su valor.Without support for reference return values, such an operation is performed by returning the index of the array element along with its value. Después, el autor de la llamada puede usar este índice para modificar el valor en una llamada al método independiente.The caller can then use this index to modify the value in a separate method call. En cambio, el autor de la llamada también puede modificar el índice para tener acceso a otros valores de matriz y, posiblemente, modificarlos.However, the caller can also modify the index to access and possibly modify other array values.

En el siguiente ejemplo, se muestra cómo el método FindNumber podría modificarse después de C# 7.3 para usar la reasignación local de tipo ref:The following example shows how the FindNumber method could be rewritten after C# 7.3 to use ref local reassignment:

using System;

class NumberStore
{
    int[] numbers = { 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023 };

    public ref int FindNumber(int target)
    {
        ref int returnVal = ref numbers[0];
        var ctr = numbers.Length - 1;
        while ((ctr > 0) && numbers[ctr] >= target)
        {
            returnVal = ref numbers[ctr];
            ctr--;
        }
        return ref returnVal;
    }

    public override string ToString() => string.Join(" ", numbers);
}

Esta segunda versión es más eficaz con secuencias mayores en escenarios donde el número que se busca está más cerca del final de la matriz.This second version is more efficient with longer sequences in scenarios where the number sought is closer to the end of the array.

Vea tambiénSee also