You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C#中实现值语义的固定大小数组(模拟Fortran行为)

在C#中模拟Fortran结构体的固定数组按值语义

要实现Fortran中结构体内部固定数组的连续内存值拷贝行为(结构体赋值时数组随结构体整块复制,而非引用传递),可以通过以下两种高效方案实现,无需手动编写递归Clone逻辑:

方案一:Unsafe 固定大小缓冲区(推荐,支持任意长度非托管值类型数组)

该方案利用C#的fixed关键字将数组元素直接嵌入结构体内存,结构体赋值时CLR会自动整块拷贝内存,性能与Fortran原生行为一致。仅适用于非托管值类型(包括自定义的无引用成员的结构体)。

步骤与示例

  1. 定义非托管值类型(确保无引用成员):
// 自定义非托管值类型,满足unmanaged约束
public struct MyValue : unmanaged
{
    public int X;
    public float Y;

    public override string ToString() => $"X={X}, Y={Y:F1}";
}
  1. 定义包含固定大小缓冲区的结构体:
[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]}]";
        }
    }
}
  1. 测试按值语义:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 03:50:06