子类型和协变返回类型

时间:2012-01-24 17:06:09

标签: c# java

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

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

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

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

6 个答案:

答案 0 :(得分:6)

这是协变返回类型的一个例子:

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

在支持返回类型协方差的语言中,这是合法的。那是返回Food的方法可以被返回Fruit的方法覆盖,因为Fruit是一种Food 。它被称为“协方差”,因为“方差”在同一方向

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#支持方法组委派转化通用委托类型转换的两种方法差异。

答案 1 :(得分:3)

与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答案。

答案 2 :(得分:1)

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

答案 3 :(得分:1)

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

 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();
     }
 }

我们可以从B返回getFoo()这一事实是共同变体回归。重要的是要注意,虽然overriding不允许更改返回类型,但java 5.0允许使用共变类型。

答案 4 :(得分:0)

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

答案 5 :(得分:0)

在Java中使用协变返回类型的能力就是你刚才所展示的,是的 - 假设TypeYTypeX的子类。关键是,只有“知道”Base中指定的签名的任何呼叫者仍然可以,因为TypeY实现返回的任何Derived仍然是有效的TypeX参考。请注意, 仅适用于引用类型(类) - 使用时无效:

// In the base class
long func() { ... }

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

...即使存在从intlong的隐式转换,int值本身不是 a long值。对于引用(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;

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

有关此主题的更多信息,请参阅MSDN(或搜索c#generic variance)。