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
Why doesn't 'ref' and 'out' support polymorphism?
提问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 ref
and out
arguments.
为什么会出现上述编译时错误?ref
和out
参数都会发生这种情况。
采纳答案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?
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
, Reptile
, Giraffe
, Turtle
and Tiger
,具有明显的子类关系。
Now suppose you have a method void M(ref Mammal m)
. M
can both read and write m
.
现在假设您有一个方法void M(ref Mammal m)
。 M
可以读写m
。
Can you pass a variable of type
Animal
toM
?
你能将一个类型的变量传递
Animal
给M
吗?
No. That variable could contain a Turtle
, but M
will assume that it contains only Mammals. A Turtle
is not a Mammal
.
不。该变量可以包含Turtle
,但M
会假定它只包含哺乳动物。ATurtle
不是Mammal
。
Conclusion 1: ref
parameters cannot be made "bigger". (There are more animals than mammals, so the variable is getting "bigger" because it can contain more things.)
结论 1:ref
参数不能“变大”。(动物比哺乳动物多,所以变量变得“更大”,因为它可以包含更多的东西。)
Can you pass a variable of type
Giraffe
toM
?
你能将一个类型的变量传递
Giraffe
给M
吗?
No. M
can write to m
, and M
might want to write a Tiger
into m
. Now you've put a Tiger
into a variable which is actually of type Giraffe
.
号M
可以写入m
,并且M
可能要编写一个Tiger
成m
。现在您已经将 aTiger
放入一个实际上是 type 的变量中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
toN
?
你能将一个类型的变量传递
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
toN
?
你能将一个类型的变量传递
Animal
给N
吗?
Hmm.
唔。
Well, why not? N
cannot read from n
, it can only write to it, right? You write a Tiger
to a variable of type Animal
and you're all set, right?
好吧,为什么不呢? N
无法读取n
,只能写入,对吗?您将 a 写入一个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. (If N
throws, all bets are off.)
1)N
必须n
在N
正常返回之前写入。(如果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 typeAnimal
. - Pass
x
as anout
parameter toN
. N
writes aTiger
inton
, which is an alias forx
.- On another thread, someone writes a
Turtle
intox
. N
attempts to read the contents ofn
, and discovers aTurtle
in what it thinks is a variable of typeMammal
.
- 声明一个
x
类型为 的字段Animal
。 x
作为out
参数传递给N
.N
写入一个Tiger
inton
,它是 的别名x
。- 在另一个线程上,有人将 a
Turtle
写入x
. N
尝试读取 的内容n
,并Turtle
在它认为是 类型的变量中发现 aMammal
。
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. 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 Foo2
a ref B
would result in a malformed object because Foo2
only knows how to fill A
part 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 Bitmap
to 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 OracleConnection
over-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
如果我的答案重复但其他答案太长,我深表歉意