I caught the end of a thread on the microsoft C# newsgroup (catching up after vacation and a teaching gig with limited connectivity). Something smells in the value type initialization world (somewhere where I seem destined to spend a large amount of time.Consider the following code: we have a class with 3 ints and a constructor that thows an exception half way through processing if a passed in flag is set to true; then we have Main that creates an instance of the class (passing in false to the flag), prints out the values. It now creates another instance assigning it to the same reference but passes true to the ctor. This generates and exception which Main swallows.
using System;
class App
{
static void Main(string[] args)
{
Foo f = new Foo(1,2,3, false);
Console.WriteLine( f );
try
{
f = new Foo(4, 5, 6, true);
}
catch
{
}
Console.WriteLine( f );
}
}
class Foo
{
public int x;
public int y;
public int z;
public Foo(int x, int y, int z, bool throwIt)
{
this.x = x;
this.y = y;
if( throwIt )
throw new Exception();
this.z = z;
}
public override string ToString()
{
return string.Format("{0} {1} {2}", x, y, z);
}
}
When you run this you get printed out
1 2 3
1 2 3
nothing very surprising there. Now change Foo to be a struct. The output is now
1 2 3
4 5 3
HUH?
The partial failure of the value type constructor has left the value fin an invalid state. To understand whats gone wrong we have to think how the memory model of reference types (classes) and value types (structs) differs.
For a reference type the reference refers to an object allocated on the managed heap. Each time new is called a new object is allocated on the heap, the constructor is run then the reference is set to it. If the constructor fails the reference assignment to the new object never occurs and so the old reference is still valid - everything in the world is good.
For a value type the call to new doesn't allocate any memory (the declaration of the variable does that. new simply allows us to state which constructor we'd like run over te memory. So in the second call to new in our example, we are simply reinitializing the same peice of memory allocated when we declared f. So the constructor is working on the memory of f and when it fails part way through it leaves it in a corrupt state.
The solution to this would be for the value type constructor to work over a temporary bit of memory and then bit blit it over the original when its completed successfully. Apparently this is what VB.NET does (kudos to that compiler team).