为什么“ ref”和“ out”不支持多态?


Answers:


169

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

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

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

有关此问题的更多评论,请参见博客页面。感谢您提出的重大问题。

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

让我们假设你有课AnimalMammalReptileGiraffeTurtleTiger,具有明显的子类关系。

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


您可以将类型的变量传递AnimalM吗?

否。该变量可以包含Turtle,但M将假定它仅包含哺乳动物。A Turtle不是Mammal

结论1ref不能使参数“更大”。(动物比哺乳动物多,因此该变量变得“更大”,因为它可以包含更多的东西。)


您可以将类型的变量传递GiraffeM吗?

M可以写入m,并且M可能要编写一个Tigerm。现在,您已将a Tiger放入实际类型为的变量中Giraffe

结论2ref不能使参数“更小”。


现在考虑N(out Mammal n)

您可以将类型的变量传递GiraffeN吗?

N可以写n,并且N可能要写一个Tiger

结论3out不能使参数“更小”。


您可以将类型的变量传递AnimalN吗?

好吧,为什么不呢? N无法读取n,只能写入,对不对?您将a写入Tiger类型的变量,Animal就已经准备好了,对吧?

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

这些规则是:

1)在正常返回之前N必须先写入。(如果抛出,则所有投注均无效。)nNN

2)N必须先写入内容,n然后才能从中读取内容n

这允许发生以下一系列事件:

  • 声明一个xtype 字段Animal
  • x作为out参数传递给N
  • N写入一个,Tigern的别名x
  • 在另一个线程,有人写了一Turtlex
  • N试图阅读的内容n,并发现Turtle它认为是类型的变量Mammal

显然,我们想使之非法。

结论4out不能将参数设为“更大”。


最后得出结论无论是ref也不out参数可能有所不同它们的类型。否则将破坏可验证的类型安全。

如果您对基本类型理论中的这些问题感兴趣,请考虑阅读我的有关协方差和反方差如何在C#4.0中工作的系列文章


6
+1。使用可以清楚地说明问题的真实类示例进行出色的解释(即-用A,B和C进行解释将更加难以证明其不起作用的原因)。
格兰特·瓦格纳

4
在阅读这个思考过程时,我感到很谦虚。我想我最好回到书上!
Scott McKenzie

在这种情况下,我们真的不能使用Abstract类变量作为参数并传递其派生类对象!
Prashant Cholachagudda,2009年

但是,为什么out不能将参数设为“更大”?您描述的顺序可以应用于任何变量,而不仅限于out参数变量。而且,读者也需要Mammal在尝试访问参数值之前将其转换为Mammal,如果不考虑
周全

29

因为在两种情况下,您都必须能够将值分配给ref / out参数。

如果您尝试将b传递给Foo2方法作为引用,并且在Foo2中尝试使用a = new A(),则此方法无效。
同样的原因,你不能写:

B b = new A();

+1直截了当,并很好地解释了原因。
Rui Craveiro 09年

10

您正在努力应对协方差(和协变)的经典OOP问题,请参阅Wikipedia:尽管此事实可能违背直觉的期望,但从数学上讲,它不可能用派生类替换基类来代替可变的(可分配的)参数(并且也是出于相同的原因而可分配项目的容器),同时仍遵守Liskov原则。在现有答案中概述了为什么这样做,并在这些Wiki文章及其链接中进行了更深入的探讨。

在保持传统的静态类型安全的同时,似乎这样做的OOP语言却在“作弊”(插入隐藏的动态类型检查,或要求对所有要检查的源进行编译时检查);基本的选择是:要么放弃这种协方差,接受从业者的困惑(就像C#所做的那样),要么转向动态类型化方法(就像最早的OOP语言Smalltalk一样),或者转向不可变(单-数据),就像功能语言一样(在不变性下,您可以支持协方差,并且还可以避免其他相关的难题,例如,在可变数据世界中不能拥有Square子类Rectangle)。



4

尽管其他回答简洁地解释了此行为的原因,但我认为值得一提的是,如果您确实需要执行此类操作,则可以通过将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 {}  
}

2

因为给出Foo2a ref B会导致对象格式错误,因为Foo2只知道如何填充的A一部分B


0

难道不是编译器告诉您希望您显式转换对象,以确保您知道自己的意图吗?

Foo2(ref (A)b)

无法做到这一点,“ ref或out参数必须是可分配的变量”

0

从安全的角度讲是有道理的,但是如果编译器给出警告而不是错误,我将更喜欢它,因为通过引用传递的多对象对象是合法使用的。例如

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

这不会编译,但是行得通吗?


0

如果您对类型使用实际示例,则会看到以下内容:

SqlConnection connection = new SqlConnection();
Foo(ref connection);

现在您有了使用祖先 Object)的函数:

void Foo2(ref Object connection) { }

那可能有什么问题呢?

void Foo2(ref Object connection)
{
   connection = new Bitmap();
}

您刚刚设法将分配BitmapSqlConnection

不好


与其他人再试一次:

SqlConnection conn = new SqlConnection();
Foo2(ref conn);

void Foo2(ref DbConnection connection)
{
    conn = new OracleConnection();
}

您将自己的东西塞满OracleConnectionSqlConnection


0

就我而言,我的函数接受了一个对象,我什么也不能发送,所以我只是做了

object bla = myVar;
Foo(ref bla);

那行得通

我的Foo在VB.NET中,它检查内部的类型并执行很多逻辑

如果我的回答是重复的,但其他人的回答太长,我深表歉意

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.