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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1207144/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 10:55:46  来源:igfitidea点击:

Why doesn't 'ref' and 'out' support polymorphism?

c#polymorphismout-parametersref-parameters

提问by Andreas Grech

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 refand outarguments.

为什么会出现上述编译时错误?refout参数都会发生这种情况。

采纳答案by Eric Lippert

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

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

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, Turtleand Tiger, with the obvious subclassing relationships.

假设您有类Animal, Mammal, Reptile, Giraffe, Turtleand Tiger,具有明显的子类关系。

Now suppose you have a method void M(ref Mammal m). Mcan both read and write m.

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



Can you pass a variable of type Animalto M?

你能将一个类型的变量传递AnimalM吗?

No. That variable could contain a Turtle, but Mwill assume that it contains only Mammals. A Turtleis not a Mammal.

不。该变量可以包含Turtle,但M会假定它只包含哺乳动物。ATurtle不是Mammal

Conclusion 1: refparameters cannot be made "bigger". (There are more animals than mammals, so the variable is getting "bigger" because it can contain more things.)

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



Can you pass a variable of type Giraffeto M?

你能将一个类型的变量传递GiraffeM吗?

No. Mcan write to m, and Mmight want to write a Tigerinto m. Now you've put a Tigerinto a variable which is actually of type Giraffe.

M可以写入m,并且M可能要编写一个Tigerm。现在您已经将 aTiger放入一个实际上是 type 的变量中Giraffe

Conclusion 2: refparameters cannot be made "smaller".

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



Now consider N(out Mammal n).

现在考虑N(out Mammal n)

Can you pass a variable of type Giraffeto N?

你能将一个类型的变量传递GiraffeN吗?

No. Ncan write to n, and Nmight want to write a Tiger.

N可以写入n,并且N可能想要写入Tiger.

Conclusion 3: outparameters cannot be made "smaller".

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



Can you pass a variable of type Animalto N?

你能将一个类型的变量传递AnimalN吗?

Hmm.

唔。

Well, why not? Ncannot read from n, it can only write to it, right? You write a Tigerto a variable of type Animaland you're all set, right?

好吧,为什么不呢? N无法读取n,只能写入,对吗?您将 a 写入一个Tiger类型的变量,Animal然后就全部设置好了,对吗?

Wrong. The rule is not "Ncan only write to n".

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

The rules are, briefly:

规则简而言之:

1) Nhas to write to nbefore Nreturns normally. (If Nthrows, all bets are off.)

1)N必须nN正常返回之前写入。(如果N投掷,则所有赌注都将取消。)

2) Nhas to write something to nbefore it reads something from n.

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

That permits this sequence of events:

这允许以下事件序列:

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

Clearly we want to make that illegal.

显然,我们想让它成为非法。

Conclusion 4: outparameters cannot be made "larger".

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



Final conclusion: Neither refnor outparameters may vary their types. To do otherwise is to break verifiable type safety.

最终结论参数参数都不ref能改变out它们的类型。否则会破坏可验证的类型安全。

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 中工作的系列文章

回答by maciejkow

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.
Same reason you can't write:

如果您尝试将 b 传递给 Foo2 方法作为参考,而在 Foo2 中您尝试分配 a = new A(),这将是无效的。
同样的原因你不能写:

B b = new A();

回答by CannibalSmith

Because giving Foo2a ref Bwould result in a malformed object because Foo2only knows how to fill Apart of B.

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

回答by Henk Holterman

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

它会违反类型安全

回答by Alex Martelli

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. Why that is so is sketched in the existing answers, and explored more deeply in these wiki articles and links therefrom.

您正在努力解决协变(和逆变)的经典 OOP 问题,请参阅维基百科:尽管这一事实可能违背直觉预期,但在数学上,允许用派生类代替基类替换可变(可赋值)参数(和也包括其项目可分配的容器,出于同样的原因)同时仍然尊重Liskov 的原则。现有答案中概述了为什么会这样,并在这些 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); 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).

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

回答by dlamblin

Isn't that the compiler telling you it would like you to explicitly cast the object so that it can be sure you know what your intentions are?

是不是编译器告诉你它希望你显式地转换对象,以便它可以确定你知道你的意图是什么?

Foo2(ref (A)b)

回答by Oofpez

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. e.g.

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

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?

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

回答by Ian Boyd

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(i.e.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 Bitmapto your SqlConnection.

您刚刚设法将 a 分配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 OracleConnectionover-top of your SqlConnection.

你把OracleConnection你的SqlConnection.

回答by BrendanLoBuglio

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

回答by Shereef Marzouk

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

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