2012-05-01 11 views
12

Attualmente sto lavorando su un problema che riguarda la generazione del codice System.Reflection.Emit. Sto cercando di capire cosa CIL emettere in posti dove vorrei usare default(SomeType) in C#.Come tradurre "default (SomeType)" da C# a CIL?

Ho eseguito alcuni esperimenti di base da Visual Studio 11 Beta. JustDecompile mi mostra il seguente output CIL per default(bool), default(string), e 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 giudicare da questo, default(T) sembra avere risolto dal compilatore per il CIL più appropriato per i tipi dati.


Sono andato a vedere cosa sarebbe successo nel caso più generale, utilizzando tre metodi generici:

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;  } 

Tutti e tre i metodi producono lo stesso metodo del corpo CIL:

.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 

Domanda:

Posso concludere da tutto ciò che C# 's default(SomeType) corrisponde più strettamente a CIL's & hellip;

  • initobj per i tipi non primitivi (ad eccezione string?)
  • ldc.iX.0/ldnull/ecc per i tipi primitivi (più string)?

E perché fa CreateClassNull<T> non solo si traducono in ldnull, ma per initobj invece? Dopo tutto, è stato emesso ldnull per string (che è anche un tipo di riferimento).

+0

cosa intendi con tipi "primitivi"? Esistono tipi di valore e tipi di riferimento. Mi aspetterei ldnull con i tipi di riferimento (inclusa la stringa). \ – sehe

+0

Con "tipi primitivi", mi riferisco a quelli supportati nativamente dal CTS (?) - 'bool',' byte', 'char',' int ',' float', 'double', ecc. Quelli che, a differenza del valore definito dall'utente o dei tipi di riferimento, non sono compositi e non possono essere scomposti in altri tipi di base. – stakx

+0

Osservo che si deve guardare il debug, codegen non ottimizzato, dato che il compilatore non ha rimosso un ramo per l'istruzione successiva. –

risposta

15

Posso concludere da tutto ciò che il C# 's default(SomeType) più si avvicina alla CIL di initobj per i tipi non primitivi e ldc.i4.0, ldnull, ecc per i tipi primitivi?

Ecco una sintesi ragionevole, ma un modo migliore per pensare a questo proposito è: se il compilatore C# classificherebbe default(T) come un tempo di compilazione costante allora il valore della costante viene emesso. Questo è zero per i tipi numerici, false per bool e null per qualsiasi tipo di riferimento. Se non fosse classificato come costante, allora dobbiamo (1) emettere una variabile temporanea, (2) ottenere l'indirizzo del temporaneo, (3) initobj quella variabile temporanea tramite il suo indirizzo e (4) assicurare che il valore del temporaneo sia in pila quando è necessario.

perché lo CreateClassNull<T> non viene semplicemente convertito in ldnull, ma in initobj?

Bene, facciamolo a modo tuo e vediamo cosa succede:

... 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 

Che probabilmente sarebbe il motivo per cui non lo facciamo.

+2

Non capisco, perché caricare un 'null' in un tipo di riferimento (' T: class') dà un errore riguardo a unboxed 'T'? – Blindy

+0

+1 Solo curioso quando il compilatore * non * classifica 'default (T) 'come costante in fase di compilazione. La mia ipotesi sarebbe "quando si tratta di generici", ma sono curioso di sapere se ci sono altri casi. – dasblinkenlight

+1

@dasblinkenlight: 'default (int?)' Non è una costante in fase di compilazione. 'default (Guid)' non è una costante in fase di compilazione. Qualsiasi tipo di valore non incorporato non è una costante. –

1

Sì, questo è ciò che fa default. Hai ragione nel dedurre che è solo zucchero sintattico per fondamentalmente 0 (o equivalenti).

+0

Ma come fa il compilatore (o come sceglierei io) tra i diversi modi possibili di codificarlo? 'ldnull',' ldfalse', 'ldc. ... .0' sembrano abbastanza ovvi ... ma perché initobj'? – stakx

+0

Facile, 'Nullable ' è un oggetto di tipo (valore), quindi viene inizializzato con 'initobj'. Pensateci per eliminazione: non potete caricare interi in esso, i tipi di valore non possono essere nulli (nemmeno i tipi 'Nullable <>'), quindi ciò che resta è inizializzarlo come oggetto vuoto (costruttore predefinito). – Blindy

+0

E il motivo per cui 'stringa' si comporta diversamente è perché non è un tipo * value *, è una classe. Può contenere un riferimento null, quindi 'default (stringa)' si risolve in 'null', proprio come' default (Form) 'per esempio. – Blindy