ref (Справочник по C#)

Ключевое ref слово указывает, что переменная является ссылкой или псевдонимом для другого объекта. Он используется в пяти разных контекстах:

  • В сигнатуре и вызове метода для передачи аргумента в метод по ссылке. Дополнительные сведения см. в статье о передаче аргументов по ссылке.
  • В сигнатуре метода для возврата значения вызывающему объекту по ссылке. Дополнительные сведения см. в разделе Значения, возвращаемые по ссылке.
  • В теле элемента для указания на то, что возвращаемые ссылочные значения хранятся локально в виде ссылки, которая может быть изменена вызывающим объектом. Или чтобы указать, что локальная переменная обращается к другому значению по ссылке. Дополнительные сведения см. в статье о ссылочных локальных переменных.
  • В объявлении struct, чтобы объявить ref struct или readonly ref struct. Дополнительные сведения см. в ref struct статье.
  • В объявлении ref struct , чтобы объявить, что поле является ссылкой. См. статью оref поле.

Передача аргументов по ссылке

При использовании в списке параметров метода ключевое слово ref указывает на то, что аргумент передается по ссылке, а не по значению. Ключевое слово ref позволяет превратить этот формальный параметр в псевдоним для аргумента, который должен представлять собой переменную. Другими словами, любая операция в параметре осуществляется с аргументом.

Для примера предположим, что вызывающая сторона передает выражение локальной переменной или выражение доступа к элементу массива. Тогда вызываемый метод может заменить объект, на который ссылается параметр ref. В этом случае или элемент массива или локальная переменная вызывающей стороны будет ссылаться на новый объект, когда метод возвращает значение.

Примечание

Не путайте понятие передачи по ссылке с понятием ссылочных типов. Эти два понятия не совпадают. Параметр метода может быть изменен с помощью ref независимо от того, принадлежит ли он к типу значения или ссылочному типу. При передаче по ссылке упаковка-преобразование типа значения не производится.

Для использования параметра ref и при определении метода, и при вызове метода следует явно использовать ключевое слово ref, как показано в следующем примере. (За исключением того, что вызывающий метод может опускать ref при вызове COM.)

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

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

Перед передачей аргумент, передаваемый в ref объект или in параметр, необходимо инициализировать. Это требование отличается от параметров out , аргументы которых не должны быть явно инициализированы перед их передачей.

Члены класса не могут иметь сигнатуры, отличающихся только ref, in или out. Если единственное различие между двумя членами типа состоит в том, что один из них имеет параметр ref, а второй — параметр out или in, возникает ошибка компилятора. Например, следующий код не будет компилироваться.

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) { }
}

Но методы можно перегружать, если один метод имеет параметр ref, in или out, а другой — передаваемый по значению параметр, как показано в приведенном ниже примере.

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

В других ситуациях, требующих соответствия сигнатур, таких как скрытие или переопределение, in, ref и out являются частью сигнатуры и не соответствуют друг другу.

Свойства не являются переменными. Они методы и не могут передаваться в ref параметры.

Ключевые слова ref, in и out запрещено использовать для следующих типов методов.

  • Асинхронные методы, которые определяются с помощью модификатора async.
  • Методы итератора, которые включают оператор yield return или yield break.

Методы расширения также имеют ряд ограничений при использовании этих ключевых слов:

  • Ключевое out слово нельзя использовать для первого аргумента метода расширения.
  • Ключевое ref слово нельзя использовать для первого аргумента метода расширения, если аргумент не является структурой или универсальным типом, не ограниченным как структурой.
  • Ключевое in слово нельзя использовать, если только первый аргумент не является структурой. Ключевое in слово не может использоваться для любого универсального типа, даже если ограничение должно быть структурой.

Передача аргументов по ссылке. Пример

В предыдущих примерах типы значений передаются по ссылке. Также можно использовать ключевое слово ref для передачи ссылочных типов по ссылке. Передача ссылочного типа по ссылке позволяет вызываемому методу заменять объект, на который указывает ссылочный параметр в вызывающем объекте. Место хранения объекта передается методу в качестве значения ссылочного параметра. Если изменить место хранения параметра (с указанием на новый объект), необходимо изменить место хранения, на который ссылается вызывающий объект. В следующем примере экземпляр ссылочного типа передается как параметр 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

Дополнительные сведения о передаче ссылочных типов по значению и по ссылке см. в разделе Передача параметров ссылочного типа.

Возвращаемые ссылочные значения

Возвращаемые ссылочные значения — это значения, которые метод возвращает вызывающему объекту по ссылке. То есть вызывающий объект может изменить значение, возвращаемое методом, и это изменение отражается в состоянии объекта в вызываемом методе.

Возвращаемое ссылочное значение определяется с помощью ключевого слова ref:

  • В сигнатуре метода. Например, следующая сигнатура указывает, что метод GetCurrentPrice возвращает значение Decimal по ссылке.
public ref decimal GetCurrentPrice()
  • Между токеном return и переменной, возвращенной в инструкции return в методе. Пример:
return ref DecimalArray[0];

Чтобы вызывающий объект имел возможность изменять состояние объекта, возвращаемое ссылочное значение должно храниться в переменной, которая явно определена как ссылочная локальная переменная.

Ниже приведен более полный пример возвращаемого ссылочного значения, в котором показаны сигнатура и тело метода.

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");
}

Вызываемый метод может также объявить возвращаемое значение ref readonly, чтобы вернуть значение по ссылке, и запретить вызывающему коду изменять возвращаемое значение. Вызывающий метод может обойтись без копирования возвращаемого значения, сохраняя его в локальной переменной ref readonly.

Пример см. в разделе Пример использования возвращаемых ссылочных значений и ссылочных локальных переменных.

Ссылочные локальные переменные

Ссылочная локальная переменная задает ссылку на значения, возвращаемые с помощью return ref. Локальную переменную ссылок нельзя инициализировать в возвращаемое значение, отличное от ссылок. Другими словами, правая часть инициализации должна быть ссылкой. Любые изменения в значении ссылочной локальной переменной отражаются в состоянии объекта, метод которого возвращает значение по ссылке.

Чтобы определить локальное ссылочное значение, ключевое слово ref следует указать в двух местах:

  • перед объявлением переменной;
  • сразу после вызова метода, который возвращает значение по ссылке.

Например, следующая инструкция определяет значение ссылочной локальной переменной, которое возвращается методом GetEstimatedValue:

ref decimal estValue = ref Building.GetEstimatedValue();

Вы можете таким же образом обратиться к значению по ссылке. В некоторых случаях обращение к значению по ссылке повышает производительность, поскольку позволяет избежать потенциально затратной операции копирования. Например, следующая инструкция позволяет определить локальную ссылочную переменную, которая используется для создания ссылки на значение.

ref VeryLargeStruct reflocal = ref veryLargeStruct;

В обоих примерах ключевое ref слово должно использоваться в обоих местах, или компилятор создает ошибку CS8172 " Не удается инициализировать переменную по ссылке со значением".

Переменная итерации инструкции foreach может быть локальной или локальной переменной ref только для чтения. Дополнительные сведения см. в статье, посвященной инструкции foreach. Можно переназначить локальную переменную ref или ref только для чтения с помощью оператора присваивания ссылок.

Ссылочные локальные переменные только для чтения

Ссылочная локальная переменная только для чтения позволяет создать ссылку на значения, возвращаемые методом или свойством, которые имеют в сигнатуре модификатор ref readonly и используют return ref. Переменная ref readonly объединяет свойства локальной ref переменной с переменной readonly : это псевдоним хранилища, которому он назначен, и его нельзя изменить.

Пример использования возвращаемых ссылочных значений и ссылочных локальных переменных

В следующем примере определяется класс Book, содержащий два поля String: Title и Author. Также определяется класс BookCollection, который включает частный массив объектов Book. Отдельные объекты книг возвращаются по ссылке путем вызова метода 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();
    }
}

Если вызывающий объект сохраняет значение, возвращаемое методом GetBookByTitle, в качестве ссылочной локальной переменной, изменения, которые этот объект вносит в возвращаемое значение, отражаются в объекте BookCollection, как показано в следующем примере.

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

Поля ссылок

В ref struct типах можно объявить поля, которые являются полями ref . ref поля допустимы только в ref struct типах, чтобы ссылка не вылила объект, на который он ссылается. Эта функция включает следующие System.Span<T>типы:

public readonly ref struct Span<T>
{
    internal readonly ref T _reference;
    private readonly int _length;

    // Omitted for brevity...
}

Тип Span<T> сохраняет ссылку, через которую он обращается к последовательнным элементам. Ссылка позволяет объекту Span<T> избежать копирования хранилища, на которое он ссылается.

Спецификация языка C#

Дополнительные сведения см. в спецификации языка C#. Спецификация языка является предписывающим источником информации о синтаксисе и использовании языка C#.

См. также