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]: Method for creating a List #102100
Comments
Tagging subscribers to this area: @dotnet/area-system-collections |
The same idea has been rejected in #80311 |
Wouldn't this mean that List would be required to have an array as the backing store? That seems problematic for any future development in this space. |
This can only be possible if we implement and enforce some ownership moving semantic. |
user creates a List not through the constructor but through a static method, they should understand that this array will be used in the List. |
Well, it's faster if there's an array, for example, and I won't be using it anymore, but I need a List. |
Well, it seems like a completely different api |
Different name, the same functionality |
It need to be safe in the first place, to guarantee nobody can misuse the array after.
|
Well, then it's already the problem of whoever will be using it; the method doesn't guarantee safety in using the array for anyone. For instance, you can pass the array by "ref" to emphasize this.
|
Well, there are fewer functions than in a List |
If the caller need to pay attention around it, the method should be explicitly noted as unsafe: name it like Unsafe operation also requires more judgement about its benefit. In this case, why the caller gets an array in the first place? Can it be avoided and let the caller add items into a list? This comes the difference with Hope this helps you understanding the judgement. |
Sure, but I don't like that, for example, I have an array, I won't use it anymore, I create a list, and it takes this array and rewrites it into a new array and puts it into the list. So, resources are spent on copying, and additional memory is used. Well, there are all sorts of arrays, but most people use lists. |
Well, I don't know about the name, but it's better that this class is in List. |
For this, there's ToList if you need to use the array further. |
That doesn't address my point. This would require arrays to having an array as the backing field. It would prevent changing that in the future. |
Why replace an array? And what can be used instead of an array? |
Any other impl. Or a specialized one from the runtime. For example a length and the elements inline without an array indirection. Your proposal would force the impl to stay an array forever. |
copy the array from the method I'm talking about into a new storage . Although the array is in a List there since 2000 |
|
That already exists - the |
I mention in case of a change in the array to some abstract data storage type of the future; however, I highly doubt such a thing will exist |
That would break the very semantics of the method you're proposing. If you want to copy, there are methods for that.
As an impl detail. It can be changed if runtime wants to optimize this (which would have wide benefits). This API would prevent that |
It seems very reasonable for this to happen. I would save an extra allocation in the most commonly used collection type. |
Do we have a proposal for that? It would need GC and runtime work too special case lists similar to strings? |
Yes. I would def expect that. But a growable/shrinkable, low overhead, array impl seems like a good fit (esp. given how widespread usage of |
If you replace the array with something else, it will disrupt the operation of the List; this collection may not work properly in some cases. If there are any new super modern types besides the array, most likely you will need to write a new List. |
Why would it disrupt anything?
What cases would it not work properly in?
Why would you need to do that. The impl of |
Yes, they still use reflection. |
Think of it this way: if the backing array was changed to However, your proposals entire purpose is to provide a constructor that avoids allocation. Changing the backing array to a You even mention this yourself by saying the constructor would change from In other words, your proposal would force the array-backed items to be “part” of the API surface. The team would be unable to replace it without violating the contract. On the contrary, the To put it shortly: your proposal is not just an API guarantee (which is easy to maintain), but a performance guarantee. If this API were to exist, the only way to maintain its performance is to force the backing store away to always be an array. Just because it’s been that way for ~25 years doesn’t mean it won’t change. Lastly, why is this API necessary? This feels like an XY problem. Is performance of |
No contracts are being ignored here. I propose a static method and a private constructor. |
That is the definition of an API contract. Saying that the user can use it to avoid allocation is a guarantee the runtime would then have to keep. In other words, how will you avoid allocations when the backing array no longer exists? As I mentioned, should it be replaced with |
If we change the name of the array inside the List now, somes percentage of programs will error because some may be using reflection. Sure, over time, we can change the array to a LinkedList, but how do we prove that this implementation definitely adheres to the current contract? Because there are various situations, such as behavior in multithreading and so on. |
Reflection for private fields is not part of the API contract. It's an implementation detail, and implementation details are not part of the contract. If implementation details were part of the contract, performance improvements in many scenarios would be impossible. Programs that use reflection to access the array are misbehaving. |
Who is "they"? Using reflection to get at internal impl details and take a dependency on them is not a supported scenario.
The documented contract of
Why would this be relevant? Either programs are using the stated contract or not. If they are, they will continue to work. If they are not, then they may break with any change at any time. |
Simple. The contract is specified in the documentation. You then test the new impl against that documentation. The runtime already has tons of tests that do this already. That's how the runtime already makes changes today, including completely revising the internal implementation of types. If that breaks someone using reflection, that's fine as that's not a supported scenario. |
The runtime already states what are contracts and what are not. Again, your view here would preclude the runtime making any changes (including optimizing things). They can and do make changes all the time to impl details that are not part of the contract of a type. |
Doesn't this constructor bind the array to a List? |
This constructor sets the initial length of the array; my method sets the initial array. |
No. There is no array there at all. All that says is that it:
This constructor is an O(1) operation. And all that
All that matters with that is:
|
Again, as an implementation detail. It could pick a different implementation as long as it abides by the stated contract which people can depend on. |
Yes. That's a 'remark'. A remark gives info, but not a contract.
It would still work correctly, as long as it abides by the stated contract. Again, this is how the runtiem works. They change internal impl details all the time. Especially for performance. I'm not sure how much more plainly or clearly i can state this. |
let them change the implementation; the semantics of this method are that it takes the initial data for "List", which is stored in an array |
|
How do you expect the team to change the implementation when your proposal, by design, requires a specific implementation? |
|
List already has a way to do that. It is the list constructor that takes another collection. The semantics of that constructor are exactly |
The difference between this and that is that there's IEnumerable there, while here it's an array, and this method is "Unsafe," meaning it's much faster than that constructor, only it's unsafe. |
But this would now mean we added a method directly documented to be fast, and it would become slow in a future release, causing code to fall off a perf cliff. No code could take advantage of this as the only reason to would be perf gain, but that perf could easily become a perf-pitfall. If a method exists for performance, it needs to not regress, as otherwise people can't use it. That's why ImmutableArray allows this. Because there is a contract about its impl, and guarantees around the marshaling methods. These won't change because it would be a disastrous perf problem for all the code that took a dependency on the documented performance characteristics there. |
As a side note, |
Background and motivation
runtime/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/List.cs
Line 25 in dc98263
I suggest adding a method to the List class that would allow creating a new instance of List without copying the array, but simply inserting the array into the middle of the List
API Proposal
API Usage
Alternative Designs
No response
Risks
No response
The text was updated successfully, but these errors were encountered: