web-dev-qa-db-ja.com

ジェネリック型パラメーターを任意のインターフェイスに明示的にキャストします

Generics FAQ:Best Practices 言います:

コンパイラを使用すると、ジェネリック型パラメーターを任意のインターフェイスに明示的にキャストできますが、クラスにはキャストできません。

interface ISomeInterface
{...}
class SomeClass
{...}
class MyClass<T> 
{
   void SomeMethod(T t)
   {
      ISomeInterface obj1 = (ISomeInterface)t;//Compiles
      SomeClass      obj2 = (SomeClass)t;     //Does not compile
   }
}

クラス/インターフェイスが制約タイプとして指定されていない限り、クラスとインターフェイスの両方に合理的な制限があります。

では、なぜそのような振る舞い、なぜインターフェースに許可されるのか

37
Incognito

これは、SomeClassへのキャストは、使用可能な変換に応じて任意の数を意味できるのに対し、ISomeInterfaceへのキャストは参照変換またはボクシング変換にしかできないためだと思います。

オプション:

  • 最初にオブジェクトにキャスト:

    _SomeClass obj2 = (SomeClass) (object) t;
    _
  • 代わりにasを使用します。

    _SomeClass obj2 = t as SomeClass;
    _

明らかに2番目のケースでは、tnot a SomeClassの場合、後でヌルチェックも実行する必要があります。

編集:この理由は、C#4仕様のセクション6.2.7に記載されています。

上記の規則では、制約のない型パラメーターから非インターフェイス型への直接の明示的な変換は許可されていません。これは驚くべきことです。この規則の理由は、混乱を防ぎ、そのような変換のセマンティクスを明確にするためです。たとえば、次の宣言を検討してください。

_class X<T>
{
    public static long F(T t) {
        return (long)t; // Error 
    }
} 
_

Tからintへの直接の明示的な変換が許可されている場合、X<int>.F(7)が7Lを返すと簡単に予想できます。ただし、標準の数値変換は、バインディング時に型が数値であることがわかっている場合にのみ考慮されるため、そうはなりません。セマンティクスを明確にするために、上記の例を代わりに記述する必要があります。

_class X<T>
{
    public static long F(T t) {
        return (long)(object)t; // Ok, but will only work when T is long
    }
}
_

このコードはコンパイルされますが、X<int>.F(7)を実行すると、ボックス化されたintを直接longに変換できないため、実行時に例外がスローされます。

52
Jon Skeet

C#の継承の原則では、インターフェイスは複数回継承できますが、クラスは1回だけです。インターフェースからの継承には複雑な階層があるため、。netフレームワークでは、コンパイル時にジェネリック型Tに特定のインターフェースを確保する必要はありません。(EDIT)反対に、クラスはコンパイル時に型制約を次のコードとして宣言する特定のクラス。

class MyClass<T> where T : SomeClass
{
   void SomeMethod(T t)
   {
      ISomeInterface obj1 = (ISomeInterface)t;
      SomeClass      obj2 = (SomeClass)t;     
   }
}
2
Jin-Wook Chung