2012-06-26 15 views
11

提供されているジェネリック型パラメータに基づいて具体的な実装イ​​ンスタンスを提供する単純なファクトリメソッドがあります。具象クラスがタイプパラメータを持つ共通の抽象基底クラスから継承している場合、それらをキャストすることはできません。コンパイラは私にError 2 Cannot convert type 'Car' to 'VehicleBase<T>'と伝えます。同じ型パラメータを持つインタフェースの抽象クラスを代入するか、抽象クラスから汎用型パラメータを削除すると正常に動作します。型パラメータを持つ抽象クラスを抽象クラスにキャストできません

interface IWheel 
{ 
} 

class CarWheel : IWheel 
{ 
} 

abstract class VehicleBase<T> 
{ 
} 

class Car : VehicleBase<CarWheel> 
{ 
} 

class VehicleFactory 
{ 
    public static VehicleBase<T> GetNew<T>() 
    { 
     if (typeof(T) == typeof(CarWheel)) 
     { 
      return (VehicleBase<T>)new Car(); 
     } 
     else 
     { 
      throw new NotSupportedException(); 
     } 
    } 
} 

(VehicleBase<T>)new Car()でコンパイルできません。これはコンパイラの欠陥か、抽象クラスとインタフェースを型パラメータと違ったやり方で扱う意図的な設計上の決定でしょうか?

回避策として、私はいつも抽象クラスにインターフェイスを実装させ、これをファクトリメソッドの戻り値として使用することができますが、なぜこの動作が起こっているのかを知りたいと思います。汎用コードがあらゆる可能Tのために(同じILで)作業する必要があり、何も言うことがないので、証明可能ない

答えて

5

これはコンパイラの欠陥でも慎重な判断でもありません。ジェネリッククラスのタイプパラメータはneither covariant nor contravariantです。つまり、同じジェネリッククラスのスペシャライゼーション間に継承関係はありません。ドキュメントから:

.NET Frameworkバージョン4では、バリアント型パラメータは、汎用インターフェイスとジェネリックデリゲートタイプに制限されています。それは抽象クラスの代わりにインターフェイスを使用しているため、次のコードは、コンパイルされることを意味し

interface IWheel 
{ 
} 

class CarWheel : IWheel 
{ 
} 

interface IVehicleBase<T> 
{ 
} 

class Car : IVehicleBase<CarWheel> 
{ 
} 

class VehicleFactory 
{ 
    public static IVehicleBase<T> GetNew<T>() 
    { 
     if (typeof(T) == typeof(CarWheel)) 
     { 
      return (IVehicleBase<T>)new Car(); 
     } 
     else 
     { 
      throw new NotSupportedException(); 
     } 
    } 
} 

チェックの詳細情報および例については、「Covariance and Contravariance in Generics」。

私はインターフェイスを使用している場合があり詳細を持つC#のFAQブログでCovariance and Contravariance FAQもあり、Eric Lippert

+0

これは私が使用した解決方法です。私にインターフェイス/代理人の違いについて思い出させてくれてありがとう - 感謝し、私の質問に答えます。 – dahvyd

+0

私に思い出させていただきありがとうございます。あなたがドアの外にあるコードを手に入れなければならない時を忘れるのはずっと簡単です –

9

そのCar : VehicleBase<float>、例えば。コンパイラは、ifのチェックで、TCarWheelであることがわかっていることを過度に分析しません。静的チェッカーは、各文をと別々に扱います.は、条件の原因と影響を理解しようとしません。しかし

return (VehicleBase<T>)(object)new Car(); 

真ん中にobjectにキャスト、それを強制するには!あなたのアプローチは、それほど「一般的な」ものではありません。

+0

による被写体の11-part series!は合理的に聞こえるが、なぜ違いは?これは私が理解していないものです。 – dahvyd

+0

これは証明できないだけではありません。ジェネリックインタフェースのみがバリアント型を持つことができます。このコードは、適切な型制約を指定しても機能しません –

3

これは動作するようです:

return new Car() as VehicleBase<T>; 

マイ推測それはその方法である理由:
VehicleBase<T>のジェネリック型のインスタンスが関連していないとして、それらをキャストすると仕事ができることを証明することはできません。

enter image description here

TはタイプBlahである場合、キャストは動作しません。あなたはオブジェクトに戻り、他のブランチを取ることはできません(C#では多重継承はありません)。

objectにキャストすると、キャストが機能する可能性が再び開かれます。パスはまだVehicleBase<CarWheel>になる可能性があるためです。もちろん、インターフェースはと表示されます。このツリーのobjectの下にあります。

関連する問題