-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
API for inserting a Span<T> into a List<T> efficiently #1530
Comments
Do you have a scenario to help motivate this addition (maybe example code today where the workarounds are noticeably inefficient)? Are there other List APIs that fall into this category (for instance what about the Similarly, we have the CopyTo that takes arrays. Presumably, there was a reason such APIs didn't meet the bar during the first round:
|
Just to add to the List<T> of workarounds 😛 public static class ListMarshal
{
static readonly IntPtr _items = Marshal.OffsetOf(typeof(List<>), "_items");
static readonly IntPtr _size = Marshal.OffsetOf(typeof(List<>), "_size");
static readonly IntPtr _version = Marshal.OffsetOf(typeof(List<>), "_version");
class RawData
{
public byte Data;
}
static ref TField GetField<T, TField>(List<T> list, IntPtr offset)
{
var raw = Unsafe.As<RawData>(list);
return ref Unsafe.As<byte, TField>(ref Unsafe.Add(ref raw.Data, offset));
}
public static T[] GetArray <T>(List<T> list) => GetField<T, T[]>(list, _items);
public static ref int GetCountRef <T>(List<T> list) => ref GetField<T, int>(list, _size);
public static ref int GetVersionRef<T>(List<T> list) => ref GetField<T, int>(list, _version);
} Which you could turn into the following extension method: public static class ListExtensions
{
public static void AddRange<T>(this List<T> self, ReadOnlySpan<T> values)
{
if (self.Count + values.Length > self.Capacity)
self.Capacity = self.Count + values.Length;
var array = ListMarshal.GetArray(self);
values.CopyTo(array.AsSpan(self.Count));
ListMarshal.GetCountRef (self) += values.Length;
ListMarshal.GetVersionRef(self)++;
}
} Which would be used as follows: var list = new List<int> { 1, 2, 3, 4 };
var data = new[] { 5, 6, 7, 8 }.AsSpan();
list.AddRange(data);
foreach (var value in list)
Console.WriteLine(value); It feels incredibly dirty to do this though, so a proper solution in CoreFX would be greatly appreciated. |
Yes, it is dirty and it does not even work. You will get |
The best current workaround for this is to roll your own |
Interestingly, it worked perfectly fine while running on Mono (must be an oversight on their part). However on .NET Core I get the error you mentioned. It's fixable by using reflection, although it requires some extra ceremony.
I'd normally recommend this too, but it's not an option when you're dealing with a third party API taking a |
Due to lack of recent activity, this issue has been marked as a candidate for backlog cleanup. It will be closed if no further activity occurs within 14 more days. Any new comment (by anyone, not necessarily the author) will undo this process. This process is part of the experimental issue cleanup initiative we are currently trialing in a limited number of areas. Please share any feedback you might have in the linked issue. |
This issue will now be closed since it had been marked |
Since this is now closed (I didn’t see the comment by the bot from 2 weeks ago), what is the preferred solution for doing this while avoiding the allocation that would come from |
@Raymonf to my knowledge the only known workarounds are the ones listed in the OP. In the future it might be possible to do this using the CollectionsMarshall.AsSpan method, however we're still missing a method for presizing lists (cf. #55217). In pseudocode it would look as follows: public static void AddRange<T>(this List<T> list, ReadOnlySpan<T> items)
{
int initialCount = list.Count;
list.Resize(initialCount + items.Length); // !!! NB this method is currently not implemented
Span<T> buffer = CollectionsMarshal.AsSpan(list);
items.CopyTo(buffer.Slice(initialCount));
} |
@eiriktsarpalis how come we're closing this? Was there a decision to reject the proposal altogether? |
I guess the question is "why was |
See #60288 for context. |
I updated the top post. I think we should add this. |
Are there opportunities for adding other span overloads? At quick glance, a constructor accepting |
Adding the proposed overload |
CopyTo would be reasonable. It does have a workaround today, which is that someone can use CollectionsMarshal.AsSpan to get a span for the list, and then CopyTo with that as the source... there's no good workaround today for AddRange. A ctor is going to have the same ambiguity problem that tfenise calls out, and we can't workaround that with a name. |
This would be the desired API: namespace System.Collections.Generic;
public partial class List<T>
{
public void AddRange(ReadOnlySpan<T> span);
public void InsertRange(int index, ReadOnlySpan<T> span);
public void CopyTo(Span<T> span);
} If we go down the extension method route, it would look like this: namespace System.Collections.Generic;
public static class CollectionExtensions
{
public static void AddRange<T>(this List<T> list, ReadOnlySpan<T> source);
public static void InsertRange<T>(this List<T> list, int index, ReadOnlySpan<T> source);
public static void CopyTo<T>(this List<T> list, Span<T> destination);
// We discussed this but decided against this until there is a need.
// public static bool TryCopyTo<T>(this List<T> list, Span<T> destination);
} We're inclined to approve the extension method shape and simultaneously work with the compiler team to see whether we can use a language feature to avoid using the extension methods. |
EDITED 9/22/2022 by @stephentoub:
Perhaps an
AddRange
overload taking aSpan<T>
can be added to theList
class?Workarounds
The text was updated successfully, but these errors were encountered: