为什么不“裁判”和“出”支持多态?(Why doesn't 'ref' an

2019-06-17 19:33发布

看看下面:

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论点。

Answer 1:

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

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

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

关于此问题的详细解说,请参阅博客页面。 感谢伟大的问题。

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

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

现在,假设你有一个方法void M(ref Mammal m)M可以同时读取和写入m


你可以通过类型的变量Animal ,以M

号该变量可能包含一个Turtle ,但M会认为它仅包含哺乳动物。 一个Turtle是不是Mammal

结论1: ref参数不能取得“更大”。 (还有更多的动物比哺乳动物,所以变数也越来越“大”,是因为它可以包含更多的东西。)


你可以通过类型的变量GiraffeM

M可以写入m ,而M可能要到写Tigerm 。 现在你已经把Tiger变成一个变量,它实际上是类型Giraffe

结论2: ref参数不能进行“较小”。


现在考虑N(out Mammal n)

你可以通过类型的变量GiraffeN

N可以写信给nN可能要编写一个Tiger

结论3: out参数不能进行“更小”。


你可以通过类型的变量AnimalN

嗯。

那么,为什么不呢? N无法读取n ,它只能写,对不对? 你写了一个Tiger到类型的变量Animal和你所有的设置,对不对?

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

规则是,简单:

1) N具有写入n之前N正常返回。 (如果N抛出,所有的赌注都关闭)。

2) N有写东西n它读取的东西之前n

这使得这一系列事件:

  • 声明字段x型的Animal
  • 通过x作为out参数N
  • N写入Tigern ,这是一个别名x
  • 在另一个线程,有人写了一个Turtlex
  • N尝试读取的内容n ,并发现了Turtle在它认为是什么类型的变量Mammal

显然,我们想使非法。

结论4: out参数不能作出“较大”。


最后得出结论无论是ref还是out参数可能有所不同它们的类型。 如果不这样做就是要打破可验证的类型安全。

如果基本类型理论的兴趣,你这些问题,可以阅读我的系列协方差和逆变如何工作在C#4.0 。



Answer 2:

因为在这两种情况下,你必须能够赋值给REF /输出参数。

如果你试图通过B插入foo2的方法为参考,并在foo2的尝试assing一个新= A(),这将是无效的。
你可以不写同样的原因:

B b = new A();


Answer 3:

你和协方差 (和逆变)的经典OOP问题所困扰,请参阅维基百科 :竟有这样的事实可能违背直觉的预期,这是数学上不可能允许派生类的替代替代基地的人对可变(分配)参数(和也是容器,其项目转让的,只是同样的原因),同时仍然尊重里氏原理 。 为什么会这样是在现有的答案勾勒,并在这些维基文章和链接更深入探讨由此。

出现这样做的同时保持传统的静态类型安全的“作弊” OOP语言(插入隐藏动态类型检查,或要求的所有来源的编译时检查,以检查); 根本的选择是:要么放弃对这个协方差和接受从业者的困惑(如C#在这里所做的),或移动到一个动态类型的方法(因为第一个面向对象的语言,Smalltalk的,所做的那样),或者移动到不可变的(单分配)数据,如函数式语言做(下不变性,可以支持协方差,还可以避免其他相关的难题,如事实,你不能有一个可变数据世界广场的子类矩形)。



Answer 4:

考虑:

class C : A {}
class B : A {}

void Foo2(ref A a) { a = new C(); } 

B b = null;
Foo2(ref b);

这将违反类型安全



Answer 5:

因为给Foo2一个ref B将导致畸形的对象,因为Foo2只知道如何填写A的一部分B



Answer 6:

而其他的反应已经说明简洁背后这种行为的理由,我认为这是值得一提的是,如果你真的需要做这种性质的东西,你可以通过使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 {}  
}


Answer 7:

是不是编译器告诉你它想你明确地投的对象,以便它可以确保你知道你的意图是什么?

Foo2(ref (A)b)


Answer 8:

从安全角度看是有道理的,但我宁愿它如果编译器给了一个警告而不是错误的,因为有按引用传递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);
}

这不会编译,但它的工作?



Answer 9:

如果您使用的实际例子为你的类型,你会看到它:

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

现在,你有你的函数,它的祖先 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();
}

你酿的OracleConnection过顶你的SqlConnection



Answer 10:

在我的情况,我的函数接受一个对象,我不能送任何东西,所以我压根儿

object bla = myVar;
Foo(ref bla);

这作品

我的foo是VB.NET,它的内部检查类型,做了很多的逻辑

我很抱歉,如果我的回答是重复的,但其他太长



文章来源: Why doesn't 'ref' and 'out' support polymorphism?