StackOverflow上covariance and contravariance有一些很好的资源,但我似乎误解了逆变的基本原理。我希望这个例子有效:
public partial class WebForm1 : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
A a = new B();
B b = new A();
}
}
public class A
{
int id { get; set; }
}
public class B : A
{
}
设置a到B的工作原理是协方差,但是将b设置为新的A会因编译错误而失败。即使进行显式转换仍然会在编译时生成错误。有没有办法做到这一点,还是我完全误解了逆变?
答案 0 :(得分:6)
我是否完全误解了逆变?
是。你有完全和完全误解了“协方差”和“逆变”意味着。您已将它们与赋值兼容性混淆。
这是一个非常常见的错误。这两个概念是相关的,但它们根本不相同。
作业兼容性是一种类型的表达式可以存储在另一种类型的变量中的属性。
协方差是从类型到类型的映射保留方向分配兼容性的属性。如果Giraffe的赋值与Animal兼容,这意味着IEnumerable<Giraffe>
与IEnumerable<Animal>
的赋值兼容,则IEnumerable<T>
映射为协变。
有关详细信息,请参阅我关于此主题的文章:
设置a到B有效,这是协方差
是的,它有效。 “a”与赋值兼容与B.它不“协方差”因为没有变化。没有从类型到类型的映射,它保留了赋值“a = B”中使用的赋值兼容性的方向;什么都不是通用的。
但是将b设置为新的A会因编译错误而失败。
正确。
有办法做到这一点吗?
没有。而不是“A”和“B”,称他们为动物和长颈鹿。每个长颈鹿都是动物,所以如果变量可以容纳动物,那么它可以容纳长颈鹿。如果你试图走另一条路,你就不能把动物变成长颈鹿类型的变量。动物实际上可能是一只老虎。你为什么要被允许把老虎变成长颈鹿类型的变量?
答案 1 :(得分:5)
你做不到:
B b = new A();
因为A
只是 不是<{1}}。作业无效。我不确定我是否会称这种差异 - 它只是继承。
在B具有A没有的成员的一般情况下,您可以看到这样做是没有意义的(如果B
实际上持有对b
对象的引用):
A
其中SomeMethod仅在B中定义,但此逻辑扩展到变量本身的赋值。即使您添加了强制转换,强制转换也会进行隐式类型检查。
答案 2 :(得分:1)
逆变不是违反施法规则;它适用于以下情况:
void WithApple(Action<Apple> eat);
现在,假设你有一些知道如何吃任何水果的方法:
void EatFruit(Fruit fruit);
因为它可以吃任何种类的水果,你应该可以说:
WithApple(EatFruit);
在逆向支持之前这是不可能的,因为代表必须完全匹配。
这与协方差不同,后者是更直观的概念:
void EatAllFruit(IEnumerable<Fruit> inBasket);
你应该能够:
IEnumerable<Apple> apples = someBasket;
EatAllFruit(apples);