37
votes

In the following piece of code I expected to be able to implicitly cast from elements to baseElements because TBase is implicitly convertible to IBase.

public interface IBase { }
public interface IDerived : IBase { }
public class VarianceBug
{
    public void Foo<TBase>() where TBase : IBase
    {
        IEnumerable<TBase> elements = null;
        IEnumerable<IDerived> derivedElements = null;
        IEnumerable<IBase> baseElements;

        // works fine
        baseElements = derivedElements;

        // error CS0266: Cannot implicitly convert type 
        //   'System.Collections.Generic.IEnumerable<TBase>' to 
        //   'System.Collections.Generic.IEnumerable<IBase>'. 
        //   An explicit conversion exists (are you missing a cast?)
        baseElements = elements;
    }
}

However, I get the error that is mentioned in the comment.

Quoting from the spec:

A type T<A1, …, An> is variance-convertible to a type T<B1, …, Bn> if T is either an interface or a delegate type declared with the variant type parameters T<X1, …, Xn>, and for each variant type parameter Xi one of the following holds:

  • Xi is covariant and an implicit reference or identity conversion exists from Ai to Bi

  • Xi is contravariant and an implicit reference or identity conversion exists from Bi to Ai

  • Xi is invariant and an identity conversion exists from Ai to Bi

Checking my code, it appears to be consistent with the spec:

  • IEnumerable<out T> is an interface type

  • IEnumerable<out T> is declared with variant type parameters

  • T is covariant

  • an implicit reference conversion exists from TBase to IBase

So - is it a bug in the C# 4 compiler?

2
What happens when you do cast explicitly? The compiler says that there is one. Since you are downcasting it kinda makes sense..?flq
Just to make it explicit - it is your last bullet "an implicit reference conversion exists from TBase to IBase" that is untrue (unless you add : class). It may be assignable, but it is not necessarily a reference-conversion. Without the : class it is a "constrained" conversion, which is some magic that lets the same IL call methods (including property accessors) on reference-types and value-types in the same way: msdn.microsoft.com/en-us/library/…Marc Gravell
Charles: you're wrong - the first assignment works (Works on My Machine (TM)).Omer Mor
Mark: right - my bad. It is not a reference type, so there's no reference conversion. However I'm left with the question of why does the first assignment works?Omer Mor

2 Answers

51
votes

Variance only works for reference-types (or there is an identity conversion). It is not known that TBase is reference type, unless you add : class:

 public void Foo<TBase>() where TBase : class, IBase

since I could write a:

public struct Evil : IBase {}
14
votes

Marc is correct - I was just about to paste the same response.

See the Covariance & Contravariance FAQ:

http://blogs.msdn.com/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx

From the FAQ:

"Variance is supported only if a type parameter is a reference type."

Variance is not supported for value types

The following doesn’t compile either:

// int is a value type, so the code doesn't compile.
IEnumerable<Object> objects = new List<int>(); // Compiler error here.