Come tradurre “default (SomeType)” da C # a CIL?

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

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

A giudicare da ciò, il default(T) sembra essere risolto dal compilatore per il CIL più appropriato per i tipi specificati.


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

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

Tutti e tre i metodi producono lo stesso corpo del metodo 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 questo che il default(SomeType) C # default(SomeType) corrisponde più strettamente al CIL …

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

E perché CreateClassNull non si limita a tradurre in ldnull , ma in initobj ? Dopo tutto, ldnull stato emesso per string (che è anche un tipo di riferimento).

Posso concludere da questo che l’ default(SomeType) C # default(SomeType) corrisponde più strettamente a initobj di CIL per i tipi non primitivi e ldc.i4.0 , ldnull , ecc. Per i tipi primitivi?

Questo è un sumrio ragionevole, ma un modo migliore di pensarci è: se il compilatore C # classificasse il default(T) come costante di tempo di compilazione, verrà emesso il valore della costante. 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é CreateClassNull non si limita a tradurre 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() 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 

Questo probabilmente sarebbe il motivo per cui non lo facciamo.

Sì, è quello che fa l’ default . Hai ragione nel dedurre che è solo zucchero sintattico per fondamentalmente 0 (o equivalenti).