簡體   English   中英

無法在 C# 中強制轉換為基本泛型類型,但可以使用“as”運算符

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM