Возможен ли интервал<T>, указывающий на фиксированные размерные буферы без фиксированного выражения?
Я использую.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 в настоящее время может отслеживать управляемый массив с помощью сборщиков мусора, поэтому я не вижу ничего, что мешало бы ему отслеживать фиксированные буферы подобным образом.
Если это невозможно, что произойдет, если я использую фиксированное выражение, например:
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 ответ
Итак, я провел некоторые исследования в виде сборок.NET ILSpy, а также провел тестирование на.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 полевых диапазона:
//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 использует 2-х полевую модель Span. Из-за.NET Framework, использующего модель 3 полей, его указатель не будет обновлен. Причина? Span<T>(void* pointer, int length)
конструктор (который я использую для этого) для 3 поля диапазона устанавливает _byteOffset
поле с pointer
аргумент. Указатель в диапазоне 3 полей, который будет обновлен GC, является _pinnable
поле. С 2 полями Span они одинаковы.
Итак, ответ на мой вопрос: да, у меня может быть точка Span для фиксированного буфера с фиксированным оператором или без него, но это опасно делать вообще, если не используется 2-полевая Span Model.NET Core. Поправьте меня, если я ошибаюсь в текущей модели Span.NET Framework.