Pregunta

Vamos a examinar el código MSIL generado para el siguiente método genérico:

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
}

Sin embargo, para el código genérica anterior, la representación más eficiente IL debe ser:

  IL_0000:  ldarg.0
  IL_0001:  box        !!T
  IL_0006:  ret

Se sabe de las limitaciones que el valor está en un recuadro en tipo de referencia . opcode Unbox.any es completamente redundante porque después de opcode box el valor en pila IL ya será una referencia válida para !!U, que se puede utilizar sin ningún unboxing.

¿Por qué compilador de C # 3.0 no utiliza restricciones de metadatos para emitir código genérico más eficiente? Unbox.any da una pequeña sobrecarga (justo 4x-5x más lento), pero ¿por qué no emite un código mejor en este escenario?

¿Fue útil?

Solución

Parece que el compilador hace esto debido a algunos problemas con el verificador.

La IL que le gustaría que el compilador genere no es verificable, por lo que el compilador de C # no puede generarlo (todo el código C # fuera de contextos "inseguros" deben ser verificables).

Las reglas para "compatibilidad tipo de verificación" se dan en la Sección 1.8.1.2.3, Partion III de la especificación ECMA.

Dicen que un tipo 'S' es la verificación compatible con un tipo de 'T' o (S: = T) utilizando las siguientes reglas:

  1. [: = es reflexiva] Para todos los tipos de verificación S, S: S =
  2. [: = es transitivo] para todos los tipos de verificación S, T, U y si S: = T y T: = T, entonces S:. = U
  3. S:. = T si S es la clase base de T o un interfaz implementada por T y T no es un tipo de valor
  4. objeto:. = T si T es un tipo de interfaz
  5. S: = T si S y T son ambos interfaces y la aplicación de T requiere la implementación de S
  6. S: = null si S es un tipo de objeto o una interfaz
  7. S []: = T [] si S: = T y las matrices son o bien ambos vectores (basado en cero, rango uno) o ni es un vector y ambos tienen el mismo rango. (Esta regla se ocupa de matriz de covarianza.)
  8. Si S y T son punteros método, entonces S: = T si las firmas (regreso tipos, los tipos de parámetros y convención de llamada) son los mismos.

De estas normas, el único que podría ser de aplicación en este caso es # 3.

Sin embargo, # 3 no se aplica a su código, porque 'U' no es una clase base de la 'T', y no es una interfaz de base de 'T', por lo que la 'o' cheque devuelve falso.

Esto significa que algunos de instrucciones debe ser ejecutado con el fin de convertir una caja T en forma de U de una manera que va a pasar el verificador.

Estoy de acuerdo con usted en que las reglas de verificación debe ser cambiado, por lo que la generación del código que desea es en realidad verificable.

Técnicamente, sin embargo, el compilador está haciendo lo "correcto", basada en la especificación ECMA.

Debe presentar un error con alguien en Microsoft.

Otros consejos

Estas limitaciones parecen extraños:

where T : struct, U
where U : class

T es un tipo de valor, pero en el mismo tiempo debe heredar de U que es un tipo de referencia. Me pregunto qué tipos podrían satisfacer las restricciones anteriores y nos permiten llamar a este método.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top