-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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 Proposal]: CollectionsMarshal.CreateList<T>(T[])
#80311
Comments
Tagging subscribers to this area: @dotnet/area-system-collections Issue DetailsBackground and motivationThe
To avoid resorting to suboptimal or outright hacky solutions, this API enables a performant and trustworthy solution, while being hidden under the API Proposalusing System.Collections.Generic;
namespace System.Runtime.InteropServices;
public static class CollectionsMarshal
{
// Creates a list with the given underlying array buffer, with the Count property being equal to the array's length
public static List<T> CreateList<T>(T[] arrayBuffer);
// Creates a list with the given underlying array buffer, with the Count property manually specified
public static List<T> CreateList<T>(T[] arrayBuffer, int listCount);
} API Usage// Create a buffer for 1000 elements
var buffer = new int[1000];
const int count = 578;
// Only fill the first 578 elements
for (int i = 0; i < count; i++)
{
buffer[i] = i * i - 2;
}
var list = CollectionsMarshal.CreateList(buffer, count);
// list.Count == 578
// list.Capacity == 1000 Alternative DesignsAs mentioned above, alternatives include:
RisksNo breaking changes. This API provides a safer and built-in way for a performance-oriented improvement on the subject of constructing a list.
|
I would argue against over allocating the array with 1k elements if you know for sure that you will only use a specific count of elements. I would change the sample to: const int count = 578;
var buffer = new int[count];
// Only fill the first 578 elements
for (int i = 0; i < count; i++)
{
buffer[i] = i * i - 2;
}
var list = CollectionsMarshal.CreateList(buffer, count);
// list.Count == 578
// list.Capacity == 578 Why? because there are cases where you might want to not allocate more than what is needed on the backing buffer. Also, while this is a thing, why not also allow using properties that returns an At least if I remember right Also, lifetimes can be a problem as well. Say for example you use |
For your specific scenario, |
Is this a valid concern? If so, one way I believe can counter this is to introduce a |
It is not. It's completely undefined behavior and could cause all sorts of memory corruption issues. |
The only dangerous workaround possible would be doing it with a DynamicMethod that ignores access checks. |
Edited the thread to reflect the actual viability of the proposed dangerous workaround |
Doing |
|
I might be missing something, but couldn't your usage example could be written as follows? var list = new List<int>(1000);
const int count = 578;
// Only fill the first 578 elements
for (int i = 0; i < count; i++)
{
list[i] = i * i - 2;
}
// list.Count == 578
// list.Capacity == 1000 What is a use case where you have a buffer whose ownership you need to transfer to a list and that buffer couldn't have been a list in the first place? |
That example would throw an index out of range because your list's buffer is 1000 items, but its count is 0. You cannot directly set to the buffer, you have to follow the current size of the list. |
#55217 has been approved for .NET 8 and will let you set the count. |
That's the one step. With that addition to the API, the whole operation of setting values directly to the buffer is basically entirely available, alongside the usage of the API getting you the span to the list's buffer. This API therefore becomes a matter of unsafely constructing a list out of a buffer becoming more convenient. |
Closing in favor of #55217 |
Do note that this API would be slightly more efficient due to avoiding a single copy, at the cost of being more unsafe. |
Background and motivation
The
System.Collections.Generic.List<T>
collection does not provide a way to begin its lifetime from a given underlying array buffer backing the contents of the list. The best options are:To avoid resorting to suboptimal or outright hacky solutions, this API enables a performant and trustworthy solution, while being hidden under the
CollectionsMarshal
class. While the unsafe solution proposed above is sometimes viable and works deceivingly well for the given layout of the class, it always relies on a per-framework basis, and may not be considered stable. This is unwanted for a solution to a problem around a very commonly-used data structure.API Proposal
API Usage
Alternative Designs
As mentioned above, alternatives include:
Risks
No breaking changes. This API provides a safer and built-in way for a performance-oriented improvement on the subject of constructing a list.
The text was updated successfully, but these errors were encountered: