Is this a covariance bug in C# 4?

2019-01-06 14:27发布

问题:

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?

回答1:

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 {}


回答2:

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.