是否可能存在指向固定大小缓冲区的 Span<T>,但没有固定表达式?

10

我正在使用.NET Core 2.1和语言标准7.3。我希望引用一个固定的缓冲区而不获取指向它的指针。目前是否可能?

public unsafe struct InteropStruct
{
    private fixed byte dataField[32];

    public Span<byte> Data
    {
        get
        {
            //return a span referencing the private field without a fixed statement
        }
    }
}

我知道Span目前可以跟踪托管数组在垃圾回收期间的情况,因此我认为没有什么阻止它以类似的方式跟踪固定缓冲区。

如果不可能,那么如果我像下面这样使用fixed语句会发生什么:

public unsafe struct InteropStruct
{
    private fixed byte dataField[32];

    public Span<byte> Data
    {
        get
        {
            fixed (byte* ptr = dataField)
            {
                return new Span<byte>(ptr, 32);
            }
        }
    }
}

如果在堆上将结构体包装在对象或类中,那么垃圾收集器会成为一个问题吗?

1个回答

7

那么,我通过使用ILSpy检查.NET程序集并在.NET Core 2.1上进行一些测试进行了一些研究。我的测试结果如下:

    interface ITest
    {
        Span<byte> Data { get; }
    }

    unsafe struct TestStruct : ITest
    {
        fixed byte dataField[8];

        public Span<byte> Data
        {
            get
            {
                //Unsafe.AsPointer() to avoid the fixed expression :-)
                return new Span<byte>(Unsafe.AsPointer(ref dataField[0]), 8);
            }
        }
    }

    class Program
    {
        //Note: This test is done in Debug mode to make sure the string allocation isn't ommited
        static void Main(string[] args)
        {
            new string('c', 200);

            //Boxes the struct onto the heap.
            //The object is allocated after the string to ensure it will be moved during GC compacting
            ITest HeapAlloc = new TestStruct();

            Span<byte> span1, span2;

            span1 = HeapAlloc.Data; //Creates span to old location

            GC.Collect(2, GCCollectionMode.Forced, true, true); //Force a compacting garbage collection

            span2 = HeapAlloc.Data; //Creates span to new location

            //Ensures that if the pointer to span1 wasn't updated, that there wouldn't be heap corruption
            //Write to Span2
            span2[0] = 5;
            //Read from Span1
            Console.WriteLine(span1[0] == 5); //Prints true in .NET Core 2.1, span1's pointer is updated
        }
    }

请原谅我如果我没有解释清楚,以下是我从对IL的研究中学到的内容:

.NET Core的2个字段Span:

//Note, this is not the complete declaration, just the fields
public ref readonly struct Span<T>
{
    internal readonly ByReference<T> _pointer;
    private readonly int _length;
}

.NET Framework的3个字段跨度:

//Same note as 2 Field Span
public ref readonly struct Span<T>
{
    private readonly Pinnable<T> _pinnable;
    private readonly IntPtr _byteOffset;
    private readonly int _length;
}

.Net Core正在使用Span的2字段模型。由于.NET Framework使用3字段模型,它的指针不会被更新。原因是?3字段span的Span<T>(void* pointer, int length)构造函数(我正在使用此函数)将_byteOffset字段设置为pointer参数。由GC更新的3字段span中的指针是_pinnable字段。而对于2字段Span,它们是相同的。

所以,我的问题的答案是,是的,我可以让Span指向一个固定的缓冲区,无论是否使用fixed语句,但当不使用.NET Core的2字段Span模型时,这样做非常危险。如果我对.NET Framework当前的Span模型有误,请纠正我。


完全没有必要使用ILSpy。.NET是开源的(即使.NET Framework也有其源代码公开可用),只需使用https://source.dot.net - ABPerson
2
这是在我还不知道source.dot.net之前。也是在我还不知道Span<T> MemoryMarshal.CreateSpan<T>(ref T reference, int length)之前。 - Gamma_Draconis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接