Pourquoi la méthode générique avec la contrainte de T: class aboutit-elle à la boxe? [dupliquer]

StackOverflow https://stackoverflow.com/questions/1400414

  •  05-07-2019
  •  | 
  •  

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?

Était-ce utile?

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é".

 entrer la description de l'image ici

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top