-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
[System.Linq] Consider adding runtime checks for IReadOnlyCollection<T> in input sources #42254
Comments
Tagging subscribers to this area: @eiriktsarpalis, @jeffhandley |
It doesn't. |
If I recall correctly such suggestions to add (not to replace |
@davidwrighton are these kinds of "optimistic checks for interfaces" indeed much cheaper now than in the past? |
cc @VSadov |
As I was! Egg on my face for sure. Apologies, i thought this would be a drop-in request. Thanks for the quick replies |
@danmosemsft a quick benchmark: static IEnumerable<string> strings = new List<string>();
[Benchmark]
public bool IsCollection() => strings is ICollection<string>;
[Benchmark]
public bool IsReadOnlyList() => strings is IReadOnlyCollection<string>; .NET Core 2.2:
.NET Core 3.0:
.NET 5.0:
Related PR: dotnet/coreclr#23548 |
Covariant interfaces are not super slow now. Cost can vary for both regular interface casts and for fancy ones. Regular interface cast is a linear search, but typically does not need to search far. Cached cast may need to deal with hash collisions, but typically just gets a cached value. As a veeery rough estimate a fancy cast can be counted as a 2X of a regular interface cast. In the past the cost of complicated casts was technically unbounded. As you nest variant generics, the cost would go up and considerably. Thus they were avoided by library owners. |
Thanks @EgorBo that is indeed much faster. |
It is definitely faster than before, but there is still a non-zero cost to performance when making the suggested change. I have a few possible concerns here.
|
A good example is Linq's Count: https://github.com/dotnet/runtime/blob/master/src/libraries/System.Linq/src/System/Linq/Count.cs#L11-L46
|
There were also perf issues with limited numbers of "fast" dictionary slots (see #11971) that should now be (largely) mitigated by the dynamic dictionary expansion added in 5.0. |
I found quite a few complains or rejected attempts to optimize LINQ for #28651 - LINQ results implicit support for IReadOnlyCollection |
This can be expanded to different scenarios:
|
There are a few other interfaces used to determine IEnumerable counts, also not in a subtype relationship with either Next step should be to enumerate a list of methods that could benefit from specialization. |
Tagging subscribers to this area: @eiriktsarpalis, @jeffhandley |
Related to #31001 |
Related: #23337. |
Hello everyone! I'm not sure if someone else thought about this before, but I just had an idea that could solve this problem. Why not introduce a new, non-generic interface to the BCL named "ICountable", with nothing more than a "Count" property? Then just make ICollection, ICollection<T> and IReadOnlyCollection<T> all implement this interface. That would easily solve the problem with covariant casts, since we don't even have type parameters anymore. And we could even simplify all the code paths with just a test for "ICountable". |
Adding more interfaces can make things a mess and worse. Not all classes will implement the new interface, so an additional interface check may be required. |
Is there any progress on the issue? Even with .NET 6.0, Linq functions which should be able to take advantage of indexed random-access collections, such as |
No, there hasn't been any progress. The general conclusion is that we can't add new interface checks here without changing the interface checking mechanism. We've been kicking around the idea of an optimized type switch operation for a few years, but it would almost certainly make the most common case a little bit slower in exchange for allowing more scenarios to have roughly equivalent performance. However, we haven't built out that low level feature enough to see the practical impact on changing the common patterns in the Linq codebase. |
Which is why we intentionally skipped checks for One possible alternative avenue to explore is introducing a common base interface for exposing the count, which should be possible using DIMs. Here's a sketch of that idea. We've generally resisted retrofitting old interfaces with DIMs so far though, since they can be susceptible to both source and runtime breaking changes. |
@elgonzo consider this:
P.S. I must admit this. |
I'm joining the discussion regarding support for I came here after first writing my own version of this method. Then, IntelliSense alerted me to your version. The method names were similar. At first, I was confused. Your code is similar to mine. Then, I was excited. I thought I could delete mine. Then, I was disappointed. I discovered that While I remain in favor of adding support, I respect the opposing opinion. If the decision remains not to support it, I'd like to offer some alternate suggestions:
I understand, given its history, that its natural to consider this purely from the perspective of how the method fits into the larger LINQ ecosystem. However, not every use-case is LINQ-related. Mine was not. That said, let me be clear, I am a huge fan of LINQ. It has probably saved me thousands of hours of coding over the years! Thank you for your time and consideration. |
@stephentoub this is very exciting news. Thank you for sharing! I hope the reversion does indeed get reverted 😄 The whole However, it might be worth updating the comments/documentation until this (hopefully?) becomes a reality. I assume .NET 10 is at least a year away. In the meantime, I wrote my own overload method/extension with a For reference, in case it helps anyone else, here's my new method:
|
There are many places in the Linq / Collection code that leverage detecting if an
IEnumerable<T>
is anICollection<T>
to perform optimizations (e.g. presizing a new array, etc.)List.cs
Because
ICollection<T>
implementsIReadonlyCollection<T>
,IReadonlyCollection<T>
should be exclusively used in these scenarios to support customIReadonlyCollection<T>
implementations that don't necessary want to exposeAdd(T item)
Currently, collection authors have to implement ICollection to take advantage of the performance gains and leave Add throwing
NotImplementedException
to convey proper usage.The text was updated successfully, but these errors were encountered: