Warum führt die generische Methode mit Einschränkung von T: Klasse zum Boxen? [Duplikat

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

  •  05-07-2019
  •  | 
  •  

Frage

Diese Frage hat hier bereits eine Antwort:

Warum eine generische Methode, die T auf die Klasse einschränkt, würde Boxanweisungen im Generationen MSIL -Code haben?

Ich war ziemlich überrascht, da sicherlich T, da T auf einen Referenztyp eingeschränkt wird, der generierte Code kein Boxen ausführen sollte.

Hier ist der C# Code:

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;
    }
}

Hier ist das erzeugte IL:

.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$0000)
    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 
}

Beachten Sie die Box !! t Anweisungen.

Warum wird das generiert?

Wie vermeiden Sie das?

War es hilfreich?

Lösung

Sie müssen sich keine Sorgen über Leistungsabbau von der machen box Anweisung, denn wenn sein Argument ein Referenztyp ist, die box Der Anweisungen tut nichts. Obwohl es immer noch seltsam ist, dass die box Der Unterricht wurde sogar erstellt (vielleicht faul/einfacher Design bei der Codegenerierung?).

Andere Tipps

Ich bin mir nicht sicher, warum ein Boxen auftritt. Eine mögliche Möglichkeit, das Boxen zu vermeiden, besteht darin, es nicht zu verwenden. Einfach ohne das Boxen kompilieren. 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$0000)

        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

}

}

... Wenn Sie in einer Datei speichern, können Sie einfach als solche neu kompilieren:

iildasm /dll recomp_srp.msil

Und es läuft in Ordnung ohne das Boxen an meinem Ende:

        FixedPBF TestFixedPBF = new FixedPBF();

        TestFixedPBF.SetRefProperty<string>(ref TestField, "test2");

... Natürlich habe ich es von geschützt zu öffentlich geändert. Sie müssten die Änderung wieder vornehmen und den Rest Ihrer Implementierung bereitstellen.

Ich glaube, das ist von Design beabsichtigt. Sie beschränken T nicht auf eine bestimmte Klasse, daher ist es höchstwahrscheinlich, dass sie sie auf Objekt steckt. Deshalb sehen Sie die IL -Einschlüsse des Boxens.

Ich würde diesen Code mit der tatsächlichen Klasse t: true

Nach ein paar Punkten folgen. Erstens tritt dieser Fehler für beide Methoden in a auf generische Klasse mit Einschränkung where T : class und auch Generische Methoden mit derselben Einschränkung (in einer generischen oder nicht generischen Klasse). Es tritt nicht für eine (ansonsten identische) nicht generische Methode auf, die verwendet Object Anstatt von 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

Beachten Sie einige zusätzliche Probleme mit dem ersten Beispiel. Statt einfach ldnull Wir haben ein Fremd initobj Rufen Sie sinnlos auf eine überschüssige lokale Variable ab tmp.

Die guten Nachrichten deuteten jedoch an. hier, ist, dass nichts davon wichtig ist. Trotz der Unterschiede im IL -Code, das für die beiden oben genannten Beispiele generiert wurde, ist die x64 JIT generiert fast identischen Code für sie. Das folgende Ergebnis ist für .NET Framework 4.7.2 Release -Modus mit Optimierung "nicht unterdrückt".

enter image description here

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top