15

Consider the following example:

class Base {}

class Derived : Base {}

class Test1
{
    private List<Derived> m_X;

    public IEnumerable<Base> GetEnumerable()
    {
        return m_X;
    }
}

This compiles just fine, because IEnumerable<T> is covariant in T.

However, if I do exactly the same thing but now with generics:

class Test2<TBase, TDerived> where TDerived : TBase
{
    private List<TDerived> m_X;

    public IEnumerable<TBase> GetEnumerable()
    {
        return m_X;
    }
}

I get the compiler error

Cannot convert expression type 'System.Collection.Generic.List' to return type 'System.Collection.Generic.IEnumerable'

What am I doing wrong here?

Vincent van der Weele
  • 12,927
  • 1
  • 33
  • 61

1 Answers1

14

Thing is, in the first case, the Base is known to be a class. In the second case, the type parameter T could be either class or a struct (this is how compiler thinks).

Solve the case by specifying that T is a class, and the error will disappear:

class Test2<TBase, TDerived> where TDerived : class, TBase
{
    private List<TDerived> m_X;

    public IEnumerable<TBase> GetEnumerable()
    {
        return m_X;
    }
}

So, the compiler tries to show us that TDerived could be a struct (since you didn't specify class constraint) and as we already know, covariance and contravariance do not work with structs.

Community
  • 1
  • 1
AgentFire
  • 8,944
  • 8
  • 43
  • 90
  • Pardon my ignorance as I'm still fairly new to C# (or maybe this is one of those really abstruse things about .NET in general), but doesn't the fact that `TDerived` derives from `TBase` -- which is a class -- preclude the ambiguity regarding whether `TDerived` is, itself, a class or a struct? Can you derive a struct from a class? – rory.ap Feb 11 '15 at 14:57
  • Interesting! I tried `where TBase : class` but that had no effect. – Vincent van der Weele Feb 11 '15 at 14:58
  • @roryap In C# yes, but not necessarily in the CLR. `ValueType` is a class, but all structs derive from it. – Theodoros Chatzigiannakis Feb 11 '15 at 14:59
  • 1
    @roryap no, you can't derive a struct, or from a struct. – AgentFire Feb 11 '15 at 14:59
  • 1
    @AgentFire -- So why do you need to be explicit, e.g. `where TDerived : class, TBase` vs. `where TDerived : TBase` since `TBase` is a class.? – rory.ap Feb 11 '15 at 15:00
  • 1
    @roryap because you **can declare** a type parameter with a struct type, like int: `void Foo() where T : int`. – AgentFire Feb 11 '15 at 15:03
  • @roryap `TBase` could be *any* type. The fact that it maps to the class `Base` is coincidence. The compiler doesn't know that detail. – CoderDennis Feb 11 '15 at 15:07
  • @CoderDennis -- Okay, but are we all in agreement that the compiler *could* (and maybe *should*) know? – rory.ap Feb 11 '15 at 15:10
  • 1
    @roryap Go read the blog post "[Minus 100 points](http://blogs.msdn.com/b/ericgu/archive/2004/01/12/57985.aspx)" by Eric Gunnerson (a member of the C# compiler team). It explains pretty well why features like you describe may have not made it in to the program. – Scott Chamberlain Feb 11 '15 at 15:23
  • 1
    @roryap without the `class` constraint, `new Test2()` would be valid. The compiler has no way of knowing you won't do that unless you add the class constraint to `TDerived`. Simply specifying `where TDerived : TBase` doesn't ensure that `TBase` or `TDerived` are classes. – CoderDennis Feb 11 '15 at 15:23
  • 2
    @roryap What you're talking about is a C# rule. In CIL, this rule doesn't apply (all C# structs derive from a class). When a project is built, it's not C# anymore, it's CIL. But you can still use that code (for example, those generic types) from a C# project. Thus, I'm guessing the C# compiler simply doesn't assume that its own rules are also enforced implicitly by all possible CLI languages. So, instead, it requires an explicit constraint. – Theodoros Chatzigiannakis Feb 11 '15 at 15:26
  • This had me stumped, even though I had just explained why one had to use `default(T)` instead of `null` for a generic type. Thanks! – Thomas S. Trias Mar 16 '17 at 16:00