Génériques: types de casting et de valeur, pourquoi est-ce illégal?

Pourquoi est-ce une erreur de compilation?

public TCastTo CastMe(TSource i) { return (TCastTo)i; } 

Erreur:

annot convertir le type ‘TSource’ en ‘TCastTo’

Et pourquoi est-ce une erreur d’exécution?

 public TCastTo CastMe(TSource i) { return (TCastTo)(object)i; } int a = 4; long b = CastMe(a); // InvalidCastException // this consortingved example works int aa = 4; int bb = CastMe(aa); // this also works, the problem is limited to value types ssortingng s = "foo"; object o = CastMe(s); 

J’ai cherché SO et Internet pour trouver une réponse et trouvé de nombreuses explications sur des problèmes de casting génériques similaires, mais je ne trouve rien sur ce cas simple et particulier.

Pourquoi est-ce une erreur de compilation?

Le problème est que chaque combinaison possible de types de valeur a des règles différentes pour ce que signifie une conversion . Transtyper un double de 64 bits en un entier de 16 bits est un code complètement différent de la conversion d’une virgule décimale en une valeur flottante, etc. Le nombre de possibilités est énorme. Alors pensez comme le compilateur. Quel code le compilateur est-il censé générer pour votre programme?

Le compilateur devrait générer un code qui le redémarre au moment de l’exécution, effectue une nouvelle parsing des types et émet de manière dynamic le code approprié .

Cela semble être peut-être plus de travail et moins de performances que ce à quoi vous vous attendiez avec les génériques, alors nous le proscrivons tout simplement. Si vous voulez vraiment que le compilateur redémarre et parsing les types, utilisez “dynamic” en C # 4; c’est ce que ça fait.

Et pourquoi est-ce une erreur d’exécution?

Même raison.

Un int en boîte ne peut être décompressé qu’en int (ou int?), Pour la même raison que ci-dessus; si le CLR tente de faire toutes les conversions possibles d’un type de valeur encadré à tout autre type de valeur possible, il doit essentiellement exécuter à nouveau un compilateur au moment de l’exécution . Ce serait inopinément lent.

Alors, pourquoi ne s’agit-il pas d’une erreur pour les types de référence?

Parce que chaque conversion de type de référence est identique à toute autre conversion de type de référence : vous interrogez l’object pour voir s’il est dérivé ou identique au type souhaité. Si ce n’est pas le cas, vous lâchez une exception (si vous effectuez un transt) ou vous obtenez null / false (si vous utilisez les opérateurs “as / is”). Les règles sont cohérentes pour les types de référence, contrairement aux types de valeur. N’oubliez pas que les types de référence connaissent leur propre type . Les types de valeur ne le font pas; avec les types de valeur, la variable faisant le stockage est la seule chose qui connaisse la sémantique du type qui s’applique à ces bits . Les types de valeur contiennent leurs valeurs et aucune information supplémentaire . Les types de référence contiennent leurs valeurs ainsi que de nombreuses données supplémentaires.

Pour plus d’informations, voir mon article sur le sujet:

http://ericlippert.com/2009/03/03/representation-and-identity/

C # utilise une syntaxe de conversion pour plusieurs opérations sous-jacentes différentes:

  • upcast
  • abattu
  • boxe
  • déballage
  • conversion numérique
  • conversion définie par l’utilisateur

Dans un contexte générique, le compilateur n’a aucun moyen de savoir lequel de ces correctifs est correct, et ils génèrent tous un MSIL différent, de sorte qu’il échoue.

En écrivant return (TCastTo)(object)i; au lieu de cela, vous forcez le compilateur à effectuer une TCastTo en object , suivie d’une TCastTo en TCastTo . Le compilateur générera du code, mais si ce n’est pas la bonne façon de convertir les types en question, vous obtiendrez une erreur d’exécution.


Exemple de code:

 public static class DefaultConverter { private static Converter cached; static DefaultConverter() { ParameterExpression p = Expression.Parameter(typeof(TSource)); cached = Expression.Lambda(Expression.Convert(p, typeof(TCastTo), p).Comstack(); } public static Converter Instance { return cached; } } public static class DefaultConverter { public static TOutput ConvertBen(TInput from) { return DefaultConverter.Instance.Invoke(from); } public static TOutput ConvertEric(dynamic from) { return from; } } 

Le chemin d’Eric est plus court, mais je pense que le mien devrait être plus rapide.

L’erreur de compilation est due au fait que TSource ne peut pas être implicitement converti en TCastTo. Les deux types peuvent partager une twig sur leur arbre d’inheritance, mais il n’y a aucune garantie. Si vous souhaitez appeler uniquement les types partageant un ancêtre, vous devez modifier la signature CastMe () pour utiliser le type d’ancêtre au lieu des génériques.

L’exemple d’erreur d’exécution évite l’erreur de votre premier exemple en convertissant d’abord TSource i en un object, ce dont dérivent tous les objects en C #. Bien que le compilateur ne se plaint pas (car object -> quelque chose qui en dérive, pourrait être valide), le comportement de transtypage via la syntaxe de variable (Type) s’exécutera si le transtypage est incorrect. (Le même problème que le compilateur a empêché de se produire dans l’exemple 1).

Une autre solution, qui fait quelque chose de similaire à ce que vous recherchez …

  public static T2 CastTo(T input, Func convert) { return convert(input); } 

Tu appellerais ça comme ça.

 int a = 314; long b = CastTo(a, i=>(long)i); 

Espérons que cela aide.