为什么'ref'和'out'支持多态?
采取以下措施:
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) {} }
为什么会发生上述编译时错误? 这与ref
和out
参数一起发生。
=============
更新:我用这个答案作为这个博客条目的基础:
为什么ref和out参数不允许types变化?
请参阅博客页面以获取关于此问题的更多评论。 谢谢你的好问题。
=============
假设你有Animal
, Mammal
, Reptile
, Giraffe
, Turtle
和Tiger
类,有明显的亚类关系。
现在假设你有一个方法void M(ref Mammal m)
。 M
可以读写m
。
你可以传递一个types为
Animal
的variables给M
吗?
不,这个variables可以包含一只Turtle
,但是M
会认为它只包含了哺乳动物。 一只Turtle
不是一只Mammal
。
结论1 : ref
参数不能做得“大”。 (有比动物更多的动物,所以variables越来越大,因为它可以包含更多的东西。)
你能把一个
Giraffe
types的variables传给M
吗?
不, M
可以写给m
, M
可能要写一个Tiger
到m
。 现在你已经把一个Tiger
放入了一个实际上是Giraffe
types的variables。
结论2 : ref
参数不能做得“小”。
现在考虑N(out Mammal n)
。
你能把一个
Giraffe
types的variables传给N
吗?
N
可以写到n
, N
可能要写一个Tiger
。
结论3 : out
参数不能做得“小”。
你可以传递一个
Animal
types的variables到N
吗?
嗯。
那么,为什么不呢? N
不能从n
读取,只能写入它,对不对? 你写了一个Tiger
Animal
types的variables,你都准备好了,对吗?
错误。 规则不是“ N
只能写入n
”。
规则是,简要地说:
1) N
必须在N
正常返回之前写入n
。 (如果N
投,所有投注都closures。)
2)在从n
读取某些内容之前, N
必须先写入n
。
这允许这一系列事件:
- 声明一个
Animal
types的字段x
- 将
x
作为out
parameter passing给N
-
N
将一个Tiger
写入n
,这是x
的别名。 - 在另一个线程上,有人把一只
Turtle
写入x
。 -
N
尝试读取n
的内容,并在它认为是Mammal
types的variables中发现一只Turtle
。
很明显,我们想把这个做成非法的。
结论4 : out
参数不能做得“大”。
最后的结论 : ref
和参数都不会改变它们的types。 否则就是打破可validation的types安全。
如果基本types理论中的这些问题对您感兴趣,请考虑阅读我的系列文章,了解C#4.0中的协变和反变换是如何工作的 。
因为在这两种情况下,你都必须能够给ref / out参数赋值。
如果您尝试将b传入Foo2方法作为参考,并且在Foo2中您尝试指定= new A(),则这将无效。
同样的理由你不能写:
B b = new A();
你正在为经典的协方差问题(和协变性 )苦苦挣扎,请参阅维基百科(wikipedia) :就像这个事实可能违背直觉的预期一样,在math上不可能让派生类代替基本代替variables(可赋值)也是容器的项目是可分配的,出于同样的原因),同时仍然尊重Liskov的原则 。 为什么在现有的答案中勾画了这些,并在这些wiki文章和链接中进行了更深入的探索。
OOP语言似乎这样做,而传统的静态types安全是“作弊”(插入隐藏的dynamictypes检查,或需要编译时检查所有来源检查); 最基本的select是:或者放弃这个协变,接受从业者的困惑(就像C#在这里所做的那样),或者转向dynamictypes化的方法(如第一个OOP语言Smalltalk所做的那样),或者转向不可变的赋值)数据,就像函数式语言一样(在不变性的情况下,你可以支持协变性,也可以避免其他相关的难题,例如在可变数据世界中你不能有Square子类Rectangle的事实)。
考虑:
class C : A {} class B : A {} void Foo2(ref A a) { a = new C(); } B b = null; Foo2(ref b);
这将违反types安全
因为赋予Foo2
一个ref B
会导致格式错误的对象,因为Foo2
只知道如何填充A
一部分。
是不是编译器告诉你,它会希望你明确地转换对象,以确保你知道你的意图是什么?
Foo2(ref (A)b)
从安全的angular度来看是有道理的,但是如果编译器给出了一个警告而不是错误的话,我会更喜欢它,因为通过引用传递的polymoprhic对象是合法的。 例如
class Derp : interfaceX { int somevalue=0; //specified that this class contains somevalue by interfaceX public Derp(int val) { somevalue = val; } } void Foo(ref object obj){ int result = (interfaceX)obj.somevalue; //do stuff to result variable... in my case data access obj = Activator.CreateInstance(obj.GetType(), result); } main() { Derp x = new Derp(); Foo(ref Derp); }
这不会编译,但会工作吗?
如果你使用你的types的实际例子,你会看到它:
SqlConnection connection = new SqlConnection(); Foo(ref connection);
现在你有你的function,采取祖先 ( 即 Object
):
void Foo2(ref Object connection) { }
这可能是什么错误?
void Foo2(ref Object connection) { connection = new Bitmap(); }
你只是设法分配一个Bitmap
到你的SqlConnection
。
这不好。
再试一次:
SqlConnection conn = new SqlConnection(); Foo2(ref conn); void Foo2(ref DbConnection connection) { conn = new OracleConnection(); }
你在SqlConnection
的顶部填充了一个OracleConnection
。