views:

209

answers:

4

Say, for instance, I have a class:

public class MyFoo : IMyBar
{
    ...
}

Then, I would want to use the following code:

List<MyFoo> classList = new List<MyFoo>();
classList.Add(new MyFoo(1));
classList.Add(new MyFoo(2));
classList.Add(new MyFoo(3));

List<IMyBar> interfaceList = new List<IMyBar>(classList);

But this produces the error:

`Argument '1': cannot convert from 'IEnumerable<MyFoo>' to 'IEnumerable<IMyBar>' 

Why is this? Since MyFoo implements IMyBar, one would expect that an IEnumerable of MyFoo could be treated as an IEnumerable of IMyBar. A mundane real-world example being producing a list of cars, and then being told that it wasn't a list of vehicles.

It's only a minor annoyance, but if anyone can shed some light on this, I would be much obliged.

+12  A: 

This is going to work in C# 4.0! What you are talking about is called generic covariance.

In the meantime you can use the Cast extension method:

List<IMyBar> interfaceList = new List<IMyBar>(classList.Cast<IMyBar>());
Mehrdad Afshari
Ok, brilliant, but it doesn't really seem like rocket science - is there any particular reason why it wasn't supported previously?
Matt Whitfield
@Matt: It's not rocket science but like any feature, it takes time and money to implement and there have been some great features with higher priority that they have implemented sooner (e.g. LINQ is really good, isn't it?). You'll have to cut some great features to ship a product in time. -- One thing to note about this specific feature is that it requires support from the underlying CLR and CLR 2.0 didn't support it. Support is added in CLR 4.0.
Mehrdad Afshari
Thanks - very helpful... :)
Matt Whitfield
Actually it would have taken them about two seconds to implement in CLR 2.0: http://stackoverflow.com/questions/1995113/strangest-language-feature/2004371#2004371 This was simply an oversight on their part.
BlueRaja - Danny Pflughoeft
@Mehrdad - compared to a lot of language features, covariance *is* pretty much rocket science. It's not simple at all!
Dan Puzey
@BlueRaja: A constructor cannot take generic type arguments.
Mehrdad Afshari
@Dan: Well, I agree that adding covariance to a language requires a lot of thought (you have to make a lot of choices about the kind of covariance you pick and how to implement it). I think C# guys have done that very well. I said it's not rocket science because it's a solved problem in computer science and type theory.
Mehrdad Afshari
@BlueRaja: Actually it's already supported in CLR 2.0 - it's just not in C# 2.0 (or 3.0). I wouldn't regard it as quite as much of a solved problem as you guys think though - even in .NET 4.0 there will be problems when it comes to combining variant delegates...
Jon Skeet
To clarify: support for definition of variant types was added to CLR v2.0. Support for defining and using such in C# was added in C# 4. Annotating the BCL types to use this feature was done in CLR 4. And for those of you who think it was easy, I wish you could read the hundreds of pages of bug reports that the feature has generated so far. A change to the type system this radical is *not easy*; that an academic can show that the algebra works out nicely is completely irrelevant to the problem of how to make it work with an existing body of millions of lines of code.
Eric Lippert
@Eric: Thanks for the clarification. Re *ease of implementation*, I believe you're right and basically I said the same in my last comment. Never meant to undervalue the great job you guys did in C# 4. I hope that's not interpreted from anything I said.
Mehrdad Afshari
Mehrdad, I think Eric is responding to BlueRaja's comment where he said that implementing this feature would have taken 2 seconds. It's ironic when you think that he took 10 seconds (5 times the amount) just to type comment about a feature but expects that the feature itself will take 2 seconds.
SolutionYogi
@Eric Lippert - Just to clarify - I wasn't saying it *was* easy - I was saying it *seemed* easy - so very grateful for your wisdom which gives me a greater understanding :)
Matt Whitfield
@Eric: I was just talking about AddRange<T> being able to support adding classes which extend T, I wasn't talking about co/contra-variance in general. I realize how complicated of a topic that is, and appreciate the enormous effort you guys went through to implement it <3
BlueRaja - Danny Pflughoeft
+3  A: 

This is supported in .NET 4.0 but not earlier.

http://msdn.microsoft.com/en-us/library/dd799517%28VS.100%29.aspx

John Buchanan
Technically it's been supported in .Net via IL since 2.0 but it only gained support in C# and VB.Net in 4.0
JaredPar
@JaredPar: Really? Does the CLR 2.0 support generic covariance in *verifiable* IL?
Mehrdad Afshari
@Mehrdad, Yes I believe so. It's been 1+ years since I had this discussion with Lucian but my current recollection is that CLR supported co/contravariance since 2.0 and that 4.0 was all in the compilers (maybe a bug fix or two in the CLR).
JaredPar
@JaredPar Interesting. Jon is saying the same thing here. I've never thought that would be the case.
Mehrdad Afshari
@Mehrdad - yes, but `IEnumerable<T>` isn't marked as covariant in the 2.0 BCL.
kvb
+3  A: 

To clarify, the reason it doesn't work now is because IEnumerable<MyClass> does not inherit from IEnumerable<MyBar>. I'll say that again: the enumerable of the derived type does not inherit from the enumerable of the base type. Rather, both types specialize the IEnumerable<T> generic type. In .Net 3.5 and lower, they have no other relationship beyond specialization (which is not inheritance) and are otherwise two completely different types in the type system.

.Net 4.0 will not change the way these types relate at all. They will still be two completely different types that relate only through specialization. What it will do is allow you to write methods that know about the specialization relationship and in some cases allow you substitute one when you wrote the other.

Joel Coehoorn
To clarify your second paragraph: the point of generic variance is that it alters the "is assignment compatible with" relation on certain generic types. That's the relevant relation; the inheritance relation is unchanged, as you say.
Eric Lippert
Joel, I never thought about co-variance/contra variance this way. Very useful answer, +1 from me.
SolutionYogi
+1  A: 

If you want to cast between IEnumerables (as you wrote in the headline of your question) and not between Lists (as you wrote in your question) you can wait for the c# 4.0 covariance feature. Before that day you can use extension methods. But I would not use the Cast extension method noted in other answers, but writing my own method, that can be only used for covariance casting in IEnumerable. When/If you switch to C# 4.0 you will easily find all places in your code where the cast will be redundant.

public static class cEnumerableExtensions
{
    public static IEnumerable<TResult> CovarianceConversion<TResult, TSource>(this IEnumerable<TSource> source)
        where TSource : TResult
    {
        foreach (var item in source)
        {
            yield return item;
        }
    }
}

Final Node. There seems to be no precompiler constant for the netframework 4.0, otherwise I would add

    #if DOTNET4
    [Obsolete("You can directly cast in C# 4.0.")]
    #endif
Janko R