C# 3.0 compiler bug – Using object initializers, generics and value types

There is a description of the bug here. The following piece of code demonstrates the bug.


    class Program
    {
        interface I
        {
            int X { get; set; }
        }

        struct S : I
        {
            public int X { get; set; }
        }

        static void Main(string[] args)
        {
            Func<S>();
        }

        static void Func<T>() where T : I, new()
        {
            var c = new T() { X = 1 };
            Console.WriteLine(c.X);
        }
    }

The code prints 0 instead of 1. The bug goes away if object initializers are not used – changing var c = new T() { X = 1 }; to var c = new T(); c.X = 1; will print 1.

I had assumed all along that object initializers were transformed into property setters at the source code level (or somewhere above IL). This bug doesn’t help much to confirm that :)

Anyway, let’s see what’s causing the problem. Peeking into the generated IL, 


   .locals init (
        [0] !!T c,
        [1] !!T <>g__initLocal0,
        [2] !!T CS$0$0000)
        ... // Store zeroed out instance of S in locals[1]
    L_0022: ldloc.1 
    L_0023: box !!T
    L_0028: ldc.i4.1 
    L_0029: callvirt instance void CSharp3Test.Program/I::set_X(int32)
    L_002e: nop 
    L_002f: ldloc.1 
    L_0030: stloc.0 
    L_0031: ldloca.s c
    L_0033: constrained !!T
    L_0039: callvirt instance int32 CSharp3Test.Program/I::get_X()
    L_003e: call void [mscorlib]System.Console::WriteLine(int32)

 Can you spot the problem now?

You can see that at L_0023, S is boxed and the boxed instance is used to make the virtual call to set X. You can also see that the boxed instance is not stored in any of the local variables, the callvirt instruction pops it off the stack and then it’s gone. To load the value of X, the callvirt instruction is given a reference to c (locals[0]), which contains the unboxed instance of the struct. Which is why we are getting a 0 (the default value), instead of 1. Essentially, the compiler is generating the equivalent IL for something like 


   S s = new S();
   I i = s;
   i.X = 10;
   Console.WriteLine(s.X);

This again prints 0 instead of 10, because i is assigned a different (boxed) instance of S and therefore changing i.X doesn’t change s.X.

But then how does doing the initialization in two steps (var c = new T(); c.X = 1) work. The answer – Two step initialization does not use boxing when setting the value of X. Here’s how the generated IL looks.

   .locals init (
        [0] !!T c,
        [1] !!T CS$0$0000)
    L_0021: stloc.0 
    L_0022: ldloca.s c
    L_0024: ldc.i4.1 
    L_0025: constrained !!T
    L_002b: callvirt instance void CSharp3Test.Program/I::set_X(int32)
    L_0030: nop 
    L_0031: ldloca.s c
    L_0033: constrained !!T
    L_0039: callvirt instance int32 CSharp3Test.Program/I::get_X()
    L_003e: call void [mscorlib]System.Console::WriteLine(int32)

 

The box instruction is gone, instead, the compiler has generated a constrained prefix for the callvirt instruction. MSDN says that

“When a callvirt method instruction has been prefixed by constrained  thisType, the instruction is executed as follows:

  • If thisType is a reference type (as opposed to a value type) then ptr is dereferenced and passed as the ‘this’ pointer to the callvirt of method.

  • If thisType is a value type and thisType implements method then ptr is passed unmodified as the ‘this’ pointer to a call  method instruction, for the implementation of method by thisType.


 “

In other words, the address of the instance is passed as the this parameter to the call method – no boxing at all. If you’d looked carefully enough, the getter for X uses the constrained prefix, even in our buggy IL (the first listing).

That explains the bug then, but opens up another question. If virtual dispatch can be done for a value type without boxing, then why not do it that way for

   S s = new S();
I i = s;
i.X = 10;

 as well? Mind you, virtual dispatch doesn’t mean anything for value types, as they cannot derive from or derived by anything else, so any callvirt instruction on value types can be treated as a straight call instruction. The constrained prefix does exactly this, after making sure that this is a value type. So why not use it for all virtual calls? Performance (not many value types implementing interfaces)?

What do you think? 

2 thoughts on “C# 3.0 compiler bug – Using object initializers, generics and value types”

  1. Excellent post da. How did you come across this situation (using initializers and also properties of the same name) ? Was it intentional or it happened so ?

    Need a discussion time about this with you sometime.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>