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 (varin 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()
   {
   }
}

kick it on DotNetKicks.com

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under:

Comments

# re: C# 3.0 Feature Focus - Part 1 - Local Type Inference

Wednesday, December 06, 2006 6:17 PM by Eber Irigoyen

I would say, fragment 1 doesn't work, the declaration needs to infer the type

# re: C# 3.0 Feature Focus - Part 1 - Local Type Inference

Thursday, December 07, 2006 2:37 AM by bart

Hi Eber,

That's correct: fragment 1 doesn't compile indeed. Notice you didn't say anything about the other fragments, so with ternary logic (yes/no/undecisive) your answer is correct. However, what about the other fragments?

-Bart

# C# 3.0 Automatic Properties explained

Saturday, March 03, 2007 2:34 PM by B# .NET Blog

A few months ago in the TechEd: Developers Europe timeframe I blogged about the Automatic Property feature

# The IQueryable tales - LINQ to LDAP - Part 0

Thursday, April 05, 2007 7:04 PM by B# .NET Blog

Here we are again for some cool LINQ stuff. In the past I've been blogging on C# 3.0 language innovation

# The IQueryable tales - LINQ to LDAP - Part 1: Key concepts

Friday, April 06, 2007 8:14 AM by B# .NET Blog

Introduction Welcome to the first real part of our LINQ-to-LDAP series. So far, we've been discussing:

# New "Orcas" Language Feature: Anonymous Types

Tuesday, May 15, 2007 12:56 AM by ScottGu's Blog

Over the last two months I've published a series of posts covering some of the new language features

# New "Orcas" Language Feature: Anonymous Types

Tuesday, May 15, 2007 1:23 AM by BusinessRx Reading List

Over the last two months I've published a series of posts covering some of the new language features

# New "Orcas" Language Feature: Anonymous Types

Tuesday, May 15, 2007 1:29 AM by ASP.NET

Over the last two months I've published a series of posts covering some of the new language features

# Nueva caracter??stica de "Orcas": Tipos an??nimos &laquo; Thinking in .NET

Pingback from  Nueva caracter??stica de "Orcas": Tipos an??nimos &laquo; Thinking in .NET

# 新Orcas语言特性:匿名类型

Sunday, May 20, 2007 7:27 PM by Joycode@Ab110.com

【原文地址】 New &quot;Orcas&quot; Language Feature: Anonymous Types 【原文发表日期】 Tuesday, May 15, 2007 7:02 AM

# 新Orcas语言特性:匿名类型

Sunday, May 20, 2007 10:36 PM by shoutor

匿名类型是C#和VB的方便语言特性,它允许开发人员在代码内简明地定义行内CLR类型,而不用显式地对类型定义一个正式的类声明。这个LINQ的强有力的特性允许你对一个数据源(不管这个数据源是数据库,XML文件还是内存中的集合)做查询操作,然后对查询数据的结果构形成与原先数据源不同的结构或格式。但有的时候,我只想要在我当前的代码范围内查询和操作数据,我不想要另外正式地定义一个类来代表我的数据,才可以操作数据。在这种情形下,匿名类型非常有用,因为它们允许你在你的代码内,简明地定义一个新类型在行内使用。

# Local Type Inference

Monday, November 19, 2007 8:56 AM by Gyun's Blog

Local Type Inference

# C# 3.0 Feature Focus – Link Collection

Saturday, August 09, 2008 7:13 AM by B# .NET Blog

Collecting a few of my posts for easy quick reference: C# 3.0 Feature Focus - Part 1 - Local Type Inference

# 新Orcas语言特性:匿名类型

Monday, January 19, 2009 4:16 PM by PSCWAY

【原文地址】New

# New &#8220;Orcas&#8221; Language Feature: Anonymous Types | OOP - Object Oriented Programing

Pingback from  New &#8220;Orcas&#8221; Language Feature: Anonymous Types | OOP - Object Oriented Programing

# Using LINQ to SQL :Anonymous Types &laquo; T. Nayak

Saturday, March 19, 2011 2:39 AM by Using LINQ to SQL :Anonymous Types « T. Nayak

Pingback from  Using LINQ to SQL :Anonymous Types &laquo; T. Nayak