13

UPDATE: The following code only makes sense in C#4.0 (Visual Studio 2010)

It seems like I am having some misunderstanding of covariance/contravariance thing. Can anybody tell me why the following code doesn't compile?

public class TestOne<TBase>
{
    public IEnumerable<TBase> Method<TDerived>(IEnumerable<TDerived> values)
        where TDerived: TBase
    {
        return values;
    }
}

while this one compiles: (!!!)

public interface IBase
{
}
public interface IDerived: IBase
{
}
public class TestTwo
{
    public IEnumerable<IBase> Method(IEnumerable<IDerived> values)
    {
        return values;
    }
}
Trident D'Gao
  • 18,973
  • 19
  • 95
  • 159

3 Answers3

13

Covariance only applies to reference types (for the type arguments), so you have to add a class constraint:

public IEnumerable<TBase> Method<TDerived>(IEnumerable<TDerived> values)
    where TDerived : class, TBase
{
    return values;
}

This will prevent you from trying to convert, say, an IEnumerable<int> into an IEnumerable<object>, which is invalid.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • @Adam : I believe you are wrong, `IEnumerable` is not `IEnumerable` by default so it wouldn't compile even in `3.5` – sll Feb 20 '12 at 15:39
  • 1
    @AdamMihalcin: No, that code *wouldn't* have compiled before .NET 4. I've just tried it myself to verify that. Without generic invariance, the conversion from `IEnumerable` to `IEnumerable` is simply invalid. – Jon Skeet Feb 20 '12 at 15:39
  • @bonomo: Okay, fixed my answer in line with the question change :) – Jon Skeet Feb 20 '12 at 15:46
1

I cannot think of any situation where you actually need TDerived. Using TBase is sufficient:

public class TestOne<TBase>
{
    public IEnumerable<TBase> Method(IEnumerable<TBase> values)
    {
        return values;
    }
}

After all, you have no information about TDerived apart from the fact that it is a TBase...

linepogl
  • 9,147
  • 4
  • 34
  • 45
  • 2
    unfortunately it is not something I made up for the sake of complexity, it's a real situation that I ended up having in my code – Trident D'Gao Feb 20 '12 at 15:45
  • 1
    @bonomo: That's interesting, I would love to see a more detailed example for future reference. :-) – linepogl Feb 20 '12 at 15:49
0

Neither compiled for me initially. Both failed on the implicit cast from Super(T/I) to Base(T/I). When I added an explicit case, however, both compiled.

public IEnumerable<TBase> Method<TSuper>(IEnumerable<TSuper> values)
    where TSuper: TBase
    {
        return (IEnumerable<TBase>) values;
    }
devstruck
  • 1,497
  • 8
  • 10