Sunday, December 03, 2006 5:47 AM
bart
C# 3.0 Feature Focus - Part 1 - Local Type Inference
Introduction
In this C# 3.0 Feature Focus Week we'll focus on the new language features that will be part of C# 3.0 "Orcas". As you already might know, the major innovation on Orcas is the LINQ (Language Integrated Query) technology that will make data queries a first class citizen of the language (C# 3.0 and VB 9.0). In order to unlock this potential, a set of language features were added to the language that all work in concert to enable LINQ stuff. However, all of those individual features prove to be useful in isolation as well. Later, we'll focus on LINQ itself.
Local Type Inference
One of the goals of C# 3.0 is to make the language cleaner and smoother. C# 2.0's major innovation was undoubtly generics, but using these introduced quite a bit of "noise" when declaring and instantiating generic types. Consider the following piece of code:
Dictionary<string, List<Customer>> dictionary = new Dictionary<string, List<Customer>>();
I think we all agree there's some redundant information in this particular piece of code, isn't it? As the matter in fact, the compiler can be intelligent enough to find out about the type of the variable when a rvalue is specified in the assignment statement. This is where Local Type Inference enters the scene, allowing you to abbreviate the code fragment above to:
var dictionary = new Dictionary<string, List<Customer>>();
It's important to understand that these two lines of code are 100% identical for what the compiled program (i.e. the IL code) and the CLR is concerned. In other words, we don't sacrifice any strong typing or type safety at all. The var keyword isn't a replacement for something like System.Object or so! To prove this, take a look at this:
using System;
using System.Collections.Generic;
class Lti
{
public static void Main()
{
int a = 123;
var b = 123;
string s = "Hello";
var t = "Hello";
Dictionary<string, List<int>> d = new Dictionary<string, List<int>>();
var e = new Dictionary<string, List<int>>();
}
}
This piece of code will create 6 local variables in IL-code named V_0 to V_5. We should see that every pair V_2n and V_2n+1 for n=0..2 has two completely identical declarations and instantiations:
.method public hidebysig static void Main() cil managed
{
.entrypoint
.locals init (int32 V_0,
int32 V_1,
string V_2,
string V_3,
class [mscorlib]System.Collections.Generic.Dictionary`2<string,class [mscorlib]System.Collections.Generic.List`1<int32>> V_4,
class [mscorlib]System.Collections.Generic.Dictionary`2<string,class [mscorlib]System.Collections.Generic.List`1<int32>> V_5)
IL_0000: nop
IL_0001: ldc.i4.s 123
IL_0003: stloc.0
IL_0004: ldc.i4.s 123
IL_0006: stloc.1
IL_0007: ldstr "Hello"
IL_000c: stloc.2
IL_000d: ldstr "Hello"
IL_0012: stloc.3
IL_0013: newobj instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,class [mscorlib]System.Collections.Generic.List`1<int32>>::.ctor()
IL_0018: stloc.s V_4
IL_001a: newobj instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,class [mscorlib]System.Collections.Generic.List`1<int32>>::.ctor()
IL_001f: stloc.s V_5
IL_0021: nop
IL_0022: ret
} // end of method Lti::Main
Observe the 100 percent identical code for V_0 (IL_0001-IL_0003) and V_1 (IL_0004-IL-0006), for V_2 and V3 and for V_4 and V_5.
So far, you've seen the use of the var keyword as a convenience feature only. However, there are situations where the use of var is a must, more specifically when working with anonymous types. Such types are typically created in the projection (aka "select") part of a LINQ query, but let's give a simple example of this. We'll cover anonymous types in more detail in a future episode of the C# 3.0 Feature Focus.
using System;
class Lti
{
public static void Main()
{
var a = new { Number = 123, Name = "Bart" };
}
}
This piece of code creates an anonymous type with a compiler-generated name. So, there's no way we can use the name directly. Because of this, var is the only possible way to refer to the auto-generated type. The IL code reveals how this is done:
.method public hidebysig static void Main() cil managed
{
.entrypoint
.locals init (class Lti/'<Projection>f__0' V_0,
class Lti/'<Projection>f__0' V_1)
IL_0000: nop
IL_0001: nop
IL_0002: newobj instance void Lti/'<Projection>f__0'::.ctor()
IL_0007: stloc.1
IL_0008: ldloc.1
IL_0009: ldc.i4.1
IL_000a: callvirt instance void Lti/'<Projection>f__0'::set_Number(int32)
IL_000f: nop
IL_0010: ldloc.1
IL_0011: ldstr "Bart"
IL_0016: callvirt instance void Lti/'<Projection>f__0'::set_Name(string)
IL_001b: nop
IL_001c: ldloc.1
IL_001d: nop
IL_001e: stloc.0
IL_001f: ret
} // end of method Lti::Main
Don't worry about the two local variables being created, just focus on the type of those: Lti/'<Projection>f__0' V_0. This is our anonymous type, which has two properties with getters and setters defined, as well as overrides for System.Object::Equals and System.Object::GetHashCode. Observe the use of the "Projection" word in the auto-generated name, which indicates typical usage for projections in LINQ.
Last but not least, var can also be used in the context of a foreach loop. Pretty easy to understand; after all the iterator variable in a foreach loop is a local variable too. And again, the use of var is a must over here in case you need to iterate over an IEnumerable<T> result produced by LINQ where T is some "projected" anonymous type. An example using a non-anonymous type is shown below:
using System;
using System.Collections.Generic;
class Lti
{
public static void Main()
{
var l = new List<string>();
foreach (var i in l)
;
}
}
Okay, doesn't make much sense, but for the sake of the IL inspection this should be enough. The first use of var shouldn't be a concern anymore, instead we'll focus on the use of var in the foreach loop. This piece of code gets translated into:
.method public hidebysig static void Main() cil managed
{
.entrypoint
.locals init (class [mscorlib]System.Collections.Generic.List`1<string> V_0,
string V_1,
valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<string> V_2,
bool V_3)
IL_0000: nop
IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List`1<string>::.ctor()
IL_0006: stloc.0
IL_0007: nop
IL_0008: ldloc.0
IL_0009: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<string>::GetEnumerator()
IL_000e: stloc.2
.try
{
IL_000f: br.s IL_0019
IL_0011: ldloca.s V_2
IL_0013: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<string>::get_Current()
IL_0018: stloc.1
IL_0019: ldloca.s V_2
IL_001b: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<string>::MoveNext()
IL_0020: stloc.3
IL_0021: ldloc.3
IL_0022: brtrue.s IL_0011
IL_0024: leave.s IL_0035
} // end .try
finally
{
IL_0026: ldloca.s V_2
IL_0028: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<string>
IL_002e: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0033: nop
IL_0034: endfinally
} // end handler
IL_0035: nop
IL_0036: ret
} // end of method Lti::Main
If you've had prior exposure to enumerators, you should be able to dim the lights on all of the IL noise that's related to the iteration itself. There are only two lines you should consider: IL_0013 and IL_0018 which stores the current item in the iteration in local variable V_1 which is ... of type string. So as you can see, the var keyword did its inference job correctly, based on the IEnumerable<T>'s generic type parameter which is in our case string.
Quiz
Which of the following code fragments compile and which don't (and why)? Have fun!
Fragment 1
using System;
class Lti
{
public static void Main()
{
var a;
a = 123;
}
}
Fragment 2
using System;
class Lti
{
public static void Main()
{
int a = 123;
var b = a;
}
}
Fragment 3
using System;
using System.Collections.Generic;
class Lti
{
public static void Main()
{
IEnumerable<string> l = new List<string>();
var m = l;
int i = m.Count;
}
}
Fragment 4
using System;
using System.Collections.Generic;
class Lti
{
private var a = 123;
private const var b = 123;
private static var c = 123;
public static void Main()
{
}
}
Del.icio.us |
Digg It |
Technorati |
Blinklist |
Furl |
reddit |
DotNetKicks
Filed under: C# 3.0