C# 结构体在数组中传递时保持引用

2022年10月25日 1392点热度 1人点赞 0条评论
内容纲要

因为结构体或者值类型在传递时,是值复制,导致传递后修改其值,原先的值不会发生改变。
即使使用 ref 做参数,也没法改变传递数组是值复制的问题。
阅读 .NET 源码是发现了 .NET 6 的一个 API,可以很方便完成这个任务,让结构体像引用类型一样。

using System.Runtime.InteropServices;

public class Progarm
{
     static void Main()
    {
        var a = new A[100];
        var span = a.AsSpan();
        ref A c = ref MemoryMarshal.GetReference(span);
        c.B = "1111";

    }
}
public struct A
{
    public string B
    {
        get; set;
    }
}

file

如果要获取指定位置的指针,可以使用:

        ref A d = ref Unsafe.Add(ref c, 1);
     static void Main()
    {
        var a = new A[100];
        var span = a.AsSpan();
        ref A c = ref MemoryMarshal.GetReference(span);
        c.B = "0";
        ref A d = ref Unsafe.Add(ref c, 1);
        d.B = "1";
        ref A e = ref Unsafe.Add(ref c, 2);
        e.B = "2";
    }

这样可以访问基于 c 开始的,指定位置的值。

.NET 源代码就使用这种方式反转结构体数组(Span<T>) 的内容:

        public static void Reverse<T>(this Span<T> span)
        {
            ref T p = ref MemoryMarshal.GetReference(span);
            int i = 0;
            int j = span.Length - 1;
            while (i < j)
            {
                T temp = Unsafe.Add(ref p, i);
                Unsafe.Add(ref p, i) = Unsafe.Add(ref p, j);
                Unsafe.Add(ref p, j) = temp;
                i++;
                j--;
            }
        }

通过 Span 获取结构体指针的原理:

        public static ref T GetReference<T>(Span<T> span)
        {
            if (span.Pinnable == null)
                unsafe { return ref Unsafe.AsRef<T>(span.ByteOffset.ToPointer()); }
            else
                return ref Unsafe.AddByteOffset<T>(ref span.Pinnable.Data, span.ByteOffset);
        }

源码位置:System.Runtime.InteropServicesMemoryMarshal

痴者工良

高级程序员劝退师

文章评论