【问题标题】:Is this a covariance bug in C# 4?这是 C# 4 中的协方差错误吗?
【发布时间】:2010-05-06 17:49:05
【问题描述】:

在下面的代码中,我希望能够从elements 隐式转换为baseElements,因为TBase 可以隐式转换为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;
    }
}

但是,我收到了评论中提到的错误。

引用规范:

如果T 是使用变体类型参数T&lt;X1, …, Xn&gt; 声明的接口或委托类型,则类型T&lt;A1, …, An&gt; 可以变型转换为类型T&lt;B1, …, Bn&gt;,并且对于每个变体类型参数Xi 一个以下持有:

  • Xi 是协变的,存在从 AiBi 的隐式引用或身份转换

  • Xi 是逆变的,存在从 BiAi 的隐式引用或身份转换

  • Xi 是不变的,存在从AiBi 的身份转换

检查我的代码,它似乎与规范一致:

  • IEnumerable&lt;out T&gt; 是一个接口类型

  • IEnumerable&lt;out T&gt; 使用变体类型参数声明

  • T 是协变的

  • 存在从TBaseIBase 的隐式引用转换

那么 - 这是 C# 4 编译器中的错误吗?

【问题讨论】:

  • 当你显式转换时会发生什么?编译器说有一个。既然你在低调,这有点道理..?
  • 只是为了明确说明 - 这是你的最后一个项目符号“存在从 TBase 到 IBase 的隐式引用转换”这是不真实的(除非你添加 : class)。它可能是可分配的,但它不一定是引用转换。没有: class,它是一个“受约束的”转换,这是一种神奇的方式,可以让相同的 IL 以相同的方式在引用类型和值类型上调用方法(包括属性访问器):msdn.microsoft.com/en-us/library/…
  • Charles:你错了 - 第一个作业有效(Works on My Machine (TM))。
  • 马克:对——我的错。它不是引用类型,所以没有引用转换。但是我留下了一个问题,为什么第一个作业有效?

标签: c# generics covariance


【解决方案1】:

Variance 仅适用于引用类型(或存在 identity 转换)。不知道TBase是引用类型,除非你加: class

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

因为我可以写一个:

public struct Evil : IBase {}

【讨论】:

  • 好答案 - 添加类约束有效。然而——这又引发了另一个问题:为什么第一个作业有效?
  • @Omer - 因为IBaseIDerived 被视为引用;只有TBase 尚未决定。
  • @MarcGravell:更准确地说,虽然接口可以由非堆(值)类型实现,但接口类型的存储位置将始终保存堆对象参考。例如,List&lt;IEnumerator&lt;int&gt;&gt; 持有对实现IEnumerator&lt;int&gt; 的堆对象的引用,而如果TList&lt;int&gt;.EnumeratorList&lt;T&gt; where T:IEnumerator&lt;int&gt; 将持有该结构类型的实例。请注意,如果尝试将List&lt;int&gt;.Enumerator 存储在List&lt;IEnumerator&lt;int&gt;&gt; 中,系统会将其字段复制到新堆对象并存储对它的引用。
  • 在其他情况下,这正是泛型的优点:如果类型参数T 是值类型,那么我们就不会装箱。例如,如果T 恰好是Int32,则List&lt;T&gt; 不是装箱整数列表。但是对于接口我们有装箱,例如var li = new List&lt;IFormattable&gt; { 2, 4, 6, }; 给出了一个盒子列表。
【解决方案2】:

Marc 是正确的 - 我正要粘贴相同的回复。

查看协方差和逆变常见问题解答:

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

来自常见问题解答:

“仅当类型参数是引用类型时才支持变体。”

值类型不支持方差

以下内容也无法编译:

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

【讨论】:

  • 更符合问题的是,以下内容也不起作用:IEnumerable&lt;IComparable&gt; comparables = new List&lt;int&gt;();
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-09-01
  • 1970-01-01
  • 2011-01-01
  • 2016-10-24
  • 1970-01-01
  • 2019-07-17
  • 1970-01-01
相关资源
最近更新 更多