在C# 7.0之前,值类型的赋值和传递默认都是值拷贝,即使你想要修改某个值类型的原始实例,往往也只能拿到它的拷贝副本,无法直接操作原始数据。C# 7.0引入的ref locals(引用局部变量)和ref returns(引用返回)特性彻底改变了这一现状,让值类型也能拥有类似引用类型的操作体验。

基本概念说明
ref locals指的是用ref关键字声明的局部变量,它不会存储值类型的拷贝,而是存储值类型实例的引用,对这个变量的修改会直接反映到原始实例上。
ref returns指的是方法可以返回某个变量的引用,而不是返回值的拷贝,调用方拿到这个引用后可以直接操作原始数据。
ref locals的使用方法
声明ref局部变量需要遵循两个要点:一是变量必须被初始化,二是初始化表达式必须是一个可寻址的变量(不能是字面量或者临时计算结果)。我们来看一个简单示例:
struct Point
{
public int X;
public int Y;
public Point(int x, int y)
{
X = x;
Y = y;
}
}
class Program
{
static void Main()
{
Point p = new Point(1, 2);
// 声明ref局部变量,指向p的引用
ref Point pRef = ref p;
// 修改ref变量,会同步修改原始p
pRef.X = 10;
Console.WriteLine(p.X); // 输出10
// 错误示例:不能将字面量赋值给ref局部变量
// ref int num = ref 5; // 编译报错
}
}ref returns的使用方法
要让方法支持返回引用,需要在返回类型前加ref关键字,同时return后面的表达式也必须是可寻址的变量。下面示例展示了一个返回数组元素引用的方法:
class ArrayHelper
{
// 返回数组中指定索引元素的引用
public static ref int GetElementRef(int[] arr, int index)
{
// 返回数组元素的引用,而不是拷贝
return ref arr[index];
}
}
class Program
{
static void Main()
{
int[] nums = { 1, 2, 3, 4 };
// 拿到数组索引1位置的引用
ref int numRef = ref ArrayHelper.GetElementRef(nums, 1);
// 修改引用,会直接修改数组中的元素
numRef = 100;
Console.WriteLine(nums[1]); // 输出100
}
}使用限制和注意事项
ref locals和ref returns虽然强大,但也有不少使用限制,需要开发者特别注意:
- ref局部变量不能重新赋值指向另一个变量,一旦初始化后就不能改变引用的目标
- 不能将ref返回值存储在普通的局部变量中,必须用ref局部变量接收
- ref返回的生命周期不能超过被引用变量的生命周期,否则会出现悬垂引用问题
- ref参数和ref返回值不能用于异步方法、迭代器方法,也不能是返回值为void的方法
适用场景说明
这两个特性最适合用在以下场景:
- 频繁操作大的值类型结构体,避免多次值拷贝带来的性能损耗
- 需要直接修改结构体内部字段,而不想通过方法返回值覆盖整个实例
- 封装数组、集合的访问逻辑,让调用方可以直接操作集合中的元素
在实际开发中,不需要所有值类型操作都用ref相关特性,只有在确实需要避免拷贝或者需要直接修改原始实例的场景下使用,才能让代码既高效又易读。
| 特性 | 作用 | 核心限制 |
|---|---|---|
| ref locals | 存储值类型的引用,避免拷贝 | 必须初始化,不能重新指向其他变量 |
| ref returns | 返回变量的引用而非拷贝 | 返回的表达式必须可寻址,生命周期不能超过被引用变量 |
注意:ref locals和ref returns并不会改变值类型的本质,只是提供了操作值类型的引用方式,和引用类型还是有本质区别,不要混淆两者的概念。
C#ref_localsref_returns值类型引用语义修改时间:2026-05-29 15:24:23