5

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.

Mechanical snail
  • 29,755
  • 14
  • 88
  • 113
Matt Whitfield
  • 6,436
  • 3
  • 29
  • 44
  • 1
    The answer you're after was also covered in this question: http://stackoverflow.com/questions/2346763/any-simple-way-to-explain-why-i-cannot-do-listanimal-animals-new-arraylistdo/2346857#2346857 – Dan Puzey Mar 22 '10 at 21:41
  • Yeah thanks - I saw that in the 'related' list once I'd posted the question, but it didn't come up while I was writing it... – Matt Whitfield Mar 22 '10 at 21:45

4 Answers4

14

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
  • 414,610
  • 91
  • 852
  • 789
  • 1
    Ok, brilliant, but it doesn't really seem like rocket science - is there any particular reason why it wasn't supported previously? – Matt Whitfield Mar 22 '10 at 21:31
  • @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 Mar 22 '10 at 21:33
  • 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 Mar 22 '10 at 21:41
  • @Mehrdad - compared to a lot of language features, covariance *is* pretty much rocket science. It's not simple at all! – Dan Puzey Mar 22 '10 at 21:43
  • @BlueRaja: A constructor cannot take generic type arguments. – Mehrdad Afshari Mar 22 '10 at 21:43
  • @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 Mar 22 '10 at 21:45
  • @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 Mar 22 '10 at 21:51
  • 5
    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 Mar 22 '10 at 22:30
  • @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 Mar 22 '10 at 22:45
  • 1
    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 Mar 22 '10 at 23:28
  • @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 Mar 22 '10 at 23:55
  • @Eric: I was just talking about AddRange 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 Mar 23 '10 at 00:33
3

This is supported in .NET 4.0 but not earlier.

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

John Buchanan
  • 5,054
  • 2
  • 19
  • 17
  • 1
    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 Mar 22 '10 at 21:49
  • @JaredPar: Really? Does the CLR 2.0 support generic covariance in *verifiable* IL? – Mehrdad Afshari Mar 22 '10 at 21:50
  • @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 Mar 22 '10 at 21:58
  • @JaredPar Interesting. Jon is saying the same thing here. I've never thought that would be the case. – Mehrdad Afshari Mar 22 '10 at 22:01
  • @Mehrdad - yes, but `IEnumerable` isn't marked as covariant in the 2.0 BCL. – kvb Mar 22 '10 at 22:02
3

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
  • 399,467
  • 113
  • 570
  • 794
  • 1
    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 Mar 22 '10 at 22:35
  • Joel, I never thought about co-variance/contra variance this way. Very useful answer, +1 from me. – SolutionYogi Mar 22 '10 at 23:31
1

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
Community
  • 1
  • 1
Janko R
  • 137
  • 1
  • 9