I’m currently working on a problem that involves System.Reflection.Emit code generation. I’m trying to figure out what CIL to emit in places where I would use default(SomeType) in C#.
I’ve run a few basic experiments from Visual Studio 11 Beta. JustDecompile shows me the following CIL output for default(bool), default(string), and 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>
Judging from this, default(T) seems to get resolved by the compiler to the most appropriate CIL for the given types.
I went on to see what would happen in the more general case, using three generic methods:
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; }
All three methods produce the same CIL method body:
.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
Question:
Can I conclude from all this that C#’s default(SomeType) corresponds most closely to CIL’s…
initobjfor non-primitive types (exceptstring?)ldc.iX.0/ldnull/ etc. for primitive types (plusstring)?
And why does CreateClassNull<T> not just translate to ldnull, but to initobj instead? After all, ldnull was emitted for string (which is also a reference type).
That’s a reasonable summary but a better way to think about it is: if the C# compiler would classify
default(T)as a compile time constant then the value of the constant is emitted. That is zero for numeric types, false for bool, and null for any reference type. If it would not be classified as a constant then we must (1) emit a temporary variable, (2) obtain the address of the temporary, (3) initobj that temporary variable via its address and (4) ensure that the temporary’s value is on the stack when it is needed.Well, let’s do it your way and see what happens:
…
That would probably be why we don’t do that.