2012-05-01 14 views
12

Actualmente estoy trabajando en un problema que involucra la generación de código System.Reflection.Emit. Intento averiguar qué CIL emitir en los lugares donde usaría default(SomeType) en C#.¿Cómo traducir "por defecto (SomeType)" de C# a CIL?

He realizado algunos experimentos básicos de Visual Studio 11 Beta. JustDecompile me muestra el siguiente resultado CIL para default(bool), default(string) y default(int?:

.locals init (
    [0] bool           V_0, 
    [1] string          V_1, 
    [2] valuetype [mscorlib]System.Nullable`1<int32> V_2  
) 

// bool b = default(bool); 
ldc.i4.0 
stloc.0 

// string s = default(string); 
ldnull 
stloc.1 

// int? ni = default(int?); 
ldloca.s V_2 
initobj valuetype [mscorlib]System.Nullable`1<int32> 

A juzgar por esto, default(T) parece que se resolvió por el compilador para el CIL más adecuado para los tipos dados.


Fui a ver lo que sucedería en el caso más general, utilizando tres métodos genéricos:

T CreateStructDefault<T>() where T : struct { return default(T); } 
T CreateClassDefault<T>() where T : class { return default(T); } 
T CreateClassNull<T>()  where T : class { return null;  } 

Los tres métodos producen los mismos CIL método de cuerpo:

.locals init (
    [0] !!T V_0, 
    [1] !!T V_1 
) 

IL_0000: nop  
IL_0001: ldloca.s V_1 
IL_0003: initobj !!T 
IL_0009: ldloc.1 
IL_000a: stloc.0 
IL_000b: br.s IL_000d 
IL_000d: ldloc.0 
IL_000e: ret 

Pregunta:

¿Puedo concluir de todo esto que C# 's default(SomeType) corresponde más estrechamente a CIL & hellip;

  • initobj para los tipos no primitivos (excepto string?)
  • ldc.iX.0/ldnull/etc. para los tipos primitivos (más string)?

y por qué CreateClassNull<T> no sólo se traducen en ldnull, pero a initobj en su lugar? Después de todo, se emitió ldnull para string (que también es un tipo de referencia).

+0

¿Qué quieres decir con tipos 'primitivos'? Hay tipos de valores y tipos de referencia. Esperaría ldnull con tipos de referencia (incluida la cadena). \ – sehe

+0

Con "tipos primitivos", me refiero a los que son originalmente compatibles con el CTS (?) - 'bool',' byte', 'char',' int ',' float', 'double', etc. Aquellos que, a diferencia del valor definido por el usuario o de los tipos de referencia, no son compuestos y no pueden dividirse en tipos más básicos. – stakx

+0

Observo que debe estar mirando el codegen sin depurar, depurado, dado que el compilador no ha eliminado una rama de la siguiente instrucción. –

Respuesta

15

¿Puedo concluir de todo esto que el C# 's default(SomeType) corresponde más estrechamente a CIL de initobj para los tipos no primitivos y ldc.i4.0, ldnull, etc., para los tipos primitivos?

Eso es un resumen razonable, pero una mejor manera de pensar en ello es: si el compilador de C# clasificaría como una constante default(T) entonces el valor de la constante de tiempo se emite compilación. Eso es cero para tipos numéricos, falso para bool y nulo para cualquier tipo de referencia. Si no se clasificara como una constante, entonces debemos (1) emitir una variable temporal, (2) obtener la dirección del temporal, (3) initobj esa variable temporal a través de su dirección y (4) asegurar que el valor temporal sea en la pila cuando es necesario.

¿por qué CreateClassNull<T> no solo se traduce a ldnull, sino a initobj?

Bueno, vamos a hacerlo a su manera y ver lo que sucede:

... etc 
.class private auto ansi beforefieldinit P 
     extends [mscorlib]System.Object 
{ 
    .method private hidebysig static !!T M<class T>() cil managed 
    { 
    .maxstack 1 
    ldnull 
    ret 
    } 
    ... etc 

...

D:\>peverify foo.exe 

Microsoft (R) .NET Framework PE Verifier. Version 4.0.30319.17379 
Copyright (c) Microsoft Corporation. All rights reserved. 

[IL]: Error: 
[d:\foo.exe : P::M[T]] 
[offset 0x00000001] 
[found Nullobjref 'NullReference']  
[expected (unboxed) 'T'] 
Unexpected type on the stack. 
1 Error(s) Verifying d:\foo.exe 

Eso sería probablemente por qué no hacemos eso.

+2

No lo entiendo, ¿por qué cargar un 'null' en un tipo de referencia (' T: class') da un error sobre unboxed 'T'? – Blindy

+0

+1 Solo curiosidad cuando el compilador * no * clasifica 'por defecto (T)' como una constante de tiempo de compilación. Mi suposición sería "cuando se trata de medicamentos genéricos", pero tengo curiosidad si hay otros casos. – dasblinkenlight

+1

@dasblinkenlight: 'default (int?)' No es una constante en tiempo de compilación. 'default (Guid)' no es una constante en tiempo de compilación. Cualquier tipo de valor no incorporado no es una constante. –

1

Sí, eso es lo que default hace. Está en lo correcto al deducir que es solo azúcar sintáctica básicamente para 0 (o equivalentes).

+0

Pero, ¿cómo el compilador (o cómo podría) elegir entre las diferentes formas posibles de codificación que? 'ldnull',' ldfalse', 'ldc. ... .0' parece bastante obvio ... pero ¿por qué' initobj'? – stakx

+0

Fácil, 'Nullable ' es un objeto de tipo (valor), por lo que se inicializa con 'initobj'. Piénselo por eliminación: no puede cargar enteros en él, los tipos de valores no pueden ser nulos (ni siquiera los tipos 'Nullable <>'), por lo que lo que queda es inicializarlo como un objeto vacío (constructor predeterminado). – Blindy

+0

Y la razón por la cual 'cadena' se comporta de manera diferente es porque no es un * valor * tipo, es una clase. Puede contener una referencia nula, por lo que 'default (string)' se resuelve a 'null', al igual que' default (Form) 'por ejemplo. – Blindy