Compilador C # + código genérico com o boxe + restrições
Pergunta
Vamos examinar o código MSIL gerado para o método genérico seguinte:
public static U BoxValue<T, U>(T value)
where T : struct, U
where U : class
{
return value;
}
Look:
.method public hidebysig static !!U BoxValue<valuetype .ctor
([mscorlib]System.ValueType, !!U) T,class U>(!!T 'value') cil managed
{
.maxstack 8
IL_0000: ldarg.0
IL_0001: box !!T
IL_0006: unbox.any !!U
IL_000b: ret
}
Mas para código genérico acima, a representação IL mais eficiente deve ser:
IL_0000: ldarg.0
IL_0001: box !!T
IL_0006: ret
É conhecida dos constrangimentos que o valor está encaixotado em tipo de referência . Unbox.any
opcode é completamente redundante porque depois box
opcode o valor em IL pilha já será uma referência válida para !!U
, que pode ser usado sem qualquer unboxing.
Por que C # 3.0 do compilador não usa restrições de metadados para emitir código genérico mais eficiente? Unbox.any dá uma pequena sobrecarga (apenas 4x-5x mais lento), mas por que não um código melhor emitem neste cenário?
Solução
Parece que o compilador faz isso por causa de alguns problemas com o verificador.
A IL que você gostaria que o compilador para gerar não é verificável, e assim o compilador C # não pode gerá-lo (todo o código C # fora de contextos "inseguros" deve ser verificável).
As regras para a "compatibilidade de tipo de verificação" são apresentados na secção 1.8.1.2.3, partion III da Ecma spec.
Eles dizem que um tipo de 'S' é a verificação compatível com um tipo de 'T' ou (S: = T) usando as seguintes regras:
- [: = é reflexiva] Para todos os tipos de verificação S, S: = S
- [: = é transitivo] Para todos os tipos de verificação S, T e U Se S: = T e T: = U, então S:. = U
- S:. = T se S é a classe base de T ou uma interface implementada por T e T não é um tipo de valor
- objeto:. = T se T é um tipo de interface
- S: = T se S e T são ambas as interfaces e a implementação de T requer a implementação de S
- S: = nulo se S é um tipo de objecto ou uma interface
- S []: = T [], se S: ??= T e as matrizes são ou ambos os vetores (base zero, um posto) ou nem é um vetor e ambos têm o mesmo valor. (Esta regra lida com covariância matriz.)
- Se S e T são ponteiros de método, então S: = T se as assinaturas (tipos de retorno, tipos de parâmetro e convenção de chamada) são os mesmos.
destas regras, o único que pode ser aplicável neste caso é # 3.
No entanto, nº 3 não se aplica ao seu código, porque 'U' não é uma classe base do 'T', e não é uma interface de base de 'T', de modo que o 'ou' verificação retorna falso.
Isto significa que algumas necessidades instrução a ser executada, a fim de converter um T encaixotado em um L de uma forma que vai passar o verificador.
Eu concordo com você que as regras de verificação deve ser alterado, de modo que gerar o código que você quer é realmente verificáveis.
Tecnicamente, no entanto, o compilador está fazendo a coisa "correta", baseado na especificação ECMA.
Você deve arquivar um bug com alguém na Microsoft.
Outras dicas
Estas restrições parecem estranhas:
where T : struct, U
where U : class
T é um tipo de valor, mas ao mesmo tempo deve herdar a partir de U, que é um tipo de referência. Eu me pergunto que tipo poderia satisfazer as restrições acima e nos permite chamar esse método.