Perché il metodo generico con vincolo di T: class si traduce in boxe? [duplicare]
Domanda
Questa domanda ha già una risposta qui:
- Pugilato quando si usano i generici in C # 2 risposte
Perché un metodo generico che vincola la classe T dovrebbe avere istruzioni di inscatolamento nel codice MSIL genera?
Ne sono rimasto piuttosto sorpreso poiché sicuramente poiché T è vincolato a un tipo di riferimento, il codice generato non dovrebbe aver bisogno di eseguire alcun inscatolamento.
Ecco il codice 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;
}
}
Ecco l'IL generato:
.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
}
Nota la casella !! T istruzioni.
Perché questo viene generato?
Come evitarlo?
Soluzione
Non devi preoccuparti di alcun peggioramento delle prestazioni dall'istruzione box
perché se il suo argomento è un tipo di riferimento, l'istruzione box
non fa nulla. Anche se è ancora strano che sia stata persino creata l'istruzione box
(forse pigrizia / design più semplice alla generazione del codice?).
Altri suggerimenti
Non sono sicuro del motivo per cui sta insorgendo una boxe. Un modo possibile per evitare il pugilato è non usarlo. Basta ricompilare senza la boxe. Es:
.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
}
}
... se salvi in ??un file recomp_srp.msil puoi semplicemente ricompilare come tale:
ildasm / dll recomp_srp.msil
E funziona bene senza il pugilato da parte mia:
<*>... ovviamente, l'ho cambiato da protetto a pubblico, dovresti apportare di nuovo la modifica e fornire il resto della tua implementazione.
Credo che questo sia inteso dal design. Non stai vincolando T a una classe specifica, quindi è molto probabile che venga lanciato verso l'oggetto. Ecco perché vedi che IL include la boxe.
Proverei questo codice con dove T: ActualClass
Seguendo un paio di punti. Innanzitutto, questo errore si verifica per entrambi i metodi in una classe generica con vincolo dove T: class
e anche metodi generici con lo stesso vincolo ( in una classe generica o non generica). Non si verifica per un metodo non generico (altrimenti identico) che utilizza Object
anziché 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
Nota alcuni problemi aggiuntivi con il primo esempio. Invece di semplicemente ldnull
abbiamo una chiamata initobj
estranea indirizzata inutilmente a una variabile locale in eccesso tmp
.
La buona notizia, tuttavia, accennata a qui , è che nulla di tutto ciò conta. Nonostante le differenze nel codice IL generato per i due esempi precedenti, il x64 JIT genera un codice quasi identico per loro. Il seguente risultato è per .NET Framework 4.7.2 modalità di rilascio con ottimizzazione " non soppressa " ;.