我最近不得不幫助解決某人在從泛型方法中回傳時遇到的問題,雖然有多個問題需要解決,但我理解并能解釋所有問題--除了讓編譯器接受回傳型別這一最后障礙。盡管我最終成功地讓程式正確地編譯和運行,但我仍然無法完全理解為什么它必須這樣做的邏輯。
我在下面用一個最小的例子再現了這個問題。給出一個非常簡單的父子類結構,只有一個通用方法:
abstract class AbstractParentClass
{
public abstract T DoThing< T>() where T : AbstractParentClass。
}
class ConcreteChildClass : AbstractParentClasspublic override T DoThing< T>()
{
return this。
}
}
這將導致在回傳行中出現錯誤:Cannot implicitly convert type 'ConcreteChildClass' to 'T'。這有點奇怪,因為T被限制為AbstractParentClass的一個實體,而this顯然是其中之一,但是好吧,當然,所以我們要明確地做:
public override T DoThing< T>()
{
return (T) this;
}
現在錯誤資訊是:無法將型別'ConcreteChildClass'轉換為'T'。如果T是不受約束的,那么當然,我知道我們不能保證它,但是像我們這樣的繼承,肯定應該是一個直接的轉換。
我們可以通過明確地投向父類來接近:
我們可以通過明確地投向父類來接近。
public override T DoThing< T>()
{
return (AbstractParentClass) this;
}
現在的錯誤是:無法將型別'AbstractParentClass'隱式轉換為'T'。存在一個顯式轉換(你是不是漏掉了一個cast?)。為什么我們不能隱式轉換為約束條件的確切型別--什么情況下會導致約束條件型別的類不能轉換為...本身?但至少現在這個問題是可以解決的,錯誤資訊甚至直接告訴我們如何去做:
public override T DoThing<。 T>()
{
return (T)(AbstractParentClass) this;
}
現在一切運行正常。如果我們想的話,我們甚至可以讓它看起來更漂亮一些:
public override T DoThing< T>()
{
return this as T。
}
縱觀這一切,如果T不受約束,我當然明白為什么這些轉換不可能像寫的那樣實作。但它確實是,而且是以一種編譯器不應該有任何問題的方式。編譯器是否因為某些原因,在允許的隱式轉換中不考慮型別約束?根據我的經驗,C#中所有類似的情況背后都有一個非常好的理由,只是我一生都無法弄清楚這個問題。
如果有人能夠深入了解為什么編譯器在處理這段(看似!)非常簡單的代碼時出現了問題,我將非常感謝您對允許這些轉換的問題的解釋。
uj5u.com熱心網友回復:
由于技術上你可以做到:
class ConcreteChildClass2 : AbstractParentClass
{
public override T DoThing< T>()
{
return null。
}
}
var ccc = new ConcreteChildClass();
var ccc2 = ccc.DoThing<ConcreteChildClass2>()。
即使你做了這些轉換,這也會在運行時炸開鍋:
System.InvalidCastException。Unable to cast object of type 'ConcreteChildClass'/span> to type 'ConcreteChildClass2'/span>.
如果你不這樣做把這個回傳為T,你會得到null作為結果,而不是一個鑄造例外。
換句話說,這個約束并不能保證DoThing回傳與定義類相同的型別,它只能保證它回傳一些繼承了AbstractParentClass的型別。
uj5u.com熱心網友回復:
"為什么 "是即使ConcreteChildClass是一個AbstractParentClass和T是一個AbstractParentClass,這并不一定保證ConcreteChildClass是一個T。
對于一個現實世界的類比,Cat是一個Pet,Dog是一個Pet,但是Cat不是一個Dog。
D Stanley的回答顯示了你如何呼叫DoThing()的T的值,這對于你所處理的物件型別來說是不正確的。
根據您的用例,您可以嘗試使用一些其他模式來解決這個問題。
例如,您可以在您的父類上使用遞回列舉宣告型別,類似于 Java 對其列舉的做法:
abstract class AbstractParentClass< T> where T : AbstractParentClass<T>
{
public abstract T DoThing()。
}
class ConcreteChildClass : AbstractParentClass<ConcreteChildClass>
{
public override ConcreteChildClass DoThing()
{
return this;
}
}
這使得你不能告訴一個ConcreteChildClass在呼叫DoThing()時使用不同的類作為其通用型別。你強制要求任何正確遵循該模式的子類必須回傳一個正確型別的實體。然而,它迫使你到處傳播泛型:你不能只是參考一個AbstractParentClass而不包括一些泛型引數。你可以用一個非泛型介面來解決這個問題,讓代碼使用它,而不關心它被賦予哪種實作型別。但是它仍然有點笨重,因為(與 Java Enums 不同)你不能真正強制每個實作都擴展父類和使用自己作為通用型別。
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/318646.html
標籤:
上一篇:JavaObjectMapper.readValue將通用型別變成LinkedHashMap
下一篇:在模擬類中使用pytest夾具
