ref(C# 参考)

ref 关键字指示按引用传递值。 它用在四种不同的上下文中:

  • 在方法签名和方法调用中,按引用将参数传递给方法。 有关详细信息,请参阅按引用传递参数
  • 在方法签名中,按引用将值返回给调用方。 有关详细信息,请参阅引用返回值
  • 在成员正文中,指示引用返回值是否作为调用方欲修改的引用被存储在本地。 或指示局部变量按引用访问另一个值。 有关详细信息,请参阅 Ref 局部变量
  • struct 声明中,声明 ref structreadonly ref struct。 有关详细信息,请参阅结构类型一文中的 ref 结构一节。

按引用传递参数

在方法的参数列表中使用 ref 关键字时,它指示参数按引用传递,而非按值传递。 ref 关键字让形参成为实参的别名,这必须是变量。 换而言之,对形参执行的任何操作都是对实参执行的。

例如,假设调用方传递了局部变量表达式或数组元素访问表达式。 然后,调用的方法可以替换 ref 参数引用的对象。 在这种情况下,调用方的局部变量或数组元素会在该方法返回时引用新的对象。

备注

不要混淆通过引用传递的概念与引用类型的概念。 这两种概念是不同的。 无论方法参数是值类型还是引用类型,均可由 ref 修改。 当通过引用传递时,不会对值类型装箱。

若要使用 ref 参数,方法定义和调用方法均必须显式使用 ref 关键字,如下面的示例所示。 (除了在进行 COM 调用时,调用方法可忽略 ref。)

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

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

传递到 refin 形参的实参必须先经过初始化,然后才能传递。 该要求与 out 形参不同,在传递之前,不需要显式初始化该形参的实参。

类的成员不能具有仅在 refinout 方面不同的签名。 如果类型的两个成员之间的唯一区别在于其中一个具有 ref 参数,而另一个具有 outin 参数,则会发生编译器错误。 例如,以下代码将不会编译。

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

但是,当一个方法具有 refinout 参数,另一个方法具有值传递的参数时,则可以重载方法,如下面的示例所示。

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

在其他要求签名匹配的情况下(如隐藏或重写),inrefout 是签名的一部分,相互之间不匹配。

属性不是变量。 它们是方法,不能传递到 ref 参数。

不能将 refinout 关键字用于以下几种方法:

  • 异步方法,通过使用 async 修饰符定义。
  • 迭代器方法,包括 yield returnyield 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 返回值)是由方法按引用向调用方返回的值。 即是说,调用方可以修改方法所返回的值,此更改反映在调用方法中的对象的状态中。

使用 ref 关键字来定义引用返回值:

  • 在方法签名中。 例如,下列方法签名指示 GetCurrentPrice 方法按引用返回了 Decimal 值。
public ref decimal GetCurrentPrice()
  • return 标记和方法的 return 语句中返回的变量之间。 例如:
return ref DecimalArray[0];

为方便调用方修改对象的状态,引用返回值必须存储在被显式定义为 ref 局部变量的变量中。

下面是一个更完整的 ref 返回示例,同时显示方法签名和方法主体。

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 变量中来避免复制该值。

有关示例,请参阅 ref 返回值和 ref 局部变量示例

ref 局部变量

ref 局部变量用于指代使用 return ref 返回的值。 无法将 ref 局部变量初始化为非 ref 返回值。 也就是说,初始化的右侧必须为引用。 任何对 ref 本地变量值的修改都将反映在对象的状态中,该对象的方法按引用返回值。

可在以下两个位置使用 ref 关键字来定义 ref 局部变量:

  • 在变量声明之前。
  • 紧接在调用按引用返回值的方法之前。

例如,下列语句定义名为 GetEstimatedValue 的方法返回的 ref 局部变量值:

ref decimal estValue = ref Building.GetEstimatedValue();

可通过相同方式按引用访问值。 在某些情况下,按引用访问值可避免潜在的高开销复制操作,从而提高性能。 例如,以下语句显示如何定义一个用于引用值的 ref 局部变量。

ref VeryLargeStruct reflocal = ref veryLargeStruct;

在这两个示例中,必须在两个位置同时使用 ref 关键字,否则编译器将生成错误 CS8172:“无法使用值对按引用变量进行初始化”。

从 C# 7.3 开始,foreach 语句的迭代变量可以是 ref 局部变量,也可以是 ref readonly 局部变量。 有关详细信息,请参阅 foreach 语句一文。

此外,从 C# 7.3 开始,可以使用 ref 赋值运算符重新分配 ref local 或 ref readonly local 变量。

Ref readonly 局部变量

Ref readonly 局部变量用于指代在其签名中具有 ref readonly 并使用 return ref 的方法或属性返回的值。 ref readonly 变量将 ref 局部变量的属性与 readonly 变量结合使用:它是所分配到的存储的别名,且无法修改。

ref 返回值和 ref 局部变量示例

下列示例定义一个具有两个 String 字段(TitleAuthor)的 Book 类。 还定义包含 Book 对象的专用数组的 BookCollection 类。 通过调用 GetBookByTitle 方法,可按引用返回个别 book 对象。


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 方法所返回的值存储为 ref 局部变量时,调用方对返回值所做的更改将反映在 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

C# 语言规范

有关详细信息,请参阅 C# 语言规范。 该语言规范是 C# 语法和用法的权威资料。

请参阅