Proposal: Support covariant generic classes and structs #2498
Replies: 19 comments 8 replies
-
The limitation of variance only being applicable to interfaces and delegates is imposed by the CLR. You can force an assembly to compile with a variant generic class using IL but the result will not be verifiable. I wonder if custom async builders would allow for a variant |
Beta Was this translation helpful? Give feedback.
-
Variance on a struct, I don't think makes sense, as the assignment would be copying. But on a class sure that's reasonable. Although now that there is support for async pattern matching, you can change that interface to use something like |
Beta Was this translation helpful? Give feedback.
-
That's a good point about the struct I hadn't considered. It's no less efficient to implement an extension method which creates a new instance than this proposal would be. I had done a search for a similar proposal to this one, but I missed #171. This can probably be closed too in that case. |
Beta Was this translation helpful? Give feedback.
-
That said, an assignment of a generic reference type whose type parameter is a generic struct (e.g. As it is, to get hold of an |
Beta Was this translation helpful? Give feedback.
-
Variance on a struct might still make sense from a type-safety point of view. Take ImmutableArray structs, you should be able to assign an ImmutableArray<string> to ImmutableArray<object>, the fact that the backing array is still a string[] isn't a problem all the bits are the same and this would be type safe. |
Beta Was this translation helpful? Give feedback.
-
Would this not be relevant for |
Beta Was this translation helpful? Give feedback.
-
It would - a struct being readonly would automatically qualify it for covariance on each of its type arguments. |
Beta Was this translation helpful? Give feedback.
-
@PCavan I'd say such types should still explicitly declare to be variant, just to avoid (potential) strange behavior changes, but that's about the only nit I see with that. |
Beta Was this translation helpful? Give feedback.
-
@Joe4evr I can't think of an example where automatically adding covariance to readonly structs would change behaviour (which is not to say there isn't one). Given that readonly structs aren't covariant now, any compiling code must be using the exact same compile-time type argument as the struct instantiation, and because that option is guaranteed to be available now, the compiler would continue to choose that option even if the struct were covariant as the exact match is always the best match.
On the other hand, preferring explicitness on these things seems to be C#'s way of doing things (e.g. having to use |
Beta Was this translation helpful? Give feedback.
-
IIRC, automatically adding covariance would break readonly structs that have mutable storage from another object, e.g.: using System.Collections.Generic;
public readonly struct Listy<T> {
public Listy(List<T> list) => this.list = list;
readonly List<T> list;
public T Get(int i) => list[i];
public void Add(T t) => list.Add(t);
} |
Beta Was this translation helpful? Give feedback.
-
@yaakov-h Ah, good point. I was surprised that was legal C# code when I read it, but this is another case of "Read-only does not mean immutable". In fact, thinking about it, even immutability isn't enough to guarantee covariance - Even if the covariance change had to be explicit, at least I think what I said above holds in that making the change won't break anything code that uses the struct (the same can't be said for covariant classes - there's no guarantee it's using the exact same type already). |
Beta Was this translation helpful? Give feedback.
-
Could structs be covariant if all of its fields are covariant? |
Beta Was this translation helpful? Give feedback.
-
This would marry well with records (#89) I think. Immutability comes for free, which makes it that easier to implement covariant classes. And it's currently a pain to deal with this limitation when you want to use generic variance on "data" classes (without behavior/logic), as you have to use interfaces, and the code starts looking quite insane when you have interfaces all over for just passing data around. |
Beta Was this translation helpful? Give feedback.
-
I'm not sure if I understood it right, but wouldn't a class of |
Beta Was this translation helpful? Give feedback.
-
@andre-ss6 Yes it would, but I was considering whether immutability implies generic covariance. The |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
Since record types are immutable, it should be covariant as an IEnumerable interface, for example, given the following type:
It should be possible to use like this:
|
Beta Was this translation helpful? Give feedback.
-
Covariant generic classes would be particularly useful for public static async IAsyncEnumerable<T> ToAsyncEnumerable<T>(this Task<IEnumerable<T>> task)
{
foreach (var item in await task)
{
yield return item;
}
}
Task.FromResult(new List<object>()).ToAsyncEnumerable() // Cannot implicitly convert type... 😞 |
Beta Was this translation helpful? Give feedback.
-
Makes no sense that this isn't already supported. |
Beta Was this translation helpful? Give feedback.
-
With C# moving towards a more functional approach, I'm finding myself using monad types more frequently. Types like
Task<T>
,Lazy<T>
,Option<T>
,Maybe<T>
, etc. would benefit from being allowed to be covariant in their generic types. They are also frequently the sort of types that would benefit from being structs, which means that having them implement a covariant interface loses much of their benefit.This proposal would alleviate a particular pain in would-be covariant types which contain an async method:
If
Task<T>
could be made covariant, then so could the interface.Specifically, a class or struct can be declared as covariant in its generic parameter only if that parameter type only appears in out positions. A simple test for this is to strip out all access modifiers, implementations, fields and constructors and changing the class or struct to an interface. If the interface could be made covariant, then so can the original class/struct.
Beta Was this translation helpful? Give feedback.
All reactions