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?

Foi útil?

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:

  1. [: = é reflexiva] Para todos os tipos de verificação S, S: = S
  2. [: = é transitivo] Para todos os tipos de verificação S, T e U Se S: = T e T: = U, então S:. = U
  3. S:. = T se S é a classe base de T ou uma interface implementada por T e T não é um tipo de valor
  4. objeto:. = T se T é um tipo de interface
  5. S: = T se S e T são ambas as interfaces e a implementação de T requer a implementação de S
  6. S: = nulo se S é um tipo de objecto ou uma interface
  7. 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.)
  8. 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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top