ref (Referencia de C#)

La palabra clave ref indica un valor que se ha pasado por referencia. Se usa en cuatro contextos diferentes:

  • En una firma del método y en una llamada al método, para pasar un argumento a un método mediante referencia. Para más información, vea Pasar un argumento mediante referencia.
  • En una firma del método, para devolver un valor al autor de la llamada mediante referencia. Para obtener más información, consulte Valores devueltos de referencia.
  • En un cuerpo de miembro, para indicar que un valor devuelto de referencia se almacena localmente como una referencia que el autor de la llamada pretende modificar. O para indicar que una variable local tiene acceso a otro valor por referencia. Para más información, vea Variables locales de tipo ref.
  • En una declaración struct, para declarar ref struct o readonly ref struct. Para obtener más información, vea la sección struct ref del artículo tipos de estructura.

Pasar un argumento mediante referencia

Cuando se usa en una lista de parámetros del método, la palabra clave ref indica que un argumento se ha pasado mediante referencia, no por valor. La palabra clave ref hace que el parámetro formal sea un alias para el argumento, que debe ser una variable. En otras palabras, cualquier operación en el parámetro se realiza en el argumento.

Por ejemplo, supongamos que el autor de la llamada pasa una expresión de variable local o una expresión de acceso a un elemento de matriz. El método al que se llama puede reemplazar el objeto al que hace referencia el parámetro ref. En ese caso, la variable local del autor de la llamada o el elemento de matriz hace referencia al nuevo objeto en la devolución del método.

Nota:

No confunda el concepto de pasar por referencia con el concepto de tipos de referencia. Estos dos conceptos no son lo mismo. Un parámetro de método puede ser modificado por ref independientemente de si se trata de un tipo de valor o de un tipo de referencia. No hay ninguna conversión boxing de un tipo de valor cuando se pasa por referencia.

Para usar un parámetro ref, la definición de método y el método de llamada deben utilizar explícitamente la palabra clave ref, como se muestra en el ejemplo siguiente. (Salvo que el método de llamada puede omitir ref al realizar una llamada COM).

void Method(ref int refArgument)
{
    refArgument = refArgument + 44;
}

int number = 1;
Method(ref number);
Console.WriteLine(number);
// Output: 45

Un argumento que se pasa a un parámetro ref o in debe inicializarse antes de pasarlo. Este requisito difiere de los parámetros out, cuyos argumentos no tienen que inicializarse explícitamente antes de pasarlos.

Los miembros de una clase no pueden tener signaturas que se diferencien solo por ref, in o out. Si la única diferencia entre dos miembros de un tipo es que uno de ellos tiene un parámetro ref y el otro tiene un parámetro out o in, se produce un error de compilador. El código siguiente, por ejemplo, no se compila.

class CS0663_Example
{
    // Compiler error CS0663: "Cannot define overloaded
    // methods that differ only on ref and out".
    public void SampleMethod(out int i) { }
    public void SampleMethod(ref int i) { }
}

En cambio, los métodos pueden sobrecargarse cuando un método tiene un parámetro ref, in o out y el otro tiene un parámetro que se pasa por valor, como se muestra en el ejemplo siguiente.

class RefOverloadExample
{
    public void SampleMethod(int i) { }
    public void SampleMethod(ref int i) { }
}

En otras situaciones que requieran firma coincidente, como ocultar o reemplazar, in, ref y out forman parte de la signatura y no coinciden entre sí.

Las propiedades no son variables. Son métodos y no se pueden pasar a parámetros ref.

Las palabras clave ref, in y out no pueden usarse para estos tipos de métodos:

  • Métodos asincrónicos, que se definen mediante el uso del modificador async.
  • Métodos de iterador, que incluyen una instrucción yield return o yield break.

Los métodos de extensión también tienen restricciones en el uso de estas palabras clave:

  • No se puede usar la palabra clave out en el primer argumento de un método de extensión.
  • No se puede usar la palabra clave ref en el primer argumento de un método de extensión cuando el argumento no es un struct ni un tipo genérico no restringido a ser un struct.
  • No se puede usar la palabra clave in a menos que el primer argumento sea un struct. No se puede usar la palabra clave in en ningún tipo genérico, incluso cuando está restringido a ser un struct.

Paso de un argumento mediante referencia: Un ejemplo

En los ejemplos anteriores se pasan tipos de valor mediante referencia. También se puede usar la palabra clave ref para pasar tipos de referencia mediante referencia. Pasar un tipo de referencia por referencia permite que el método llamado pueda reemplazar el objeto al que hace referencia el parámetro de referencia en el autor de la llamada. La ubicación de almacenamiento del objeto se pasa al método como el valor del parámetro de referencia. Si cambia el valor de la ubicación de almacenamiento del parámetro (para que apunte a un nuevo objeto), también debe cambiar la ubicación de almacenamiento a la que se refiere el autor de la llamada. En el ejemplo siguiente se pasa una instancia de un tipo de referencia como un parámetro ref.

class Product
{
    public Product(string name, int newID)
    {
        ItemName = name;
        ItemID = newID;
    }

    public string ItemName { get; set; }
    public int ItemID { get; set; }
}

private static void ChangeByReference(ref Product itemRef)
{
    // Change the address that is stored in the itemRef parameter.
    itemRef = new Product("Stapler", 99999);

    // You can change the value of one of the properties of
    // itemRef. The change happens to item in Main as well.
    itemRef.ItemID = 12345;
}

private static void ModifyProductsByReference()
{
    // Declare an instance of Product and display its initial values.
    Product item = new Product("Fasteners", 54321);
    System.Console.WriteLine("Original values in Main.  Name: {0}, ID: {1}\n",
        item.ItemName, item.ItemID);

    // Pass the product instance to ChangeByReference.
    ChangeByReference(ref item);
    System.Console.WriteLine("Back in Main.  Name: {0}, ID: {1}\n",
        item.ItemName, item.ItemID);
}

// This method displays the following output:
// Original values in Main.  Name: Fasteners, ID: 54321
// Back in Main.  Name: Stapler, ID: 12345

Para obtener más información sobre cómo pasar tipos de referencia por valor y por referencia, vea Pasar parámetros Reference-Type .

Valores devueltos de referencia

Los valores devueltos de referencia (o valores devueltos de tipo ref) son valores que devuelve un método mediante referencia al autor de la llamada. Es decir, el autor de la llamada puede modificar el valor devuelto por un método y ese cambio se refleja en el estado del objeto en el método llamado.

Un valor devuelto de referencia se define mediante la palabra clave ref:

  • En la firma del método. Por ejemplo, en la firma de método siguiente se indica que el método GetCurrentPrice devuelve un valor Decimal por referencia.
public ref decimal GetCurrentPrice()
  • Entre el token return y la variable devuelta en una instrucción return en el método. Por ejemplo:
return ref DecimalArray[0];

Para que el autor de la llamada modifique el estado del objeto, el valor devuelto de referencia debe almacenarse en una variable que se defina explícitamente como una variable local de tipo ref.

Este es un ejemplo de valor devuelto de referencia más completo que muestra la firma y el cuerpo del método.

public static ref int Find(int[,] matrix, Func<int, bool> predicate)
{
    for (int i = 0; i < matrix.GetLength(0); i++)
        for (int j = 0; j < matrix.GetLength(1); j++)
            if (predicate(matrix[i, j]))
                return ref matrix[i, j];
    throw new InvalidOperationException("Not found");
}

El método llamado también puede declarar el valor devuelto como ref readonly para devolver el valor por referencia y exigir que el código de llamada no pueda modificar el valor devuelto. El método de llamada puede evitar copiar el valor devuelto si lo almacena en una variable de tipo ref readonly.

Para obtener un ejemplo, vea Un ejemplo de valores devueltos y variables locales de tipo ref.

Variables locales de tipo ref

Una variable local de tipo ref se usa para hacer referencia a valores devueltos con return ref. Una variable local de tipo ref no se puede inicializar en un valor devuelto de tipo no ref. Es decir, el lado derecho de la inicialización debe ser una referencia. Cualquier modificación en el valor de la variable local de tipo ref se refleja en el estado del objeto cuyo método ha devuelto el valor mediante referencia.

Puede definir un valor ref local mediante la palabra clave ref en dos lugares:

  • Antes de la declaración de variable.
  • Inmediatamente antes de la llamada al método que devuelve el valor por referencia.

Por ejemplo, en la siguiente instrucción se define un valor de variable local de tipo ref que se devuelve mediante un método denominado GetEstimatedValue:

ref decimal estValue = ref Building.GetEstimatedValue();

Puede acceder a un valor por referencia de la misma manera. En algunos casos, acceder a un valor por referencia aumenta el rendimiento, ya que evita una operación de copia potencialmente cara. Por ejemplo, en la instrucción siguiente se muestra cómo definir una variable local de referencia que se usa para hacer referencia a un valor.

ref VeryLargeStruct reflocal = ref veryLargeStruct;

En ambos ejemplos la palabra clave ref debe usarse en ambos lugares. De lo contrario, el compilador genera el error CS8172, "No se puede inicializar una variable por referencia con un valor".

A partir C# 7.3, la variable de iteración de la instrucción foreach puede ser una variable ref local o ref readonly local. Para más información, vea el artículo sobre la instrucción foreach.

A partir C# 7.3, las variables locales de tipo ref o locales de tipo ref readonly se pueden reasignar con el operador de asignación ref.

Variables locales de tipo ref readonly

Una variable local de tipo ref readonly se usa para hacer referencia a los valores devueltos por el método o la propiedad que tiene ref readonly en su firma y utiliza return ref. Una variable ref readonly combina las propiedades de una ref variable local con una variable readonly: es un alias para el almacenamiento al que se asigna y no se puede modificar.

Un ejemplo de valores devueltos y variables locales de tipo ref

En el ejemplo siguiente se define una clase Book que tiene dos campos String, Title y Author. También define una clase BookCollection que incluye una matriz privada de objetos Book. Los objetos book individuales se devuelven mediante referencia llamando a su método GetBookByTitle.


public class Book
{
    public string Author;
    public string Title;
}

public class BookCollection
{
    private Book[] books = { new Book { Title = "Call of the Wild, The", Author = "Jack London" },
                        new Book { Title = "Tale of Two Cities, A", Author = "Charles Dickens" }
                       };
    private Book nobook = null;

    public ref Book GetBookByTitle(string title)
    {
        for (int ctr = 0; ctr < books.Length; ctr++)
        {
            if (title == books[ctr].Title)
                return ref books[ctr];
        }
        return ref nobook;
    }

    public void ListBooks()
    {
        foreach (var book in books)
        {
            Console.WriteLine($"{book.Title}, by {book.Author}");
        }
        Console.WriteLine();
    }
}

Cuando el autor de la llamada almacena el valor devuelto mediante el método GetBookByTitle como una variable local de tipo ref, los cambios que el autor de la llamada realiza en el valor devuelto se reflejan en el objeto BookCollection, como se muestra en el ejemplo siguiente.

var bc = new BookCollection();
bc.ListBooks();

ref var book = ref bc.GetBookByTitle("Call of the Wild, The");
if (book != null)
    book = new Book { Title = "Republic, The", Author = "Plato" };
bc.ListBooks();
// The example displays the following output:
//       Call of the Wild, The, by Jack London
//       Tale of Two Cities, A, by Charles Dickens
//
//       Republic, The, by Plato
//       Tale of Two Cities, A, by Charles Dickens

Especificación del lenguaje C#

Para obtener más información, consulte la Especificación del lenguaje C#. La especificación del lenguaje es la fuente definitiva de la sintaxis y el uso de C#.

Vea también