Por que o método genérico com restrição de T: classe resulta em boxe? [duplicado
Pergunta
Esta pergunta já tem uma resposta aqui:
- Boxe ao usar genéricos em C# 2 respostas
Por que um método genérico que restringe a classe teria instruções de boxe no código MSIL Gereates?
Fiquei bastante surpreso com isso, pois certamente, pois T está sendo restringido a um tipo de referência que o código gerado não deve precisar executar nenhum boxe.
Aqui está o código 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;
}
}
Aqui está o IL gerado:
.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
}
Observe o caixa !! t instruções.
Por que isso está sendo gerado?
Como evitar isso?
Solução
Você não precisa se preocupar com degradações de desempenho do box
instrução porque se seu argumento é um tipo de referência, o box
Instrução não faz nada. Embora ainda seja estranho que o box
A instrução foi criada (talvez preguiçoso/design mais fácil na geração de código?).
Outras dicas
Não sei por que qualquer boxe está ocorrendo. Uma maneira possível de evitar o boxe é não usá -lo. Apenas recompile sem o 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$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
}
}
... Se você salvar em um arquivo recompom_srp.msil, poderá simplesmente recompilar como tal:
ildasm /dll recomp_srp.msil
E funciona bem sem o boxe do meu lado:
FixedPBF TestFixedPBF = new FixedPBF();
TestFixedPBF.SetRefProperty<string>(ref TestField, "test2");
... É claro que eu mudei de protegido para o público, você precisaria fazer a mudança novamente e fornecer o restante da sua implementação.
Eu acredito que isso se destina ao design. Você não está restringindo T a uma classe específica, então provavelmente é lançá -la para se opor. Por isso, você vê o IL incluir boxe.
Eu tentaria este código com onde t: realclass
Seguindo alguns pontos. Primeiro de tudo, esse bug ocorre para ambos os métodos em um classe genérica com restrição where T : class
e também Métodos genéricos com a mesma restrição (em uma classe genérica ou não genérica). Não ocorre para um método não genérico (de outra forma) que usa Object
ao invés 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
Observe alguns problemas adicionais com o primeiro exemplo. Em vez de simplesmente ldnull
Temos um estranho initobj
Ligue para inútil, direcionando uma variável local em excesso tmp
.
A boa notícia no entanto, sugerido aqui, isso é nada disso importa. Apesar das diferenças no código da IL gerado para os dois exemplos acima, o x64 jit gera código quase idêntico para eles. O resultado a seguir é para .NET Framework 4.7.2 modo de liberação com otimização "não suprimido".