ref(C# 参考)
ref
关键字表示变量是引用,或是另一个对象的别名。 它用在五种不同的上下文中:
- 在方法签名和方法调用中,按引用将参数传递给方法。 有关详细信息,请参阅按引用传递参数。
- 在方法签名中,按引用将值返回给调用方。 有关详细信息,请参阅引用返回值。
- 在成员正文中,指示引用返回值是否作为调用方欲修改的引用被存储在本地。 或指示局部变量按引用访问另一个值。 有关详细信息,请参阅 Ref 局部变量。
- 在
struct
声明中,声明ref struct
或readonly ref struct
。 有关详细信息,请参阅ref struct
一文。 - 在
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
传递到 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 返回值)是由方法按引用向调用方返回的值。 即是说,调用方可以修改方法所返回的值,此更改反映在所调用方法中的对象的状态中。
使用 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:“无法使用值对按引用变量进行初始化”。
foreach
语句的迭代变量可以是 ref 局部变量,也可以是 ref readonly 局部变量。 有关详细信息,请参阅 foreach 语句一文。 可以使用 ref 赋值运算符重新分配 ref local 或 ref readonly local 变量。
Ref readonly 局部变量
Ref readonly 局部变量用于指代在其签名中具有 ref readonly
并使用 return ref
的方法或属性返回的值。 ref readonly
变量将 ref
局部变量的属性与 readonly
变量结合使用:它是所分配到的存储的别名,且无法修改。
ref 返回值和 ref 局部变量示例
下列示例定义一个具有两个 String 字段(Title
和 Author
)的 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
ref 字段
在 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# 语法和用法的权威资料。