C#的ref struct是一种特殊的结构体类型,它的核心特性是只能被分配到栈上,无法被分配到堆内存中,这一设计主要是为了优化高频使用的临时对象的内存分配,减少垃圾回收的负担。ref struct的出现让开发者可以更精细地控制值类型的内存分配位置,在需要高性能的场景下非常实用。

ref struct的定义方式
定义ref struct的语法非常简单,只需要在struct关键字前加上ref修饰符即可,和普通结构体的定义方式差异很小。下面是一个基础的ref struct定义示例:
// 定义一个只能在栈上分配的ref struct
public ref struct StackOnlyStruct
{
// 结构体字段
public int Id;
public string Name;
// 结构体方法
public void PrintInfo()
{
Console.WriteLine($"Id: {Id}, Name: {Name}");
}
}
上述代码中,StackOnlyStruct就是一个标准的ref struct,它不能被分配到堆上,只能存在于栈内存中。
ref struct的使用规则
ref struct由于分配位置的限制,有一系列必须遵守的使用规则,违反这些规则会导致编译错误。
1. 不能被装箱到堆上
ref struct不允许进行装箱操作,因为装箱会把值类型放到堆上的对象中,这违反了ref struct只能栈分配的规则。下面的代码会编译失败:
public ref struct TestRefStruct
{
public int Value;
}
class Program
{
static void Main()
{
TestRefStruct s = new TestRefStruct();
// 错误:ref struct不能被装箱
object obj = s;
// 错误:也不能转换为object类型
Console.WriteLine(s);
}
}
2. 不能作为类的字段或属性
类的实例是分配在堆上的,如果ref struct作为类的字段,就意味着ref struct会被放到堆中,因此这是不被允许的。下面的定义会编译错误:
public ref struct RefStructA
{
public int X;
}
// 错误:类不能包含ref struct类型的字段
public class NormalClass
{
// 编译错误,RefStructA是ref struct,不能作为类的字段
public RefStructA Field;
}
但是如果结构体本身也是ref struct,那么它可以包含另一个ref struct作为字段,因为两个都只能在栈上:
public ref struct InnerRefStruct
{
public int A;
}
public ref struct OuterRefStruct
{
// 合法:ref struct可以包含另一个ref struct作为字段
public InnerRefStruct Inner;
public int B;
}
3. 不能实现接口
当ref struct实现接口时,它可能会被隐式装箱为接口类型(接口是引用类型,存在于堆上),因此ref struct不允许实现任何接口,下面的代码会编译失败:
public interface ITest
{
void Do();
}
// 错误:ref struct不能实现接口
public ref struct RefStructB : ITest
{
public void Do()
{
Console.WriteLine("Do something");
}
}
4. 不能作为泛型类型参数
泛型类型参数可能会被实例化到堆上的类型中,因此ref struct不能作为泛型方法的类型参数或者泛型类的类型参数,下面的使用方式会编译错误:
public ref struct RefStructC
{
public int Val;
}
class Program
{
// 错误:ref struct不能作为泛型类型参数
static void GenericMethod<T>() where T : RefStructC { }
static void Main()
{
// 错误:也不能传递ref struct作为泛型参数
GenericMethod<RefStructC>();
}
}
5. 可以在栈上作为局部变量或方法参数
ref struct最常见的使用场景就是作为方法的局部变量或者方法的参数,这两种场景下的分配都在栈上,符合ref struct的规则:
public ref struct Point
{
public int X;
public int Y;
public Point(int x, int y)
{
X = x;
Y = y;
}
public void Move(int dx, int dy)
{
X += dx;
Y += dy;
}
}
class Program
{
// ref struct可以作为方法参数
static void PrintPoint(Point p)
{
Console.WriteLine($"X: {p.X}, Y: {p.Y}");
}
static void Main()
{
// ref struct作为局部变量,分配在栈上
Point p = new Point(10, 20);
p.Move(5, 5);
PrintPoint(p);
}
}
ref struct的适用场景
ref struct最适合用在高频率创建、生命周期短的临时对象场景,比如:
- 高性能的数值计算类型,比如向量、矩阵等临时计算对象,避免频繁分配堆内存触发GC。
- Span<T>和ReadOnlySpan<T>本身就是ref struct类型,用于安全高效地访问连续内存区域,不需要额外的堆分配。
- 临时数据缓冲区,比如方法内部使用的临时数组包装类型,只在方法执行期间存在,不需要长期存储。
ref struct的注意事项
虽然ref struct能优化内存分配,但使用时需要注意不要超出它的生命周期范围。比如不能把ref struct的引用存储到堆上的对象中,也不能从方法返回ref struct的引用给堆上的变量。如果需要在多个方法间传递ref struct,可以使用ref参数传递引用,确保它始终在栈上:
public ref struct TempBuffer
{
public byte[] Data;
public int Length;
}
class Program
{
// 使用ref参数传递ref struct,避免拷贝和堆分配
static void ProcessBuffer(ref TempBuffer buffer)
{
for (int i = 0; i < buffer.Length; i++)
{
buffer.Data[i] = (byte)(buffer.Data[i] + 1);
}
}
static void Main()
{
byte[] arr = new byte[10];
TempBuffer buffer = new TempBuffer { Data = arr, Length = 10 };
ProcessBuffer(ref buffer);
}
}
只要严格遵循ref struct的使用规则,就可以安全地用它来优化程序的内存性能,减少不必要的垃圾回收开销。
ref_structC#栈分配结构体修改时间:2026-06-28 01:57:37