And*_*ech 123 c# polymorphism out-parameters ref-parameters
请考虑以下事项:
class A {}
class B : A {}
class C
{
C()
{
var b = new B();
Foo(b);
Foo2(ref b); // <= compile-time error:
// "The 'ref' argument doesn't match the parameter type"
}
void Foo(A a) {}
void Foo2(ref A a) {}
}
Run Code Online (Sandbox Code Playgroud)
为什么会发生上述编译时错误?两者ref和out参数都会发生这种情况.
Eri*_*ert 168
=============
更新:我使用此答案作为此博客条目的基础:
有关此问题的更多评论,请参阅博客页面.谢谢你提出的好问题.
=============
让我们假设你有课Animal,Mammal,Reptile,Giraffe,Turtle和Tiger,具有明显的子类关系.
现在假设你有一个方法void M(ref Mammal m). M可以读写m.
你能把类型的变量传递
Animal给M?
不.该变量可以包含a Turtle,但M会假设它只包含哺乳动物.A Turtle不是Mammal.
结论1:ref参数不能"更大".(动物比哺乳动物多,所以变量越来越大,因为它可以包含更多东西.)
你能把类型的变量传递
Giraffe给M?
号M可以写入m,并且M可能要编写一个Tiger成m.现在你已经把一个Tiger变量放入一个实际上是类型的变量中Giraffe.
结论2:ref参数不能"变小".
现在考虑N(out Mammal n).
你能把类型的变量传递
Giraffe给N?
号N可以写n,也N可能想写一个Tiger.
结论3:out参数不能"变小".
你能把类型的变量传递
Animal给N?
嗯.
好吧,为什么不呢? N无法读取n,它只能写给它,对吧?你写一个Tiger类型的变量,Animal你已经设置好了吧?
错误.该规则不是" N只能写入n".
简要说明如下:
1)在正常返回之前N必须写入.(如果抛出,所有投注均已关闭.)nNN
2)在从中读取内容之前N必须写一些n内容n.
这允许这一系列事件:
x类型的字段Animal.x作为out参数传递给N.N写入Tiger到n,这是一个别名x.Turtle成x.N试图读取内容n,并发现Turtle它认为是类型的变量Mammal.显然,我们想把它变成非法的.
结论4:out参数不能变得"更大".
最后的结论:参数和参数都ref不会out改变它们的类型.否则就是破坏可验证的类型安全性.
如果基本类型理论中的这些问题让您感兴趣,请考虑阅读我关于协方差和逆变在C#4.0中如何工作的系列.
mac*_*kow 29
因为在这两种情况下,您必须能够为ref/out参数赋值.
如果你试图将b作为参考传递给Foo2方法,并且在Foo2中你试图确定a = new A(),这将是无效的.
同样的原因,你不能写:
B b = new A();
Run Code Online (Sandbox Code Playgroud)
Ale*_*lli 10
你正在努力解决协方差(和逆变)的经典OOP问题,请参阅维基百科:就像这个事实可能违背直觉期望一样,在数学上不可能允许替换派生类来代替基类的可变(可赋值)参数(和也是容器,其项目是可分配的,出于同样的原因),同时仍然尊重Liskov的原则.为什么会这样,在现有答案中勾勒出来,并在这些维基文章及其链接中进行更深入的探讨.
OOP语言似乎这样做,同时保持传统的静态类型安全是"作弊"(插入隐藏的动态类型检查,或要求编译时检查所有来源检查); 根本的选择是:要么放弃这种协方差并接受从业者的困惑(如C#在这里做的那样),要么转向动态类型化方法(作为第一个OOP语言,Smalltalk,确实如此),或者转向不可变(单一 - 赋值)数据,就像函数式语言一样(在不变性的情况下,你可以支持协方差,还可以避免其他相关的谜题,例如你在可变数据世界中不能拥有Square子类Rectangle这一事实).
考虑:
class C : A {}
class B : A {}
void Foo2(ref A a) { a = new C(); }
B b = null;
Foo2(ref b);
Run Code Online (Sandbox Code Playgroud)
它会违反类型安全
小智 5
虽然其他响应已经简洁地解释了这种行为背后的原因,但我认为值得一提的是,如果您确实需要做这种性质的事情,您可以通过将 Foo2 变成通用方法来完成类似的功能,如下所示:
class A {}
class B : A {}
class C
{
C()
{
var b = new B();
Foo(b);
Foo2(ref b); // <= no compile error!
}
void Foo(A a) {}
void Foo2<AType> (ref AType a) where AType: A {}
}
Run Code Online (Sandbox Code Playgroud)