Warum führt die generische Methode mit Einschränkung von T: Klasse zum Boxen? [Duplikat
Frage
Diese Frage hat hier bereits eine Antwort:
- Boxen bei Verwendung von Generika in C# 2 Antworten
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?
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".