【问题标题】:Why classes that implement variant interfaces remain invariant?为什么实现变体接口的类保持不变?
【发布时间】:2012-10-17 21:47:50
【问题描述】:

C# 4.0 进一步扩展了泛型类型和接口的协变和逆变。一些接口(如IEnumerable<T>)是协变的,所以我可以这样做:

IEnumerable<object> ie = new List<string>();

但是这条线呢?我遇到了编译时错误

List<Object> list = new List<String>();
//Cannot implicitly convert type List<string>' to List<object>'

我的意思是,如果List&lt;T&gt; 实现IEnumerable&lt;T&gt; 为什么List&lt;T&gt; 仍然不变?是否有一个很好的反例来解释为什么在 C# 中不允许这样做?

【问题讨论】:

    标签: c# c#-4.0 ienumerable covariance


    【解决方案1】:

    首先,类在 C# 中总是不变。你不能像这样声明一个类:

    // Invalid
    public class Foo<out T>
    

    其次-对于您给出的示例更重要的是-无论如何,List&lt;T&gt; 不能在T 中声明为协变或逆变,因为它具有接受和返回类型为T 的值的成员。

    想象一下,如果它协变的。然后你可以写这个(对于明显的Fruit 类层次结构):

    List<Banana> bunchOfBananas = new List<Banana>();
    // This would be valid if List<T> were covariant in T
    List<Fruit> fruitBowl = bunchOfBananas;
    fruitBowl.Add(new Apple());
    Banana banana = bunchOfBananas[0];
    

    你希望最后一行做什么?从根本上说,您不应该将Apple 引用添加到实际执行时间类型为List&lt;Banana&gt; 的对象。如果你把一个苹果加到一堆香蕉里,它就会掉下来。相信我,我已经试过了。

    最后一行应该在类型方面是安全的 - List&lt;Banana&gt; 中的唯一值应该是 null 或对 Banana 实例或子类的引用。

    现在至于为什么类不能是协变的,即使它们可以逻辑上......我相信这会在实现级别引入问题,并且也会非常 在编程层面也有限制。例如,考虑一下:

    public class Foo<out T> // Imagine if this were valid
    {
        private T value;
    
        public T Value { get { return value; } }
    
        public Foo(T value)
        {
            this.value = value;
        }
    }
    

    这仍然可能是无效的——变量仍然是可写的,这意味着它被视为一个“in”槽。您必须将T 类型的每个变量都设为只读...这只是初学者。我强烈怀疑会有更深层次的问题。

    就纯粹的实用主义而言,CLR 从 v2 开始支持委托和接口变化 - C# 4 只是引入了公开该功能的语法。我不相信 CLR 曾经支持泛型类变体。

    【讨论】:

    • 乔恩,这如何解释有一个 Fruit 列表,您将添加从 Fruit 继承的类的实例?例如,List&lt;Fruit&gt; fruitBowl = new List&lt;Fruit&gt;(); fruitBowl.Add(new Apple()); fruitBowl.add(new Banana()); 我过去曾这样做过,它总是按预期运行。为什么 CLR 不查看 fruitBowl 的类型?是不是因为您将 fruitBowl 的值设置为首先与 Fruit 列表协变的 Bananas 列表,然后 然后 尝试将不变的 Apple 添加到同一个集合?
    • @elucid8:拥有List&lt;Fruit&gt; 并添加任意水果绝对没问题。说List&lt;Banana&gt;List&lt;Fruit&gt; 是不正确的——正是因为你不能在List&lt;Banana&gt; 中添加任意水果。我不确定我是否理解你的问题。也许在新帖子中询问,如果这没有帮助? (评论线程不适合这个。)
    • 我不同意 Jon 的观点,我认为如果我们在讨论 COVARIANCE 那么我们必须使用返回 T 的 LIST 的方法。但是 Jon 是关于方法 ADD 的,它接受T 所以我们需要谈谈 CONTRAVARIANCE,在这种情况下,所有事情都会以正确的方式进行
    • 那么您认为不可能从列表中获取值吗?那是……不是这样的。
    • 服务,是的。但我们在这里讨论的不是集合,而是 CLASS VARIANCE!
    【解决方案2】:

    如果我们要讨论向 List 添加(接受)某物(T),我们必须讨论 CONTRAVARIANCE(COVARIANCE 不允许接受),所以:

    List<Fruit> bunchOfFruits = new List<Fruit>();
    // This would be valid if List<T> were contravariant in T
    List<Banana> bunchOfBananas = bunchOfFruits;
    bunchOfBananas.Add(new Apple()); // not possible! We have compile error, coz in spite of Apple is a Fruit it is not ancestor of Banana. No logical mistakes.
    bunchOfBananas.Add(new BigBanana()); // possible coz of rules of C#! we deal with a descendant of Banana
    bunchOfBananas.Add(new Fruit()); // possible, coz CONTRAVARIANCE of List. We deal with the ancestor of Banana. No logical mistakes.
    

    因此,我们可以看到 VARIANCE 既适用于类,也适用于接口和委托(一般是类,而不仅仅是集合)。而且我认为它可以在 .NET 的未来版本中实现。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-09-30
      • 1970-01-01
      • 2023-03-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-23
      相关资源
      最近更新 更多