How can I get a Span<T> from a List<T> while avoiding needless copies?

人走茶凉 提交于 2021-01-20 16:25:23

问题


I have a List<T> containing some data. I would like to pass it to a function which accepts ReadOnlySpan<T>.

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

In this particular instance T is byte but it doesn't really matter.

I know I can use .ToArray() on the List, and the construct a span, e.g.

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

However this creates a (seemingly) unneccessary copy of the items. Is there any way to get a Span directly from a List? List<T> is implemented in terms of T[] behind the scenes, so in theory it's possible, but not as far as I can see in practice?


回答1:


In .Net 5.0, you can use CollectionsMarshal.AsSpan() (source, GitHub isue) to get the underlying array of a List<T> as a Span<T>.

Keep in mind that this is still unsafe: if the List<T> reallocates the array, the Span<T> previously returned by CollectionsMarshal.AsSpan won't reflect any further changes to the List<T>. (Which is why the method is hidden in the System.Runtime.InteropServices.CollectionsMarshal class.)




回答2:


Thanks for all the comments explaining that there's no actual way to do it and how exposing the internal Array inside List could lead to bad behaviour and a broken span.

I ended up refactoring my code not to use a list and just produce spans in the first place.

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);

I'm chosing to make the explicit tradeoff of over-allocating the buffer once to avoid making an extra copy later on.

I can do this in my specific case because I know there will a maximum upper bound on the item count, and over-allocating slightly isn't a big deal, however there doesn't appear to be a generalisation here, nor would one ever get added as it would be dangerous.

As always, software performance is the art of making (hopefully favorable) trade-offs.




回答3:


You can write your own CustomList<T> that exposes the underlying array. It is then on user code to use this class correctly.

In particular the CustomList<T> will not be aware of any Span<T> that you can obtain from the underlying backing array. After taking a Span<T> you should not make the list do anything to create a new array or create undefined data in the old array.

The C++ standard library allows user code to obtain direct pointers into vector<T> backing storage. They document the conditions under which this is safe. Resizing makes it unsafe for example.

.NET itself does something like this with MemoryStream. This class allows you access to the underlying buffer and indeed unsafe operations are possible.



来源:https://stackoverflow.com/questions/52476832/how-can-i-get-a-spant-from-a-listt-while-avoiding-needless-copies

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!