web-dev-qa-db-fra.com

cast explicitement des paramètres de type générique à n'importe quelle interface

Dans FAQ sur les génériques: meilleures pratiques dit:

Le compilateur vous permettra de convertir explicitement des paramètres de type générique vers n'importe quelle interface, mais pas vers une classe:

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

Je vois une limitation raisonnable pour les classes et les interfaces, sauf si la classe/interface n'est pas spécifiée comme type de contrainte.

Alors pourquoi un tel comportement, pourquoi est-il autorisé pour les interfaces?

37
Incognito

Je pense que cela est dû au fait que la conversion en SomeClass peut signifier un certain nombre de choses en fonction des conversions disponibles, tandis que la conversion en ISomeInterface ne peut être qu'une conversion de référence ou une conversion de boxe.

Options:

  • Convertir en objet d'abord:

    SomeClass obj2 = (SomeClass) (object) t;
    
  • Utilisez à la place as:

    SomeClass obj2 = t as SomeClass;
    

De toute évidence, dans le deuxième cas, vous devrez également effectuer un contrôle de nullité dans le cas où t est not a SomeClass.

EDIT: Le raisonnement pour cela est donné dans la section 6.2.7 de la spécification C # 4:

Les règles ci-dessus ne permettent pas une conversion explicite directe d'un paramètre de type non contraint en un type sans interface, ce qui peut être surprenant. La raison de cette règle est d'éviter toute confusion et de clarifier la sémantique de ces conversions. Par exemple, considérez la déclaration suivante:

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

Si la conversion explicite directe de t en int était autorisée, on pourrait facilement s'attendre à ce que X<int>.F(7) renvoie 7L. Cependant, ce ne serait pas le cas, car les conversions numériques standard ne sont prises en compte que lorsque les types sont connus pour être numériques au moment de la liaison. Afin de clarifier la sémantique, l'exemple ci-dessus doit être écrit à la place:

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

Ce code va maintenant être compilé mais l'exécution de X<int>.F(7) lèverait alors une exception au moment de l'exécution, car un entier encadré ne peut pas être converti directement en un long.

52
Jon Skeet

Dans le principe d'héritage de C #, les interfaces peuvent être héritées plusieurs fois, mais une classe une seule fois. Comme l'héritage des interfaces a une hiérarchie complexe, le framework .net n'a pas besoin d'assurer le type générique T une interface spécifique au moment de la compilation. (EDIT) Au contraire, une classe pourrait être assurée a classe spécifique avec déclaration d'une contrainte de type lors de la compilation comme code suivant.

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