无法在C#中使用co / contra-variance

时间:2011-04-07 11:31:44

标签: c# c#-4.0 covariance contravariance

abstract class A<T> {
  List<T> Items { get; set; }
}

class B {}
class C : A<B> {}

class D : B {}
class E : A<D> {}

static class X {
  public A<B> GetThing(bool f) {
    return f ? new E() : new C();
  }
}

无法确定条件表达式的类型,因为“ConsoleApplication4.E”和“ConsoleApplication4.C”之间没有隐式转换

现在我得到了“为什么”(我认为),但我看不出如何编译。我想我必须创建一个定义某种方差的接口并从中继承,但我不确定。但无论如何,E()应该继承自C()

任何参赛者?

TIA

3 个答案:

答案 0 :(得分:15)

要在C#中使用通用差异,您必须满足所有以下条件:

  1. 使用C#4
  2. 变化的类型必须是通用接口或通用委托
  3. 类型参数必须标记为“in”(逆变)或“out”(协变)
  4. 类型参数注释必须产生一种类型,该类型在其所有可能的操作中都是可证明类型安全的
  5. “source”类型参数和“destination”类型参数之间必须具有标识或引用转换。
  6. 您的计划符合条件1和条件5,但不符合条件2,3或4。

    无法获得您想要的差异,因为您想要的东西会违反条件4 。观察当我们满足除条件4之外的所有条件时会发生什么:

    // Suppose this declaration were legal:
    interface IMyCollection<out T> 
    {
      List<T> Items { get; set; } 
    }
    
    class Animal {}
    class AnimalCollection : IMyCollection<Animal> 
    { 
      public List<Animal> { get; set; }
    }
    
    class Giraffe : Animal {}
    class GiraffeCollection : IMyCollection<Giraffe> 
    {
      public List<Giraffe> { get; set; } 
    }
    
    static class X 
    {
      public IMyCollection<Animal> GetThing() 
      {
        // IMyCollection is covariant in T, so this is legal.
        return new GiraffeCollection(); 
      }
    }
    
    class Tiger : Animal {}
    
    ...
    
    IMyCollection<Animal> animals = X.GetThing(); 
    // GetThing() actually returns a GiraffeCollection
    animals.Items = new List<Animal>() { new Tiger(); }
    

    长颈鹿系列现在包含一个包含老虎的动物名单。

    如果要使用方差,则必须是类型安全的。因为编译器无法将方差注释确定为类型安全,所以它拒绝了IMyCollection的声明。

    让我们看看我们如何做到这一点并保持类型安全。 问题是项目可以通过界面写入。

    interface IMyCollection<out T> 
    {
      IEnumerable<T> Items { get; }
    }
    
    class Animal {}
    class AnimalCollection : IMyCollection<Animal> 
    { 
      public IEnumerable<Animal> { get { yield return new Tiger(); } }
    }
    
    class Giraffe : Animal {}
    class GiraffeCollection : IMyCollection<Giraffe> 
    {
      public IEnumerable<Giraffe> { get { yield return new Giraffe(); } } 
    }
    
    static class X 
    {
      public IMyCollection<Animal> GetThing() 
      {
        return new GiraffeCollection();
      }
    }
    
    class Tiger : Animal {}
    
    ...
    
    MyCollection<Animal> animals = X.GetThing(); 
    // GetThing() actually returns a GiraffeCollection
    foreach(Animal animal in animals.Items) { ... } 
    // Items yields a giraffe, which is an animal
    

    完全类型安全。这将是一个合法的C#4计划。

    如果C#4中协方差和逆变的设计细节让您感兴趣,您可以考虑阅读关于该主题的十几篇文章。你可以在这里找到它们:

    http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/

    请注意,这些是按照从最近到最近的顺序列出的;从底部开始。

    如果您特别感兴趣的是确定界面注释何时有效的规则,请参阅本文(我刚刚发现并修正了一些错误,所以我很高兴我们进行了这次对话。)

    http://blogs.msdn.com/b/ericlippert/archive/2009/12/03/exact-rules-for-variance-validity.aspx

答案 1 :(得分:3)

EC之间没有关系。因此,您的返回类型必须为A<B>,这可能被视为这些类型的公共父级。此外,A必须是一个接口,其中泛型参数定义为out,以便工作:

interface A<out T> { }

class B { }
class C : A<B> { }

class D : B { }
class E : A<D> { }

static class X
{
    public static A<B> GetThing(bool f)
    {
        if (f)
        {
            return new E();
        }
        return new C();
    }
}

无法使其与ternary operator (?:)一起使用。编译器根本不喜欢它。


更新:

在@Eric Lippert在评论部分中的优秀评论之后,您可以使用三元运算符转换为A<B>

public static A<B> GetThing(bool f)
{
    return f ? (A<B>)new E() : new C();
}

答案 2 :(得分:0)

这不起作用,因为CE没有共同的父母。对于这样的事情,你需要创建一个共同的父类型,无论是类还是接口。