A while ago I posted about this new C# 3.0 feature. Although the IL code (aargh, yet another of these posts :-)) in that post reveals how object initializers are implemented internally, I wanted to make it slightly more explicit over here to avoid confusion.
Consider the following C# 3.0 fragment:
using System;
class Oini
{
public static void Main()
{
Customer c = new Customer() { Name = "Bart", City = "Redmond", Age = 24 };
}
}
This is not equivalent to:
using System;
class Oini
{
public static void Main()
{
Customer c = new Customer();
c.Name = "Bart";
c.City = "Redmond";
c.Age = 24;
}
}
It is equivalent to the following however:
using System;
class Oini
{
public static void Main()
{
Customer __t = new Customer();
__t.Name = "Bart";
__t.City = "Redmond";
__t.Age = 24;
Customer c = __t;
}
}
I know it's subtle but there's a very good reason to do it this way: atomic assignment. Assignments should be read from right to left:
Customer c = new Customer() { Name = "Bart", City = "Redmond", Age = 24 };
means
- evaluate the right-hand side (rhs):
new Customer() { Name = "Bart", City = "Redmond", Age = 24 }
- assign it to the left-hand side (lhs):
Customer c
therefore, we need a temporary object to evaluate the right-hand side first prior to assigning it to the target variable.
One place where this subtlety is quite important is the use of multi-threading. It's a bit of a convoluted sample, I agree, since in multi-threaded circumstances you'll likely have a locking-strategy or so in place to avoid base states but anyhow, here's a sample:
using System;
using System.Threading;
class Customer
{
public string Name { get; set; }
public string City { get; set; }
public int Age { get; set; }
}
class Demo
{
static Customer c;
static int n = 0;
static bool safe = false;
static void Main(string[] args)
{
safe = args.Length > 0;
Init();
AppDomain.CurrentDomain.UnhandledException +=
delegate(object sender, UnhandledExceptionEventArgs e)
{
Console.WriteLine("Invalid state after " + n + " assignments.");
Environment.Exit(1);
};
new Thread(() => Monitor()).Start();
for (n = 1; ; n++)
Init();
}
static void Init()
{
if (safe)
{
c = new Customer() {
Name = "Bart",
City = "Redmond",
Age = 24
};
}
else
{
c = new Customer();
c.Name = "Bart";
c.City = "Redmond";
c.Age = 24;
}
}
static void Monitor()
{
while (true)
{
if (c.Name != "Bart" || c.City != "Redmond" || c.Age != 24)
throw new Exception("Bad state detected");
}
}
}
If you execute this application without command-line arguments it should fail pretty quickly. However if you run it with some parameter, safe will be set to true, which causes Init to use the C# 3.0 implementation of object initialization, which is atomic. So we shouldn't get in intermediate states at all.
Here's the reverse translation of the Init body using Reflector:
If you understand this sample, you know why atomic assignment matters (and that's a good thing :-)). To pinpoint the core problem, consider the following fragment of Init (in the safe-negative case):
c = new Customer();
c.Name = "Bart";
c.City = "Redmond";
c.Age = 24; If the monitoring thread gets a chance to kick in somewhere in the middle of executing these four statements, the state is "corrupted". Obviously you can fix this by putting a lock in place (small exercise) but it's nice to know the atomic character of object initializers in C# 3.0 anyhow.
Enjoy!
Del.icio.us |
Digg It |
Technorati |
Blinklist |
Furl |
reddit |
DotNetKicks