During my LINQ to SQL session at DevConnections somebody in the audience asked what happens behind the scenes when you use type initializers in C# 3.0 and well it turns out I gave a hedged answer with a guess that was - uhm - wrong. So here's a discussion of how this feature actually works.
Type Initializers are a new language construct that allow for C# (and VB using similar syntax) to shortcut creating of custom constructors or writing additional code to initialize properties. For example if I have a type like this:
public class TimesheetReportParameters
{
public DateTime FromDate = DateTime.Now.AddMonths(-1);
public DateTime ToDate = DateTime.Now;
public string BillType = "Unbilled";
public bool MarkAsBilled;
public bool GenerateXml;
public bool SummaryReport;
public string ReportType = "TimeSheetClient";
public List<int> Companies = new List<int>();
}
I can instantiate and initiaize any number of properties with the following syntax:
TimesheetReportParameters parms =
new TimesheetReportParameters()
{
BillType = "Unbilled",
GenerateXml = true,
FromDate = DateTime.Now
};
This makes it a little less verbose to initialize the type and allows you to quickly and effectively assign default values. Actually it's not all that much less code than for explicit assignment since you'd can use normal assign syntax (ie. parms.FromDate = DateTime.Now;) for it just as easily with only a few more keystrokes. But what's important here is the fact that a single statement/expression can accomplish the task of creating and initializing the new type.
This feature becomes extremely useful when combined with anonymous types and - as is often the case with new C# features when you throw LINQ into the mix. When creating an anonymous type on the fly the above syntax allows you to create the new properties simply by assigning new values to the 'anonymous' properties.
So if I want to create a new anonymous type on the fly I can use code like this:
var workOrder = new
{
OrderId = "A321",
Entered = DateTime.Now,
Title = "Delivery Request",
Descript = ""
};
In this scenario both a new type is created, with new property assignments and the type initialization features both take advantage of type inferrence in C# 3.0 to determine the types for each of the new properties as well as assigning the value to each property.
All of this becomes extremely useful once you start using LINQ. When you use projection to create your result list the result list type can be either dynamically created using the anonymous type or creating an existing type and using the type initializer syntax to assign the member properties with compact syntax:
IQueryable<CustomerListResult> custList =
from c in this.Context.CustomerEntities
select new CustomerListResult { Company = c.Company, Pk = c.Pk };
The type initializer here allows the expression-like syntax for describing the output type for the result list. The same applies with anonymous type result:
var q =
from c in this.Context.CustomerEntities
select new { Company = c.Company, Pk = c.Pk };
Here the result list type is projected into an anonymous type but again the type is intialized with the simplified syntax for the value assignments. The syntax is clear and concise and more importantly it allows it to work in a single line of code which is required in order to work inside of a LINQ query.
So during my DevConnections LINQ to SQL session the question was asked: What happens behind the scenes when a type is created with type initializers. I replied that I thought that C# would actually generate a specialized constructor for the type with a parameter signature for each assignment, but I wasn't sure... so after the session I took a look with Reflector to see what actually gets generated by the compiler and - blush - it turns out I was wrong.
The original expression:
TimesheetReportParameters parms =
new TimesheetReportParameters()
{
BillType = "Unbilled",
GenerateXml = true,
FromDate = DateTime.Now
};
Is turned into the following by the compiler:
TimesheetReportParameters g__initLocalb = new TimesheetReportParameters();
g__initLocalb.BillType = "Unbilled";
g__initLocalb.GenerateXml = true;
g__initLocalb.FromDate = DateTime.Now;
TimesheetReportParameters parms = g__initLocalb;
An instance is created and the compiler generates the necessary assign statements. What's surprising though is that an intermediate instance is created for the instance and the assignment which is then assigned to the actual reference returned. Offhand I can't really see what purpose this serves, given that type initializers are always instance level code. Anybody know why this indirect referencing might be required?
It's also interesting to look at the anonymous type scenario. Looking at the disassembled C# code doesn't help much since Reflector understands anonymous types and so properly displays the C# syntax for the anonymous type looking pretty much the same as the declared code. However looking at the raw IL you can find that the anonymous indeed receives the property assignments as parameters to the constructor:
Here's the C# code again:
var workOrder = new
{
OrderId = "A321",
Entered = DateTime.Now,
Title = "Delivery Request",
Descript = ""
};
The IL:
.method public hidebysig instance void Test() cil managed
{
.maxstack 5
.locals init (
[0] class <>f__AnonymousType0`4<string, valuetype [mscorlib]System.DateTime, string, string> workOrder)
L_0000: nop
L_0001: ldstr "A321"
L_0006: call valuetype [mscorlib]System.DateTime [mscorlib]System.DateTime::get_Now()
L_000b: ldstr "Delivery Request"
L_0010: ldstr ""
L_0015: newobj instance void <>f__AnonymousType0`4<string, valuetype [mscorlib]System.DateTime, string, string>::.ctor(!0, !1, !2, !3)
L_001a: stloc.0
L_001b: ret
}
The newobj line shows the constructor signature and the variables being loaded into the constructor call. So I was at least partially right <g>...
Looking at the IL code for much of this compiler magic gets you goggle eyed quickly though. Looking at the LINQ query in the above code quickly gets unreadable in IL even for this simple query, not that it matters much.
I know I've started to use a number of the C# 3.0 features quite regularly and when going back to plain 2.x code I already miss them. Type initializers and the related Collection inializers and anonymous types are amongst the ones that I know I'll use frequently.
Other Posts you might also like