2009-11-02 19 views
3

Examinons le code MSIL généré pour la méthode générique suivante:compilateur C# + code générique avec la boxe + contraintes

public static U BoxValue<T, U>(T value) 
    where T : struct, U 
    where U : class 
{ 
    return value; 
} 

Rechercher:

.method public hidebysig static !!U BoxValue<valuetype .ctor 
([mscorlib]System.ValueType, !!U) T,class U>(!!T 'value') cil managed 
{ 
    .maxstack 8 
    IL_0000: ldarg.0 
    IL_0001: box  !!T 
    IL_0006: unbox.any !!U 
    IL_000b: ret 
} 

Mais pour le code générique ci-dessus, l'IL plus efficace la représentation doit être:

IL_0000: ldarg.0 
    IL_0001: box  !!T 
    IL_0006: ret 

Il est connu à partir des contraintes que la valeur est encadré dans type de référence. Unbox.any L'opcode est complètement redondant car après l'opcode box la valeur à IL stack sera déjà une référence valide à !!U, qui peut être utilisée sans aucun déballage.

Pourquoi le compilateur C# 3.0 n'utilise pas de métadonnées de contraintes pour émettre du code générique plus efficace? Unbox.any donne un petit overhead (juste 4x-5x plus lent), mais pourquoi ne pas émettre un meilleur code dans ce scénario?

Répondre

6

Il semble que le compilateur le fasse à cause de problèmes avec le vérificateur.

L'IL que vous souhaitez que le compilateur génère n'est pas vérifiable, et le compilateur C# ne peut donc pas le générer (tout code C# en dehors de contextes «dangereux» doit être vérifiable). Les règles de "compatibilité de type de vérification" sont données à la section 1.8.1.2.3, paragraphe III de la spécification Ecma.

Ils disent qu'un type 'S' est la vérification compatible avec un type 'T' ou (S: = T) en utilisant les règles suivantes:

  1. [: = est réflexif] Pour tous les types de vérification S , S: = S
  2. [: = est transitive] Pour tous les types de vérification S, T et U si S: = T et T: = U, alors S: = U.
  3. S: = T si S est la classe de base de T ou une interface implémentée par T et T n'est pas un type de valeur.
  4. objet: = T si T est un type d'interface.
  5. S: = T si S et T sont tous deux des interfaces et la mise en œuvre de T requiert la mise en œuvre de S
  6. S: = null si S est un type d'objet ou d'une interface
  7. S []: = T [] si S: = T et les tableaux sont soit les deux vecteurs (base zéro, rang 1), soit aucun est un vecteur et tous deux ont le même rang. (Cette règle traite de la covariance des matrices.)
  8. Si S et T sont des pointeurs de méthode, alors S: = T si les signatures (types de retour, types de paramètre et convention d'appel ) sont identiques.

Parmi ces règles, la seule qui pourrait être applicable dans ce cas est # 3. Cependant, le numéro 3 ne s'applique pas à votre code, car 'U' n'est pas une classe de base de 'T', et ce n'est pas une interface de base de 'T', donc la vérification 'ou' renvoie false.

Cela signifie que CERTAINES instructions doivent être exécutées afin de convertir un T encadré en un U de manière à passer le vérificateur. Je suis d'accord avec vous pour dire que les règles de vérification doivent être changées, afin que la génération du code que vous voulez soit réellement vérifiable.

Techniquement, cependant, le compilateur fait la chose "correcte" basée sur la spécification ECMA.

Vous devez signaler un problème à quelqu'un chez Microsoft.

3

Ces contraintes ne paraissent pas:

where T : struct, U 
where U : class 

T est un type de valeur, mais dans le même temps doit hériter de U qui est un type de référence. Je me demande quels types pourraient satisfaire les contraintes ci-dessus et nous permettent d'appeler cette méthode.

+2

Tous les types d'interface, 'System.Object',' System.ValueType' (et type 'System.Enum', mais C# ne supporte pas les contraintes d'énumération) – ControlFlow

+0

Ces contraintes semblent étranges, mais elles sont absolument dites que * type T a une conversion implicite en type U * et cette conversion est * conversion en boxe *. – ControlFlow