You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
having dedicated covariant interfaces for the different immutable collection types (and implementing them on the immutable collection classes) would allow to add extension methods that allow to add elements to an immutable covariant collection.
The extension methods could check if the incoming IImmutableCovariantList<out T> is already a ImmutableList<T> - which should be true most of the time - and in this case use all the specific methods, like insert, add, remove of that class. Otherwise, we'd need to create a new ImmutableList<T> and then operate on that new instance.
Why all the trouble?
Because explaining that an immutable collection of bananas cannot be perceived as an immutable collection of fruits is a hard task and in fact, could from a user perspective be seen as a design flaw - not as bad as the standard mutable array being covariant, but still not that beautiful.
With the proposed design the extension methods for modification would only show up on that type, not on IReadonlyList<T>. related: Make IImmutableList covariant like IReadOnlyList, IEnumerable etc. #16011. You'd have an immutable type, that when adding or removing an element would give you a new snapshot of exactly the same type, where this type is covariant.
an empty interface IImmutableCovariantList<T> implemented by Immutablelist<T>
extension methods for that interface (for now in a new namespace System.Collections.Immutable.Covariance)
Down there in the tests you also can see that how c# treats an ImmutableList<T> as an IImmutableCovariantList<T> to make adding an apple to a list of bananas work out. As a user you currently need to add the namespace System.Collections.Immutable.Covariance that I introduced to avoid blowing up the list of extension methods that you see in the intellisense list when working with a regular ImmutableList<T>.
var firstBanana = new Banana();
var bananas = ImmutableList<Banana>.Empty;
bananas = bananas.Add(firstBanana);
// add an apple to the banana(s) and you get fruits
var fruits = bananas.Add<IFruit>(new Apple());
Assert.Equal(fruits.Count, 2);
// add a banana and you still have bananas
bananas = bananas.Add(new Banana());
Assert.Equal(bananas.Count, 2);
// bananas can be seen as fruits
fruits = bananas;
var anotherApple = new Apple();
// again messing with bananas that get confronted with apples all of a sudden
IEnumerable<IFruit> applefollowedByBanana = bananas.SetItem<IFruit>(0, anotherApple);
IEnumerable<IFruit> applefollowedByBanana2 = bananas.Replace(firstBanana, anotherApple, EqualityComparer<IFruit>.Default);
Assert.Equal(applefollowedByBanana, applefollowedByBanana2);
here is the issue about covariance for classes: dotnet/roslyn#171
it will not happen.
anyways, because of this unavailable CLR feature, the only way of introducing covariant types is via interfaces. so yes, the proposed solution is a hack. it hacks around the limitations of the underlying system by introducing an interface for each immutable collection class. An ImmutableDictionary<TKey, TValue> e.g. would implement IImmutableCovariantDictionary<TKey, out TValue>. The interface is there to ship the covariance feature, which is not shippable without that "hack". So class and covariant interface would always come in pairs.
A user just needs to include System.Collections.Immutable.Covariance in her/his using statements and she/he is now able to work with IImmutableCovariantDictionary<TKey, out TValue> whenever the need arises. Therefore she/he can avoid more complicated code, which would do the same.
In the implementation of the extension methods regarding IImmutableCovariantArray<out T> would internally use the CastUp idea. The API surface would not care about when optimizations like the CastUp idea are possible or not. It would work the same way for all the immutable collections, which would be beautiful.
If in 10 years classes in .Net suddenly can have covariant type parameters, the interfaces would get obsolete and many extension methods would magically run faster as list as ImmutableList<T> would typically be true (even if the incoming list is actually of type ImmutableList<TDerived>).
public static IImmutableCovariantList<T> Insert<T>(this IImmutableCovariantList<T> list, int index, T element)
=> (list as ImmutableList<T> ?? list.ToImmutableList()).Insert(index, element);
if you look at the implementation it gets obvious that we could also return the classes on all extension methods.
e.g.
bananas.SetItem<IFruit>(0, anotherApple);
would act on ImmutableList<Banana> (seen as IImmutableCovariantList<IFruit>) and return ImmutableList<IFruit>. The user would not even need to deal with the interface.
related: https://github.com/dotnet/corefx/issues/5164
having dedicated covariant interfaces for the different immutable collection types (and implementing them on the immutable collection classes) would allow to add extension methods that allow to add elements to an immutable covariant collection.
The extension methods could check if the incoming
IImmutableCovariantList<out T>is already aImmutableList<T>- which should be true most of the time - and in this case use all the specific methods, like insert, add, remove of that class. Otherwise, we'd need to create a newImmutableList<T>and then operate on that new instance.Why all the trouble?
IReadonlyList<T>. related: Make IImmutableList covariant like IReadOnlyList, IEnumerable etc. #16011. You'd have an immutable type, that when adding or removing an element would give you a new snapshot of exactly the same type, where this type is covariant.it looks like the idea works and doesn't seem to break anything
Basically it is just
IImmutableCovariantList<T>implemented byImmutablelist<T>System.Collections.Immutable.Covariance)Down there in the tests you also can see that how c# treats an
ImmutableList<T>as anIImmutableCovariantList<T>to make adding an apple to a list of bananas work out. As a user you currently need to add the namespaceSystem.Collections.Immutable.Covariancethat I introduced to avoid blowing up the list of extension methods that you see in the intellisense list when working with a regularImmutableList<T>.here is the issue about covariance for classes:
dotnet/roslyn#171
it will not happen.
even though immutable collections would highly benefit: Eric Lippert implementing a
class Stack<out T>: https://stackoverflow.com/questions/2733346/why-isnt-there-generic-variance-for-classes-in-c-sharp-4-0/2734070#2734070 ...anyways, because of this unavailable CLR feature, the only way of introducing covariant types is via interfaces. so yes, the proposed solution is a hack. it hacks around the limitations of the underlying system by introducing an interface for each immutable collection class. An
ImmutableDictionary<TKey, TValue>e.g. would implementIImmutableCovariantDictionary<TKey, out TValue>. The interface is there to ship the covariance feature, which is not shippable without that "hack". So class and covariant interface would always come in pairs.A user just needs to include
System.Collections.Immutable.Covariancein her/his using statements and she/he is now able to work withIImmutableCovariantDictionary<TKey, out TValue>whenever the need arises. Therefore she/he can avoid more complicated code, which would do the same.In the implementation of the extension methods regarding
IImmutableCovariantArray<out T>would internally use the CastUp idea. The API surface would not care about when optimizations like the CastUp idea are possible or not. It would work the same way for all the immutable collections, which would be beautiful.If in 10 years classes in .Net suddenly can have covariant type parameters, the interfaces would get obsolete and many extension methods would magically run faster as
list as ImmutableList<T>would typically be true (even if the incoming list is actually of typeImmutableList<TDerived>).if you look at the implementation it gets obvious that we could also return the classes on all extension methods.
e.g.
would act on
ImmutableList<Banana>(seen asIImmutableCovariantList<IFruit>) and returnImmutableList<IFruit>. The user would not even need to deal with the interface.