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…
initobj
for non-primitive types (except string
?)ldc.iX.0
/ ldnull
/ etc. for primitive types (plus string
)?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).
Can I conclude from all this that C#'s
default(SomeType)
corresponds most closely to CIL'sinitobj
for non-primitive types andldc.i4.0
,ldnull
, etc. for primitive types?
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.
why does
CreateClassNull<T>
not just translate to ldnull, but to initobj instead?
Well, let's do it your way and see what happens:
... 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
That would probably be why we don't do that.
Yes, that's what default
does. You are correct in deducing it's just syntactic sugar for basically 0
(or equivalents).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With