2

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?

Tolga Evcimen
  • 7,112
  • 11
  • 58
  • 91
Mr Anderson
  • 2,200
  • 13
  • 23

1 Answers1

2

Your change leads to code that is not verifiable. If you run peverify on it you will get something like this:

[IL]: Error: [SO37583607.exe : SO37583607.TestClass::get_Foo][offset 0x00000007][found value 'System.Nullable'1[System.Int32]'][expected value 'System.Nullable'1[SO37583607.FooEnum]'] Unexpected type on the stack.

The following stack overflow answer attempts to explain what verifiable code is: https://stackoverflow.com/a/4533532/284111

So to answer your question, the compiler emits this code, to make sure it's verifiable.

Community
  • 1
  • 1
Andrew Savinykh
  • 25,351
  • 17
  • 103
  • 158