[英]Why doesn't 'ref' and 'out' support polymorphism?
采取以下措施:
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
參數都會發生這種情況。
=============
更新:我使用此答案作為此博客條目的基礎:
有關此問題的更多評論,請參見博客頁面。 感謝您提出的重大問題。
=============
假設您有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
讀取內容。
這允許發生以下一系列事件:
Animal
類型的字段x
。 x
作為out
參數傳遞給N
N
將Tiger
寫入n
, n
是x
的別名。 Turtle
寫入x
。 N
嘗試讀取n
的內容,並在它認為是Mammal
類型的變量中發現了Turtle
。 顯然,我們想使之非法。
結論4 :不能將out
參數設為“更大”。
最終結論 : ref
和out
參數都不ref
其類型。 否則將破壞可驗證的類型安全性。
如果您對基本類型理論中的這些問題感興趣,請考慮閱讀我的有關協方差和反方差如何在C#4.0中工作的系列文章 。
因為在兩種情況下,您都必須能夠將值分配給ref / out參數。
如果您嘗試將b傳遞給Foo2方法作為引用,並且在Foo2中嘗試使用a = new A(),則此方法無效。
不能寫的原因相同:
B b = new A();
您正在努力應對協方差 (和協方差 )的經典OOP問題,請參閱Wikipedia :盡管此事實可能違背了直觀的期望,但從數學上講,它不可能用派生類替代基類來代替可變的(可分配的)參數(並且也是出於相同原因可分配項目的容器),同時仍要遵守Liskov原則 。 在現有答案中概述了為什么這樣做,並在這些Wiki文章及其鏈接中進行了更深入的探討。
在保持傳統的靜態類型安全的同時,似乎這樣做的OOP語言卻在“作弊”(插入隱藏的動態類型檢查,或要求對所有源進行編譯時檢查)。 基本的選擇是:要么放棄這種協方差,接受從業者的困惑(就像C#在這里所做的那樣),要么轉向動態類型化方法(就像最早的OOP語言Smalltalk一樣),或者轉向不可變(單-數據),就像功能語言一樣(在不變性下,您可以支持協方差,還可以避免其他相關的難題,例如在可變數據世界中不能擁有Square子類Rectangle的事實)。
考慮:
class C : A {}
class B : A {}
void Foo2(ref A a) { a = new C(); }
B b = null;
Foo2(ref 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 B
會導致對象格式錯誤,因為Foo2
只知道如何填充B
A
部分。
難道不是編譯器告訴您希望您顯式轉換對象,以確保您知道自己的意圖嗎?
Foo2(ref (A)b)
如果您對類型使用實際示例,則會看到以下內容:
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();
}
您在SqlConnection
的頂部填充了OracleConnection
。
就我而言,我的函數接受了一個對象,我什么也不能發送,所以我只是做了
object bla = myVar;
Foo(ref bla);
那行得通
我的Foo在VB.NET中,它檢查內部的類型並執行很多邏輯
如果我的回答是重復的,但其他人的回答太長,我深表歉意
從安全的角度講是有道理的,但是如果編譯器給出警告而不是錯誤,我會更喜歡它,因為通過引用傳遞的多態對象是合法使用的。 例如
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);
}
這不會編譯,但是行得通嗎?
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.