如何在避免不必要的复制的同时从List <t>获取Span <t>?

时间:2018-09-24 10:00:48

标签: c# memory

我有一个List<T>,其中包含一些数据。我想将其传递给接受ReadOnlySpan<T>的函数。

List<T> items = GetListOfItems();
// ...
void Consume<T>(ReadOnlySpan<T> buffer)
// ...
Consume(items??);

在此特定情况下,T为byte,但这并不重要。

我知道我可以在列表上使用.ToArray(),并构造一个跨度,例如

Consume(new ReadOnlySpan<T>(items.ToArray()));

但是,这会创建(看似)不必要的项目副本。有什么方法可以直接从列表中获取跨度吗? List<T>是根据幕后的T[]实现的,因此从理论上讲这是可能的,但在实践中还不尽人意?

3 个答案:

答案 0 :(得分:14)

在.Net 5.0中,可以使用CollectionsMarshal.AsSpan()sourceGitHub isue)来获取List<T>的基础数组作为Span<T>。 / p>

请记住,这仍然是不安全的:如果List<T>重新分配了数组,则先前由Span<T>返回的CollectionsMarshal.AsSpan不会反映对{{1} }。 (这就是为什么该方法隐藏在List<T>类中的原因。)

答案 1 :(得分:2)

感谢所有注释,这些注释解释了没有实际方法,以及如何在List内部公开内部Array会导致不良行为和跨度中断。

我最终将代码重构为不使用列表,而只是首先产生了跨度。

void Consume<T>(ReadOnlySpan<T> buffer)
// ...

var buffer = new T[512]; 
int itemCount = ProduceListOfItems(buffer); // produce now writes into the buffer

Consume(new ReadOnlySpan<T>(buffer, 0, itemCount);

我希望做出一次过度分配缓冲区的显式权衡,以避免以后再制作额外的副本。

我可以在我的特定情况下执行此操作,因为我知道项目数将达到最大上限,并且稍微过度分配没有什么大不了的,但是这里似乎没有一个概括,也没有会被添加,因为这样做很危险。

一如既往,软件性能是权衡(希望如此)的艺术。

答案 2 :(得分:1)

您可以编写自己的CustomList<T>来公开基础数组。然后在用户代码上正确使用此类。

尤其是CustomList<T>不会知道您可以从基础支持数组中获取的任何Span<T>。使用Span<T>后,您不应使列表做任何事情来创建新数组或在旧数组中创建未定义的数据。

C ++标准库允许用户代码获取指向vector<T>后备存储的直接指针。他们记录了可以安全使用的条件。调整大小会使其变得不安全。例如

.NET本身使用MemoryStream做类似的事情。此类允许您访问基础缓冲区,并且确实可能​​进行不安全的操作。