Thursday, January 18, 2007

Automatic Properties Issue On Structs

In general, class objects are allocated on the heap while structs are created on the stack, reference vs. value for short. Their syntax is almost identical, but I found one issue using struct today. Following C# code doesn't compile in Visual Studio 2005:
    struct GeoCode
{
public double Longitude { get; set; }
public double Latitude { get; set; }

public GeoCode(double longitude, double latitude)
{
this.Longitude = longitude;
this.Latitude = latitude;
}
}
The error is:

Backing field for automatically implemented property 'Program.GeoCode.Latitude' must be fully assigned before control is returned to the caller. Consider calling the default constructor from a constructor initializer ...

And:
The 'this' object cannot be used before all of its fields are assigned to ...
It's okay with:
    struct GeoCode
{
public double Longitude;
public double Latitude;

public GeoCode(double longitude, double latitude)
{
this.Longitude = longitude;
this.Latitude = latitude;
}
}
Sounds like the issue of automatic properties on structs in .NET 2.0. To fix it, just follow the error description "Consider calling the default constructor from a constructor..":
    struct GeoCode
{
public double Longitude { get; set; }
public double Latitude { get; set; }

public GeoCode(double longitude, double latitude) : this()
{
this.Longitude = longitude;
this.Latitude = latitude;
}
}

Friday, January 05, 2007

A tricky int i=0; i=i++; i = ? question

I was asked a question that sounds simple but it's a bit tricky. What's the output of following C# code?
public class Program
{
static void Main(string[] args)
{
int i = 0; i = i++;
int j = 0; j = ++j;
Console.WriteLine("{0} {1}", i, j);
Console.Read();
}
}
I thought it's "1 1" but I was wrong. The correct answer is "0 1".

Why? i++ is an after operation, so a temporary value is created to store the before and after value. The explanation is not that straightforward. Let's look at the IL code generated by compiler, and see what's under the hood:
.method private hidebysig static void  Main(string[] args) cil managed
{
.entrypoint
// Code size 47 (0x2f)
.maxstack 3
.locals init (int32 V_0,
int32 V_1)
IL_0000: nop

// i operation
IL_0001: ldc.i4.0 // Put constant number 0 onto the stack
IL_0002: stloc.0 // Pop from the stack and store it in the local variable #0 (number = 0)
IL_0003: ldloc.0 // Push local variable #0 onto the stack
IL_0004: dup // Duplicate the value in the stack (push another 0 onto stack)
IL_0005: ldc.i4.1 // Put constant number 1 onto the stack
IL_0006: add // Pop last two values from stack and push back their sum (1+0=1)
IL_0007: stloc.0 // Pop from stack and store it to local variable #0 (number=1)
IL_0008: stloc.0 // Pop from stack and store it to local variable #0 (overwritten number=0)

// j operation
IL_0009: ldc.i4.0 // put constant number 0 onto the stack
IL_000a: stloc.1 // Pop from the stack and store it in the local variable #1 (number=0)
IL_000b: ldloc.1 // Push local variable #1 onto the stack
IL_000c: ldc.i4.1 // Put constant number 1 on the stack
IL_000d: add // Pop last two values from stack and push back their sum (1+0=1)
IL_000e: dup // Duplicate the value in the stack (push another 1 onto stack)
IL_000f: stloc.1 // Pop from the stack and store it to local variable #1 (number=1)
IL_0010: stloc.1 // Pop from the stack and store it to local variable #1 (number=1)

// Print
IL_0011: ldstr "{0} {1}"
IL_0016: ldloc.0 // Load local variable #0 (0) onto stack
IL_0017: box [mscorlib]System.Int32
IL_001c: ldloc.1 // Load local variable #1 (1) onto stack
IL_001d: box [mscorlib]System.Int32
IL_0022: call void [mscorlib]System.Console::WriteLine(string,
object,
object)
IL_0027: nop
IL_0028: call int32 [mscorlib]System.Console::Read()
IL_002d: pop
IL_002e: ret
} // end of method Program1::Main
We can see that the plus operation is run in a separate stack, the result of 1 is assigned to the local variable, but is overwritten by original value of 0. Thus the original i value of 0 is used for the assignment of i = i++. Is it ambiguous? I'm not sure. It's the way how .NET deals with such scenarios.

(Updated Jan 7, 07) I tested in Java (1.5) and got the same result of "0 1". But in C++ (compiled by VS.NET 2005) it returns "1 1".