Consider the following code:
public enum FooEnum : int
{
Foo = 0,
Bar = 1,
Qux = 2
}
public class TestClass
{
private int? _foo;
public FooEnum? Foo
{
get { return (FooEnum?)_foo; }
set { _foo = (int?)value; }
}
}
Look at the CIL generated:
get_Foo():
IL_0000: ldarg.0
IL_0001: ldfld System.Nullable`1[System.Int32] _foo
IL_0006: stloc.0
IL_0007: ldloca.s System.Nullable`1[System.Int32] (0)
IL_0009: call Boolean get_HasValue()
IL_000e: brtrue.s IL_001a
IL_0010: ldloca.s System.Nullable`1[MyAssembly.FooEnum] (1)
IL_0012: initobj System.Nullable`1[MyAssembly.FooEnum]
IL_0018: ldloc.1
IL_0019: ret
IL_001a: ldloca.s System.Nullable`1[System.Int32] (0)
IL_001c: call Int32 GetValueOrDefault()
IL_0021: newobj Void .ctor(MyAssembly.FooEnum)
IL_0026: ret
set_Foo():
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: stloc.0
IL_0003: ldloca.s System.Nullable`1[MyAssembly.FooEnum] (0)
IL_0005: call Boolean get_HasValue()
IL_000a: brtrue.s IL_0017
IL_000c: ldloca.s System.Nullable`1[System.Int32] (1)
IL_000e: initobj System.Nullable`1[System.Int32]
IL_0014: ldloc.1
IL_0015: br.s IL_0023
IL_0017: ldloca.s System.Nullable`1[MyAssembly.FooEnum] (0)
IL_0019: call MyAssembly.FooEnum GetValueOrDefault()
IL_001e: newobj Void .ctor(Int32)
IL_0023: stfld System.Nullable`1[System.Int32] _foo
IL_0028: ret
When I first saw this, it seemed intuitive to me. Just because the compiler treats regular enums as their underlying type, doesn't automatically mean their nullable forms should be required to. After all, Nullable<T>
is its own structure.
However, this is where it gets weird. I used Mono.Cecil to re-write the get and set methods using the instructions that would be used as if it were non-nullable:
get_Foo():
IL_0000: ldarg.0
IL_0001: ldfld System.Nullable`1[System.Int32] _foo
IL_0006: ret
set_Foo():
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: stfld System.Nullable`1[System.Int32] _foo
IL_0007: ret
Surprisingly, the property worked perfectly. My question is, why does the C# compiler emit the unnecessary CIL?