【问题标题】:subtype and covariant return type子类型和协变返回类型
【发布时间】:2012-01-24 17:06:08
【问题描述】:

我找到了解释What is a covariant return type?,但我并不聪明地理解这一切。

我认为理论上协变返回类型是一个函数返回的类型,该函数与返回类型不同的内置基类函数具有相同的签名。

class Base{
   TypeX func( int i ){return typex;} // builtin function
};
class Derived:Base{
   TypeY func(int i){return typey;}
}

我对这种所谓的协变返回类型的理解正确吗? [这个词真的让我很困惑。]

【问题讨论】:

    标签: c# java


    【解决方案1】:

    这将是协变返回类型的示例:

    class Food {}
    class Fruit : Food {}
    class FoodEater
    {
        public virtual Food GetFavouriteFood() { ... }
    }
    class FruitEater : FoodEater
    {
        public override Fruit GetFavouriteFood() { ... }
    }
    

    在支持返回类型协变的语言中,这是合法的。那就是返回食物的方法可以被返回水果的方法覆盖,因为水果是食物的一种。之所以称为“协方差”,是因为“方差”在同一个方向

    A Fruit may be used as a Food, therefore:
    A Fruit-returning-method may be used as a Food-returning-method
    

    看看方差是如何在同一个方向

    与参数类型逆变对比:

    class Food {}
    class Fruit : Food {}
    class Apple : Fruit {}
    class Orange : Fruit {}
    class Cake : Food {}
    class FruitComparer
    {
        public virtual bool Compare(Fruit f1, Fruit f2) { ... }
    }
    class FoodComparer : FruitComparer
    {
        public override bool Compare(Food f1, Food f2) { ... }
    }
    

    FruitComparer 可以比较苹果和橙子。 FoodComparer 还可以将苹果与橙子进行比较,但可以进行更多的比较——它可以将苹果与蛋糕进行比较,或者将蛋糕与橙子进行比较,或者其他任何东西。

    在支持参数类型逆变的语言中,这是合法的。看看现在方差方向如何反转

    A Fruit may be used as a Food, therefore
    A Food-taking-method may be used as a Fruit-taking-method
    

    现在关系已经倒退,所以它是逆变的

    C# 不支持两种方法变体用于虚拟重载。虚拟方法覆盖必须完全匹配。但是,C# 确实支持方法组委托转换通用委托类型转换的两种方法变化。

    【讨论】:

    • 我总是忘记哪一个是反方差,哪个是协方差。既然你解释了意思,我可能真的记得它。谢谢!
    • 我总是忘记哪个是哪个,但我尽量记住它们:Convariant -> out(较短的单词,较长的关键字)Convariant -> in(较长的单词,较短的关键字)
    • @lukas 如果你正确拼写“covariant”,没有“n”,你也可以使用将“co”与“o”联系起来的助记符,因此“out”和“contra”与“n”,因此是“in”。这仅在 C# 中有所帮助:-)
    • 协变 -> 联合变化 -> 同方向变化(co:联合,如合作 = 联合操作)。逆变 -> 反向变化 -> 向相反方向变化(相反:反对)。
    【解决方案2】:

    与 java 不同,C# 不支持协变返回类型。我相信这是由于 C# 属性的实现,如果允许协变返回类型,以下是可能的:

    class TypeX { }
    
    class TypeY : TypeX { }
    
    class Base
    {
        public virtual TypeX Prop { get; set; }
    }
    
    class Derived : Base
    {
        public override TypeY Prop { get; set; }
    }
    
    Derived derived = new Derived();
    derived.Prop = new TypeY(); // Valid
    
    Base @base = derived;
    @base.Prop = new TypeX(); // Invalid - As this would be using the derived property which should be of TypeY
    

    有关更多信息,请参阅Eric Lippert's 答案。

    【讨论】:

    • 既然属性设置器是一个接受属性类型输入参数的函数,为什么要允许重写一个函数以要求更具体的输入参数?就个人而言,我认为我宁愿让属性 getter 和 setter 是完全独立的函数,这些函数被标记为允许 property-get 或 property-set 语法,因为在某些情况下,属性 setter 具有其他类型的重载是有意义的比 getter 的类型,但其他读写属性必须具有不变的类型。
    【解决方案3】:

    当且仅当TypeY 派生自TypeX 时,您的理解才是正确的。

    【讨论】:

      【解决方案4】:

      协变返回类型是一种从函数返回“更窄”类型的方法。 “较窄”类型将是原始返回类型的子类。

       class A {
       }
      
       class B extends A {
       }
      
       // Classes demonstrating method overriding:
      
       class C {
           A getFoo() {
               return new A();
           }
       }
      
       class D extends C {
           B getFoo() {
               return new B();
           }
       }
      

      我们可以从getFoo() 返回B 的事实是协变返回。需要注意的是,虽然overriding 不允许您更改返回类型,但java 5.0 允许使用协变类型。

      【讨论】:

        【解决方案5】:

        允许这种类型的重载的特性称为协变返回类型iff TypeYTypeX 的子类型。维基百科对该功能进行了很好的讨论

        【讨论】:

          【解决方案6】:

          您刚刚演示了在 Java 中使用协变返回类型的能力,是的 - 假设 TypeYTypeX 的子类。关键是任何只“知道”Base 中指定的签名的调用者仍然可以,因为Derived 实现返回的任何TypeY 仍然是有效的TypeX 引用。请注意,它确实仅适用于引用类型(类) - 使用无效:

          // In the base class
          long func() { ... }
          
          // In the derived class
          @Override int func() { ... }
          

          ...即使存在从intlong 的隐式转换,并且int本身不是long 值。 representational 有效性与引用不同(String 引用 Object 引用 - 如果您知道一组特定的位是String 引用,您可以将那些完全相同的位视为Object 引用。)。

          C#支持协变返回类型,但从 C# 4 开始,它确实支持在接口和委托上声明的通用方差。例如,IEnumerable<T> 在 .NET 4 中声明为

          public interface IEnumerable<out T> { ... }
          

          out在界面中显示T协变的,也就是说这个转换是有效的:

          IEnumerable<string> strings = ...;
          IEnumerable<object> objects = strings;
          

          反向分配将有效,因为任意object引用序列可能string引用的有效序列。

          有关此主题的更多信息,请参阅MSDN(或搜索 c# 通用方差)。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2011-09-02
            • 2017-02-28
            • 1970-01-01
            • 2021-02-24
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多