[英]Cannot cast to base generic type in C# but can use 'as' operator
為什么強制轉換不適用於約束泛型類型,如下所示?
class B { }
class B1 : B { }
class G<T> where T : B
{
void x()
{
T b1 = new B1(); // why implicit conversion doesn't compile?
T b2 = (T)new B1(); // why explicit conversion doesn't compile either?
T b3 = new B1() as T; // this works!
}
}
B1
不限於可分配給 T。例如:
void Main()
{
new G<C1>().x();
}
class B { }
class B1 : B { }
class C1 : B { }
class G<T> where T : B
{
public void x()
{
T b3 = new B1() as T;
b3.Dump(); // null, because B1 cannot be converted to C1
}
}
僅僅因為 T 被限制為 B 並不意味着您可以將 B 的任何后代強制轉換為任何可能的 T。
為什么不允許呢? 在我看來,這沒有任何意義。 如果您想確保 B1 可分配給 T,則不應使用泛型。 犯這種錯誤太容易了,如果可能,您應該首先避免強制轉換泛型。 它們(主要)是為了使靜態類型更強大,同時保持類型安全(和性能優勢)。
但是,肯定有明顯錯誤的情況不會被抓住,因為
T b2 = (T)new B();
確實編譯,即使它實際上有同樣的問題,你會得到一個運行時錯誤鑄如果T是不是B.
當然,在這種情況下,檢查C# 規范會很有幫助,而且很清楚,它說:
上述規則不允許從不受約束的類型參數直接顯式轉換為非接口類型,這可能令人驚訝。 這條規則的原因是為了防止混淆並使此類轉換的語義清晰。
雖然這似乎只對值類型有意義,但這解釋了為什么你可以直接執行(T)new B();
,以及為什么你不能做(T)new B1();
- 即使兩者都有相同的問題,T 不一定是 B。
請記住,C#中的運算符不是虛擬的- 它們取決於表達式的編譯時類型。 對於值類型參數,您實際上會為您使用的每個值類型獲得一個變體(即List<long>
使用與List<int>
不同的代碼) - 因此您會獲得正確的轉換,例如從 int 轉換為 long 時,您會得到一個long 與 int 具有相同的值,而不是轉換錯誤。
對於引用類型,這不是真的。 在你的情況下,你可以有一個從 B 到 B 的自定義轉換運算符,它實際上會在(T) new B()
情況下被調用,但不是從 B1 到 B 的轉換,因為G<B>
的具體化泛型類型和G<B1>
其實是一樣的。 由於這是一個可以隨時更改的實現細節,您確實希望避免混淆和潛在的行為變化。
T b1 = new B1(); // why implicit conversion doesn't compile?
僅僅因為 T 和 B1 都源自 B 並不意味着 B1 可以賦值給 T。它們都可以賦值給 B,所以
B b1 = new B1(); // should work
.
T b2 = (T)new B1(); // why explicit conversion doesn't compile either?
如上所述,具有公共基類的兩個類不能確保類型兼容性,因此顯式轉換也不起作用
T b3 = new B1() as T; // this works!
它有效,但您可能將 null 分配給 b3,因為 B1 不能轉換為 T。 as 運算符只返回 null 而不是發出編譯器警告或拋出異常。
答案是,因為不能保證你提供B1
作為泛型參數,你不能用泛型來做到這一點,讓我們探討一下為什么......
給定的
class Animal { }
class Dog : Animal { }
class Cat : Animal { }
class Something<T> where T : Animal
{
public T Animal {get;set;}
void x()
{
Animal = (T)new Cat();
}
}
現在,如果你像這樣使用你的類怎么辦
var dog = new Something<Dog>();
dog.X() // internally you are trying to cast a Cat to a Dog
Dog dog = dog.Animal; // now you just tried to mash a Cat into a dog
Dog.Bark() // what the...
約束是一個最低限度的契約,就是這樣。 它們允許您在指定的合約上使用泛型參數。
這和你為什么不能做下面的事情完全一樣
Dog dog = new Cat();
即使它們都繼承自Animal
,也不意味着它們是相同的......它們內部具有不同的內存布局,它們具有不同的方法和屬性,它們不能像這樣靜態類型混合在一起。
簡而言之,您可能需要重新考慮您的問題。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.