简体   繁体   English

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

[英]Why doesn't 'ref' and 'out' support polymorphism?

Take the following: 采取以下措施:

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

Why does the above compile-time error occur? 为什么会出现上述编译时错误? This happens with both ref and out arguments. refout参数都会发生这种情况。

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

UPDATE: I used this answer as the basis for this blog entry: 更新:我使用此答案作为此博客条目的基础:

Why do ref and out parameters not allow type variation? 为什么ref和out参数不允许类型变化?

See the blog page for more commentary on this issue. 有关此问题的更多评论,请参见博客页面。 Thanks for the great question. 感谢您提出的重大问题。

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

Let's suppose you have classes Animal , Mammal , Reptile , Giraffe , Turtle and Tiger , with the obvious subclassing relationships. 假设您有Animal类, Mammal类, ReptileGiraffeTurtleTiger类,并且具有明显的子类关系。

Now suppose you have a method void M(ref Mammal m) . 现在假设您有一个方法void M(ref Mammal m) M can both read and write m . M可以读写m


Can you pass a variable of type Animal to M ? 您可以将Animal类型的变量传递给M吗?

No. That variable could contain a Turtle , but M will assume that it contains only Mammals. 否。该变量可以包含Turtle ,但是M会假定它仅包含哺乳动物。 A Turtle is not a Mammal . Turtle不是Mammal

Conclusion 1 : ref parameters cannot be made "bigger". 结论1 :不能将ref参数设置为“更大”。 (There are more animals than mammals, so the variable is getting "bigger" because it can contain more things.) (动物比哺乳动物多,因此该变量变得“更大”,因为它可以包含更多的东西。)


Can you pass a variable of type Giraffe to M ? 您可以将Giraffe类型的变量传递给M吗?

No. M can write to m , and M might want to write a Tiger into m . M可以写入m ,并且M可能想将Tiger写入m Now you've put a Tiger into a variable which is actually of type Giraffe . 现在,您已经将Tiger放入了实际上是Giraffe类型的变量中。

Conclusion 2 : ref parameters cannot be made "smaller". 结论2 :不能使ref参数“更小”。


Now consider N(out Mammal n) . 现在考虑N(out Mammal n)

Can you pass a variable of type Giraffe to N ? 您可以将Giraffe类型的变量传递给N吗?

No. N can write to n , and N might want to write a Tiger . N可以写n ,而N可能想写Tiger

Conclusion 3 : out parameters cannot be made "smaller". 结论3 :不能使out参数“更小”。


Can you pass a variable of type Animal to N ? 您可以将Animal类型的变量传递给N吗?

Hmm.

Well, why not? 好吧,为什么不呢? N cannot read from n , it can only write to it, right? N无法从n读取,只能对其进行写入,对吗? You write a Tiger to a variable of type Animal and you're all set, right? 您将Tiger写入Animal类型的变量,就全部准备好了,对吗?

Wrong. 错误。 The rule is not " N can only write to n ". 规则不是“ N只能写入n ”。

The rules are, briefly: 这些规则是:

1) N has to write to n before N returns normally. 1) N必须在N正常返回之前写入n (If N throws, all bets are off.) (如果N投掷,则所有投注均无效。)

2) N has to write something to n before it reads something from n . 2) N必须先向n写入内容,然后才能从n读取内容。

That permits this sequence of events: 这允许发生以下一系列事件:

  • Declare a field x of type Animal . 声明一个Animal类型的字段x
  • Pass x as an out parameter to N . x作为out参数传递给N
  • N writes a Tiger into n , which is an alias for x . NTiger写入nnx的别名。
  • On another thread, someone writes a Turtle into x . 在另一个线程上,有人将Turtle写入x
  • N attempts to read the contents of n , and discovers a Turtle in what it thinks is a variable of type Mammal . N尝试读取n的内容,并在它认为是Mammal类型的变量中发现了Turtle

Clearly we want to make that illegal. 显然,我们想使之非法。

Conclusion 4 : out parameters cannot be made "larger". 结论4 :不能将out参数设为“更大”。


Final conclusion : Neither ref nor out parameters may vary their types. 最终结论refout参数都不ref其类型。 To do otherwise is to break verifiable type safety. 否则将破坏可验证的类型安全性。

If these issues in basic type theory interest you, consider reading my series on how covariance and contravariance work in C# 4.0 . 如果您对基本类型理论中的这些问题感兴趣,请考虑阅读我的有关协方差和反方差如何在C#4.0中工作的系列文章

Because in both cases you must be able to assign value to ref/out parameter. 因为在两种情况下,您都必须能够将值分配给ref / out参数。

If you try to pass b into Foo2 method as reference, and in Foo2 you try to assing a = new A(), this would be invalid. 如果您尝试将b传递给Foo2方法作为引用,并且在Foo2中尝试使用a = new A(),则此方法无效。
Same reason you can't write: 不能写的原因相同:

B b = new A();

You're struggling with the classic OOP problem of covariance (and contravariance), see wikipedia : much as this fact may defy intuitive expectations, it's mathematically impossible to allow substitution of derived classes in lieu of base ones for mutable (assignable) arguments (and also containers whose items are assignable, for just the same reason) while still respecting Liskov's principle . 您正在努力应对协方差 (和协方差 )的经典OOP问题,请参阅Wikipedia :尽管此事实可能违背了直观的期望,但从数学上讲,它不可能用派生类替代基类来代替可变的(可分配的)参数(并且也是出于相同原因可分配项目的容器),同时仍要遵守Liskov原则 Why that is so is sketched in the existing answers, and explored more deeply in these wiki articles and links therefrom. 在现有答案中概述了为什么这样做,并在这些Wiki文章及其链接中进行了更深入的探讨。

OOP languages that appear to do so while remaining traditionally statically typesafe are "cheating" (inserting hidden dynamic type checks, or requiring compile-time examination of ALL sources to check); 在保持传统的静态类型安全的同时,似乎这样做的OOP语言却在“作弊”(插入隐藏的动态类型检查,或要求对所有源进行编译时检查)。 the fundamental choice is: either give up on this covariance and accept practitioners' puzzlement (as C# does here), or move to a dynamic typing approach (as the very first OOP language, Smalltalk, did), or move to immutable (single-assignment) data, like functional languages do (under immutability, you can support covariance, and also avoid other related puzzles such as the fact that you cannot have Square subclass Rectangle in a mutable-data world). 基本的选择是:要么放弃这种协方差,接受从业者的困惑(就像C#在这里所做的那样),要么转向动态类型化方法(就像最早的OOP语言Smalltalk一样),或者转向不可变(单-数据),就像功能语言一样(在不变性下,您可以支持协方差,还可以避免其他相关的难题,例如在可变数据世界中不能拥有Square子类Rectangle的事实)。

Consider: 考虑:

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

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

B b = null;
Foo2(ref b);

It would violate type-safety 它将违反类型安全

While the other responses have succinctly explained the reasoning behind this behavior, I think it's worth mentioning that if you really need to do something of this nature you can accomplish similar functionality by making Foo2 into a generic method, as such: 尽管其他回答简洁地解释了此行为的原因,但我认为值得一提的是,如果您确实需要执行此类操作,则可以通过将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 {}  
}

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

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

Foo2(ref (A)b)

If you use practical examples for your types, you'll see it: 如果您对类型使用实际示例,则会看到以下内容:

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

And now you have your function that takes the ancestor ( ie Object ): 现在您有了使用祖先 Object )的函数:

void Foo2(ref Object connection) { }

What can possibly be wrong with that? 那可能有什么问题呢?

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

You just managed to assign a Bitmap to your SqlConnection . 您刚刚设法将Bitmap分配给SqlConnection

That's no good. 不好


Try again with others: 与其他人再试一次:

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

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

You stuffed an OracleConnection over-top of your SqlConnection . 您在SqlConnection的顶部填充了OracleConnection

In my case my function accepted an object and I couldn't send in anything so I simply did 就我而言,我的函数接受了一个对象,我什么也不能发送,所以我只是做了

object bla = myVar;
Foo(ref bla);

And that works 那行得通

My Foo is in VB.NET and it checks for type inside and does a lot of logic 我的Foo在VB.NET中,它检查内部的类型并执行很多逻辑

I apologize if my answer is duplicate but others were too long 如果我的回答是重复的,但其他人的回答太长,我深表歉意

Makes sense from a safety perspective, but I would have preferred it if the compiler gave a warning instead of an error, since there are legitimate uses of polymoprhic objects passed by reference. 从安全的角度讲是有道理的,但是如果编译器给出警告而不是错误,我会更喜欢它,因为通过引用传递的多态对象是合法使用的。 eg 例如

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

This won't compile, but would it work? 这不会编译,但是行得通吗?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM