如何在C#中实现值语义的固定大小数组(模拟Fortran行为)
在C#中模拟Fortran结构体的固定数组按值语义
要实现Fortran中结构体内部固定数组的连续内存值拷贝行为(结构体赋值时数组随结构体整块复制,而非引用传递),可以通过以下两种高效方案实现,无需手动编写递归Clone逻辑:
方案一:Unsafe 固定大小缓冲区(推荐,支持任意长度非托管值类型数组)
该方案利用C#的fixed关键字将数组元素直接嵌入结构体内存,结构体赋值时CLR会自动整块拷贝内存,性能与Fortran原生行为一致。仅适用于非托管值类型(包括自定义的无引用成员的结构体)。
步骤与示例
- 定义非托管值类型(确保无引用成员):
// 自定义非托管值类型,满足unmanaged约束 public struct MyValue : unmanaged { public int X; public float Y; public override string ToString() => $"X={X}, Y={Y:F1}"; }
- 定义包含固定大小缓冲区的结构体:
[StructLayout(LayoutKind.Sequential)] public unsafe struct MyType { // 固定大小的MyValue数组,直接嵌入结构体内存 public fixed MyValue Xs[3]; // 初始化方法,方便批量赋值 public static MyType Create(MyValue[] values) { if (values.Length != 3) throw new ArgumentException("必须传入3个元素"); MyType result = default; fixed (MyValue* ptr = result.Xs) { for (int i = 0; i < 3; i++) { ptr[i] = values[i]; } } return result; } // 重写ToString用于输出验证 public override string ToString() { fixed (MyValue* ptr = Xs) { return $"[{ptr[0]}, {ptr[1]}, {ptr[2]}]"; } } }
- 测试按值语义:
var foo = MyType.Create(new[] { new MyValue { X = 1, Y = 1.0f }, new MyValue { X = 2, Y = 2.0f }, new MyValue { X = 3, Y = 3.0f } }); var bar = foo; // 结构体整块内存拷贝,Xs所有元素被复制 // 修改foo的第一个元素 fixed (MyValue* ptr = foo.Xs) { ptr[0].X = 0; ptr[0].Y = 0.0f; } Console.WriteLine(foo); // 输出: [X=0, Y=0.0, X=2, Y=2.0, X=3, Y=3.0] Console.WriteLine(bar); // 输出: [X=1, Y=1.0, X=2, Y=2.0, X=3, Y=3.0]
方案二:安全内存视图(无需Unsafe,适合小长度数组)
如果不想使用unsafe代码,可以通过Span<T>与MemoryMarshal将结构体的连续字段视为数组视图,结构体赋值时仍会拷贝所有字段,实现按值语义。缺点是数组长度固定时需要手动声明对应数量的字段。
示例代码
[StructLayout(LayoutKind.Sequential)] public struct MyType { // 手动声明对应数量的字段,模拟固定数组 private MyValue _x0; private MyValue _x1; private MyValue _x2; // 通过Span提供数组式访问 public Span<MyValue> Xs => MemoryMarshal.CreateSpan(ref _x0, 3); // 初始化方法 public static MyType Create(MyValue[] values) { if (values.Length != 3) throw new ArgumentException("必须传入3个元素"); MyType result = default; result.Xs.CopyFrom(values); return result; } public override string ToString() { return $"[{Xs[0]}, {Xs[1]}, {Xs[2]}]"; } } // MyValue结构体定义同上
测试逻辑与方案一完全一致,赋值bar = foo时会拷贝_x0/_x1/_x2三个字段,修改foo的Xs不会影响bar。
关键注意事项
- 两种方案仅支持非托管值类型(无引用成员的结构体/基本类型),如果必须处理包含引用类型的数组,无法实现整块内存拷贝,只能手动实现Clone逻辑(但这不符合你的性能需求)。
- 方案一的
fixed缓冲区需要开启项目的"允许不安全代码"选项。 - 两种方案均能实现结构体赋值时的整块内存拷贝,性能远高于手动递归Clone。
内容的提问来源于stack exchange,提问作者Adam
相关产品推荐
相关产品推荐

