看看下面:
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) {}
}
为什么会出现上述编译时错误? 这种情况既ref
和out
论点。
=============
更新:我用这个答案这个博客条目的基础:
为什么ref和out参数不允许类型的变化?
关于此问题的详细解说,请参阅博客页面。 感谢伟大的问题。
=============
让我们假设你有类Animal
, Mammal
, Reptile
, Giraffe
, Turtle
和Tiger
,具有明显的子类关系。
现在,假设你有一个方法void M(ref Mammal m)
。 M
可以同时读取和写入m
。
你可以通过类型的变量Animal
,以M
?
号该变量可能包含一个Turtle
,但M
会认为它仅包含哺乳动物。 一个Turtle
是不是Mammal
。
结论1: ref
参数不能取得“更大”。 (还有更多的动物比哺乳动物,所以变数也越来越“大”,是因为它可以包含更多的东西。)
你可以通过类型的变量Giraffe
到M
?
号M
可以写入m
,而M
可能要到写Tiger
成m
。 现在你已经把Tiger
变成一个变量,它实际上是类型Giraffe
。
结论2: ref
参数不能进行“较小”。
现在考虑N(out Mammal n)
你可以通过类型的变量Giraffe
到N
?
号N
可以写信给n
和N
可能要编写一个Tiger
。
结论3: out
参数不能进行“更小”。
你可以通过类型的变量Animal
来N
?
嗯。
那么,为什么不呢? N
无法读取n
,它只能写,对不对? 你写了一个Tiger
到类型的变量Animal
和你所有的设置,对不对?
错误。 规则不是“ N
只能写n
”。
规则是,简单:
1) N
具有写入n
之前N
正常返回。 (如果N
抛出,所有的赌注都关闭)。
2) N
有写东西n
它读取的东西之前n
。
这使得这一系列事件:
- 声明字段
x
型的Animal
。 - 通过
x
作为out
参数N
。 -
N
写入Tiger
成n
,这是一个别名x
。 - 在另一个线程,有人写了一个
Turtle
成x
。 -
N
尝试读取的内容n
,并发现了Turtle
在它认为是什么类型的变量Mammal
。
显然,我们想使非法。
结论4: out
参数不能作出“较大”。
最后得出结论 : 无论是ref
还是out
参数可能有所不同它们的类型。 如果不这样做就是要打破可验证的类型安全。
如果基本类型理论的兴趣,你这些问题,可以阅读我的系列协方差和逆变如何工作在C#4.0 。
因为在这两种情况下,你必须能够赋值给REF /输出参数。
如果你试图通过B插入foo2的方法为参考,并在foo2的尝试assing一个新= A(),这将是无效的。
你可以不写同样的原因:
B b = new A();
你和协方差 (和逆变)的经典OOP问题所困扰,请参阅维基百科 :竟有这样的事实可能违背直觉的预期,这是数学上不可能允许派生类的替代替代基地的人对可变(分配)参数(和也是容器,其项目转让的,只是同样的原因),同时仍然尊重里氏原理 。 为什么会这样是在现有的答案勾勒,并在这些维基文章和链接更深入探讨由此。
出现这样做的同时保持传统的静态类型安全的“作弊” OOP语言(插入隐藏动态类型检查,或要求的所有来源的编译时检查,以检查); 根本的选择是:要么放弃对这个协方差和接受从业者的困惑(如C#在这里所做的),或移动到一个动态类型的方法(因为第一个面向对象的语言,Smalltalk的,所做的那样),或者移动到不可变的(单分配)数据,如函数式语言做(下不变性,可以支持协方差,还可以避免其他相关的难题,如事实,你不能有一个可变数据世界广场的子类矩形)。
考虑:
class C : A {}
class B : A {}
void Foo2(ref A a) { a = new C(); }
B b = null;
Foo2(ref b);
这将违反类型安全
因为给Foo2
一个ref B
将导致畸形的对象,因为Foo2
只知道如何填写A
的一部分B
。
而其他的反应已经说明简洁背后这种行为的理由,我认为这是值得一提的是,如果你真的需要做这种性质的东西,你可以通过使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 (A)b)
从安全角度看是有道理的,但我宁愿它如果编译器给了一个警告而不是错误的,因为有按引用传递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);
}
这不会编译,但它的工作?
如果您使用的实际例子为你的类型,你会看到它:
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
。
在我的情况,我的函数接受一个对象,我不能送任何东西,所以我压根儿
object bla = myVar;
Foo(ref bla);
这作品
我的foo是VB.NET,它的内部检查类型,做了很多的逻辑
我很抱歉,如果我的回答是重复的,但其他太长