-
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
Consider making TryGetEnumeratedCount an interface method with default implementation #52775
Comments
Tagging subscribers to this area: @eiriktsarpalis Issue DetailsRight now I would hope calling it is also at least as fast, maybe faster, than doing a dynamic type check.
|
(Moving to 6.0 because this is likely now-or-never). |
If you want to get this to API review quickly, I think you need to mark this as ready-for-review and blocking |
There is also IReadOnlyCollection with Count. |
Ref: #27183, #48239 |
In order for such an addition to be useful we would need to provide overrides for common interfaces like |
@eiriktsarpalis Should this be unmarked as "ready-for-review" for the time being? |
It's been scheduled for today's API review. I think the proposal needs work but as @agocke said it's now or never so might as well make a call during the meeting. |
We currently have apprehension about adding DIMs at this level. For this particular method we're concerned that the obvious solution would be to re-implement the methods in But we agree that we could use some better concrete guidelines here going forward. |
FWIW I created a small repro that demonstrates both compile-time and runtime breaks when adding DIMs to existing interfaces: |
Awesome! One thing to note: the current implementation of |
Follow-up: why can't |
You mean the extension method? We decided not to include a type test for This constraint doesn't apply to the DIM variant, however the risk of introducing diamonds cancels out any potential for flexibility. |
Cf. #31001. TL;DR similar concerns about versioning apply. |
Not sure how to interpret this. If we do the same thing here, and don't implement |
It would eliminate that particular instance of a diamond, but what you're suggesting is we only implement it for |
Thinking forward from Eirik's last comment: partial interface IEnumerable<T>
{
bool TryGetNonEnumeratedCount(out int count)
{
count = 0;
return false;
}
}
partial interface IReadOnlyCollection<T> : IEnumerable<T>
{
bool IEnumerable<T>.TryGetNonEnumeratedCount(out int count)
{
count = Count;
return true;
}
}
// Nothing else in the BCL implements this method. Very important. This works in whatever version it's introduced in. No one can have beaten us to creating a method slot for However, it now becomes a potential runtime breaking change for anyone to add NugetPackageA, vAlreadyInMarket: public interface ICanHazLength<T> : IEnumerable<T>
{
int Length { get; }
} NugetPackageB, vAlreadyInMarket: public class SomethingAppropriate<T> : ICollection<T>, ICanHazLength<T>
{
int ICollection<T>.Count => Length;
public int Length => ...;
...
} Now two things happen in either order, neither of which is a break at first: NugetPackageA, vLater takes a bug fix for "hey, we should report we're countable": partial interface ICanHazLength<T>
{
bool IEnumerable<T>.TryGetNonEnumeratedCount(out int count)
{
count = Length;
return true;
}
} NugetPackageB, vLater takes a bug fix for "SomethingAppropriate should also implement IReadOnlyCollection" partial class SomethingAppropriate<T> : IReadOnlyCollection<T>
{
int IReadOnlyCollection<T>.Count => Length;
} Whichever one happened second results in a runtime error for any execution that has both libraries at the higher version ( The SomethingAppropriate class could also fail to load if we updated a collection type from a netstandard2.0 TFM assembly (that doesn't have a split build already) to add the IReadOnlyCollection conformance declaration and it derived from our type... so that adds new vectors where updates from dotnet/runtime cause runtime type loads for community types... and I think we're generally against that, despite our willingness to make more breaking changes than we had in .NET Framework 😄. (I don't actually know what happens if the collection came from a TFM-split package... does "my base class handled this" beat "I declared conformance to an interface that replaced it"? I assume it does at runtime, since the method slot is already populated. Don't know what the recompile behavior is, though, I could go either way...) Maybe it wouldn't come up in this specific situation (what's a countable-enumerable that isn't an I(ReadOnly)Collection?), but it shows the room for where the breaks come in (basically, any time an interface provides an implementation for a base interface slot). Actually, the gedankenexperiment shows that the problem comes when a /second/ override appears (anywhere in the universe), but "this can be done precisely once" brings in the question of who gets to do it (same assembly only? introduced together in the same assembly?). The alternative, "no one should do this," is easier to reason about (which gave rise to the proto-guideline "DO NOT use Default Interface Methods to provide an implementation for a member from a base interface."). That was pretty rambly, though, hopefully there were some useful thoughts in there. |
Hmm I was thinking the same thing but came to a different conclusion: why should we assume the rest of the community have the same breaking change bar as the framework? Yes, third party library usages could end up creating a diamond situation for their customers, but I'm not sure that should carry the same weight... |
Given that this discussion has turned out to be inconclusive, it looks like |
Right now
TryGetEnumeratedCount
is a LINQ extension method, meaning that the implementation is hard coded. It looks to see if the type implementsICollection<T>
orICollection
. I could see a case where a user might not implementICollection
but want to provide an implementation of this method.I would hope calling it is also at least as fast, maybe faster, than doing a dynamic type check.
@terrajobst @eiriktsarpalis
My proposed implementation:
and implementations matching the existing if statements for
ICollection<T>
andIListProvider<T>
There's a type check for
ICollection
in there, butICollection
andIEnumerable<T>
are parallel heirarchies. If we want to include that I would move the method down to the non-genericIEnumerable
(shouldn't make much of a difference otherwise).I would consider keeping the extension method as well, since otherwise it won't appear in intellisense unless the implementation is public on the deriving type.
The text was updated successfully, but these errors were encountered: