为什么'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) {} } 

为什么会发生上述编译时错误? 这与refout参数一起发生。

=============

更新:我用这个答案作为这个博客条目的基础:

为什么ref和out参数不允许types变化?

请参阅博客页面以获取关于此问题的更多评论。 谢谢你的好问题。

=============

假设你有AnimalMammalReptileGiraffeTurtleTiger类,有明显的亚类关系。

现在假设你有一个方法void M(ref Mammal m)M可以读写m


你可以传递一个types为Animal的variables给M吗?

不,这个variables可以包含一只Turtle ,但是M会认为它只包含了哺乳动物。 一只Turtle不是一只Mammal

结论1ref参数不能做得“大”。 (有比动物更多的动物,所以variables越来越大,因为它可以包含更多的东西。)


你能把一个Giraffetypes的variables传给M吗?

不, M可以写给mM可能要写一个Tigerm 。 现在你已经把一个Tiger放入了一个实际上是Giraffetypes的variables。

结论2ref参数不能做得“小”。


现在考虑N(out Mammal n)

你能把一个Giraffetypes的variables传给N吗?

N可以写到nN可能要写一个Tiger

结论3out参数不能做得“小”。


你可以传递一个Animaltypes的variables到N吗?

嗯。

那么,为什么不呢? N不能从n读取,只能写入它,对不对? 你写了一个Tiger Animaltypes的variables,你都准备好了,对吗?

错误。 规则不是“ N只能写入n ”。

规则是,简要地说:

1) N必须在N正常返回之前写入n 。 (如果N投,所有投注都closures。)

2)在从n读取某些内容之前, N必须先写入n

这允许这一系列事件:

  • 声明一个Animaltypes的字段x
  • x作为outparameter passing给N
  • N将一个Tiger写入n ,这是x的别名。
  • 在另一个线程上,有人把一只Turtle写入x
  • N尝试读取n的内容,并在它认为是Mammaltypes的variables中发现一只Turtle

很明显,我们想把这个做成非法的。

结论4out参数不能做得“大”。


最后的结论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