В проекте .NET Core 3.0 у меня есть интерфейс, который возвращает Span<byte>. Это работает для большого набора классов, за исключением одной конкретной реализации, которая может генерировать свои данные на лету (из-за того, что они не кэшируются).

Реализация выглядит так:

public Span<byte> Data => CompileBytes();

Где это было бы что-то вроде (это абстрактный код, но довольно близко к варианту использования)

public byte[] CompileBytes()
{
    using (MemoryStream stream = new MemoryStream())
    {
        foreach (IDataSource data in DataSources)
            stream.Write(data.ByteArray);
        return stream.ToArray();
    }
}

Я искал в Интернете, есть ли гарантия, что это безопасно, но не нашел.

Меня беспокоит то, что Span - это очень тонкий слой вокруг данных, которые GC проигнорирует, так что GC предполагает, что мы не позволим диапазону пережить базовый буфер, а созданный временный массив байтов в конечном итоге получит GC 'd, что означает, что у меня потенциально может быть бомба замедленного действия в моих руках, если она по какой-то причине выполняет сборку мусора, в то время как кто-то другой код использует диапазон. Так ли это? Могу ли я вернуть Span<> для временного объекта и все будет в порядке (при условии, что он правильно используется, оставаясь в пределах диапазона)?

Определение, похоже, зависит от реализации, поэтому я не могу понять, с моими ограниченными знаниями, придерживается ли оно ссылки или нет ... потому что если это так, то я в безопасности, и на мой вопрос дан ответ.

В MSDN написано «безопасно для памяти», но я не уверен в точных характеристиках, по которым они определяют безопасность для памяти, и охватывает ли это мое определение. Таким образом, если это так, то на этот вопрос будет дан ответ.

Я не использую код unsafe.

0
Water 9 Окт 2019 в 22:30

1 ответ

Лучший ответ

Наличие ссылки на управляемый массив в Span<T> безопасно, даже если это единственная ссылка на него.
Как описано в статье Все о Span: изучение новой основы .NET , Span<T> использует особый способ хранения этих ссылок, ByReference<T>, который реализован как внутренняя функция JIT.

Цитата из связанной статьи (раздел Как реализовано Span<T>? ):

Span<T> на самом деле написан для использования специального внутреннего типа во время выполнения, который рассматривается как встроенный JIT, при этом JIT генерирует для него эквивалент поля ref T

И раздел Что такое Memory<T> и зачем он вам нужен?

Span<T> является типом, подобным ref, поскольку он содержит поле ref, а поля ref могут относиться не только к началу объектов, таких как массивы, но и к их середине [...] Эти ссылки называются внутренними указатели, и их отслеживание - относительно дорогая операция для сборщика мусора среды выполнения .NET.

Последняя часть этой цитаты поясняет, что ссылка, хранящаяся в Span<T>, действительно отслеживается сборщиком мусора, поэтому он не будет очищать память, на которую все еще ссылаются.

2
UnholySheep 9 Окт 2019 в 20:55