Span<T> 是否指向固定大小的缓冲区而没有固定表达式?

Gam*_*nis 7 c# .net-core c#-7.3 .net-core-2.1

我正在使用 .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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我知道 Span 目前能够通过垃圾回收跟踪托管数组,所以我没有看到任何阻止它以类似方式跟踪固定缓冲区的情况。

如果不可能,如果我确实使用了这样的固定语句会发生什么:

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

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

如果结构被包裹在堆上的对象或类中,垃圾收集器会成为问题吗?

Gam*_*nis 5

因此,我以 ILSpy'ing .NET Assemblies 的形式进行了一些研究,并在 .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
        }
    }
Run Code Online (Sandbox Code Playgroud)

我从对 IL 的研究中学到的东西,如果我没有正确解释这一点,请原谅我:

.NET Core 的 2 字段跨度:

//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;
}
Run Code Online (Sandbox Code Playgroud)

.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;
}
Run Code Online (Sandbox Code Playgroud)

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

因此,我的问题的答案是,是的,我可以使用或不使用固定语句将 Span 指向固定缓冲区,但是在不使用 .NET Core 的 2 字段 Span 模型时这样做很危险。如果我对 .NET Framework 的当前 Span 模型有误,请纠正我。

  • 这是在我知道 source.dot.net 之前的事情。在我知道“Span&lt;T&gt; MemoryMarshal.CreateSpan&lt;T&gt;(ref T reference, int length)”之前 (2认同)