Pourquoi la méthode générique avec la contrainte de T: class aboutit-elle à la boxe? [dupliquer]
Question
Cette question a déjà une réponse ici:
Pourquoi une méthode générique qui contraint T à une classe aurait-elle des instructions de boxe dans le code MSIL généré?
J'ai été assez surpris par cela puisque sûrement, puisque T est contraint à un type de référence, le code généré ne devrait pas avoir besoin de boxer.
Voici le code c #:
protected void SetRefProperty<T>(ref T propertyBackingField, T newValue) where T : class
{
bool isDifferent = false;
// for reference types, we use a simple reference equality check to determine
// whether the values are 'equal'. We do not use an equality comparer as these are often
// unreliable indicators of equality, AND because value equivalence does NOT indicate
// that we should share a reference type since it may be a mutable.
if (propertyBackingField != newValue)
{
isDifferent = true;
}
}
Voici l'IL généré:
.method family hidebysig instance void SetRefProperty<class T>(!!T& propertyBackingField, !!T newValue) cil managed
{
.maxstack 2
.locals init (
[0] bool isDifferent,
[1] bool CS$4<*>)
L_0000: nop
L_0001: ldc.i4.0
L_0002: stloc.0
L_0003: ldarg.1
L_0004: ldobj !!T
L_0009: box !!T
L_000e: ldarg.2
L_000f: box !!T
L_0014: ceq
L_0016: stloc.1
L_0017: ldloc.1
L_0018: brtrue.s L_001e
L_001a: nop
L_001b: ldc.i4.1
L_001c: stloc.0
L_001d: nop
L_001e: ret
}
Notez les instructions Boîte !! T .
Pourquoi cela est-il généré?
Comment éviter cela?
La solution
Vous n'avez pas à vous soucier de la dégradation des performances de l'instruction box
car, si son argument est un type de référence, l'instruction box
ne fait rien. Bien que cela reste étrange que l’instruction box
ait même été créée (peut-être paresseux / conception plus facile lors de la génération de code?).
Autres conseils
Je ne sais pas pourquoi il y a de la boxe. Un moyen d'éviter la boxe est de ne pas l'utiliser. Il suffit de recompiler sans la boxe. Ex:
.assembly recomp_srp
{
.ver 1:0:0:0
}
.class public auto ansi FixedPBF
{
.method public instance void .ctor() cil managed
{
}
.method hidebysig public instance void SetRefProperty<class T>(!!T& propertyBackingField, !!T newValue) cil managed
{
.maxstack 2
.locals init ( bool isDifferent, bool CS$4 FixedPBF TestFixedPBF = new FixedPBF();
TestFixedPBF.SetRefProperty<string>(ref TestField, "test2");
)
ldc.i4.0
stloc.0
ldarg.1
ldobj !!T
ldarg.2
ceq
stloc.1
ldloc.1
brtrue.s L_0001
ldc.i4.1
stloc.0
L_0001: ret
}
}
... si vous enregistrez dans un fichier recomp_srp.msil, vous pouvez simplement recompiler en tant que tel:
ildasm / dll recomp_srp.msil
Et ça marche bien sans la boxe de mon côté:
<*>... bien sûr, je l'ai changé de protégé à public, vous devez rediffuser le changement et fournir le reste de votre implémentation.
Je pense que cela est voulu par la conception. Vous n'êtes pas obligé de contraindre T à une classe spécifique, il est donc très probable que vous le réduisiez en objet. C’est pourquoi vous voyez l’IL inclure la boxe.
Je voudrais essayer ce code avec où T: ActualClass
Suivi de quelques points. Tout d'abord, ce bogue survient pour les deux méthodes dans une classe générique avec la contrainte où T: class
ainsi que méthodes génériques avec la même contrainte ( dans une classe générique ou non générique). Cela ne se produit pas pour une méthode non générique (sinon identique) qui utilise Object
au lieu de T
:
// static T XchgNullCur<T>(ref T addr, T value) where T : class =>
// Interlocked.CompareExchange(ref addr, val, null) ?? value;
.locals init (!T tmp)
ldarg addr
ldarg val
ldloca tmp
initobj !T
ldloc tmp
call !!0 Interlocked::CompareExchange<!T>(!!0&, !!0, !!0)
dup
box !T
brtrue L_001a
pop
ldarg val
L_001a:
ret
// static Object XchgNullCur(ref Object addr, Object val) =>
// Interlocked.CompareExchange(ref addr, val, null) ?? value;
ldarg addr
ldarg val
ldnull
call object Interlocked::CompareExchange(object&, object, object)
dup
brtrue L_000d
pop
ldarg val
L_000d:
ret
Notez quelques problèmes supplémentaires avec le premier exemple. Au lieu de simplement ldnull
, nous avons un appel initobj
superficiel, ciblant inutilement une variable locale en excès tmp
.
La bonne nouvelle cependant, selon ici , est que rien de tout cela n'a d'importance. Malgré les différences de code IL générées pour les deux exemples ci-dessus, le JIT x64 génère un code presque identique pour eux. Le résultat suivant concerne .NET Framework 4.7.2 mode de publication avec optimisation "non supprimé".