Tuesday, December 05, 2006 1:44 AM bart

C# 3.0 Feature Focus - Part 3 - Collection Initializers

 

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.

 

Collection Initializers

Everyone reading my blog should be familiar with this kind of code:

using System;

class Cini
{
   public static void Main()
   {
      string[] s1 = new string[] { "Bart", "John", "Rob" };
      string[] s2 = { "Bart", "John", "Rob" };
   }
}

Quite convenient to initialize an array right away. However, did you ever try this?

using System;
using System.Collections.Generic;

class Cini
{
   public static void Main()
   {
      List<string> l = new List<string>() { "Bart", "John", "Rob" };
   }
}

In C# 2.0, this doesn't work. You can already guess what the outcome will be in C# 3.0, it works indeed! (Note: the short syntax without a new initialization instruction doesn't work however - CS0622). In reality, the code above is translated into:

using System;
using System.Collections.Generic;

class Cini
{
   public static void Main()
   {
      List<string> l = new List<string>();
      l.Add("Bart");
      l.Add("John");
      l.Add("Rob");
   }
}

Verification in the IL code proves this:

.method public hidebysig static void Main() cil managed
{
   .entrypoint
   .locals init (class [mscorlib]System.Collections.Generic.List`1<string> V_0,
            class [mscorlib]System.Collections.Generic.List`1<string> V_1)
   IL_0000: nop
   IL_0001: nop
   IL_0002: newobj instance void class [mscorlib]System.Collections.Generic.List`1<string>::.ctor()
   IL_0007: stloc.1
   IL_0008: ldloc.1
   IL_0009: ldstr "Bart"
   IL_000e: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<string>::Add(!0)
   IL_0013: nop
   IL_0014: ldloc.1
   IL_0015: ldstr "John"
   IL_001a: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<string>::Add(!0)
   IL_001f: nop
   IL_0020: ldloc.1
   IL_0021: ldstr "Rob"
   IL_0026: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<string>::Add(!0)
   IL_002b: nop
   IL_002c: ldloc.1
   IL_002d: nop
   IL_002e: stloc.0
   IL_002f: ret
} // end of method Cini::Main

Behind the scenes, the compilers checks to see whether the type used implements IEnumerable and has a suitable Add method overload. So, doing the same with a non-generic collection like ArrayList won't work (CS1801: "The member initializer does not have an identifiable name") - update: this remark doesn't apply to the RTM release anymore:

using System;
using System.Collections;

class Cini
{
   public static void Main()
   {
      ArrayList l = new ArrayList() { "Bart", "John", "Rob" };
   }
}

An example to see this work against a self-made collection is shown below. For our convenience, we just wrap the List<T> collection type inside our own collection:

using System;
using System.Collections.Generic;

class Cini
{
   public static void Main()
   {
      Bar<string> b = new Bar<string> { "Bart", "John", "Rob" };
   }
}

public class Bar<T> : ICollection<T>
{
   private List<T> lst = new List<T>();

   public virtual void Add(T item)
   {
      Console.WriteLine(item);
      lst.Add(item);
   }

   public virtual void Clear()
   {
      lst.Clear();
   }

   public virtual void CopyTo(T[] item, int n)
   {
      lst.CopyTo(item, n);
   }

   public virtual bool Contains(T item)
   {
      return lst.Contains(item);
   }

   public virtual int Count
   {
      get { return lst.Count; }
   }

   public virtual bool IsReadOnly
   {
      get { return ((ICollection<T>)lst).IsReadOnly; }
   }

   public virtual bool Remove(T item)
   {
      return lst.Remove(item);
   }

   public virtual IEnumerator<T> GetEnumerator()
   {
      return lst.GetEnumerator();
   }

   System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
   {
      return ((System.Collections.IEnumerable)lst).GetEnumerator();
   }
}

Compiling and executing this code will print Bart, John and Rob on separate lines on the screen.

This feature becomes even more useful when combined with object initializers which were the subject of part 2 of this C# 3.0 Feature Focus series:

using System;
using System.Collections.Generic;

class Cini
{
   public static void Main()
   {
      List<Customer> customers = new List<Customer>() {
         new Customer { Name = "Bart", City = "Ghent"  Age = 23 },
         new Customer { Name = "John", City = "Zottegem", Age = 58 },
         new Customer { Name = "Rob" , City = "Schoten",  Age = 21 }
      }
;
   }
}

Try the same in C# 2.0 to see the difference with your own eyes. A quick calculation learns that we'd need 16 lines of code to achieve the same. Enjoy!

kick it on DotNetKicks.com

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

Filed under:

Comments

# 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

# Pattern Matching in C# - Part 6

Monday, April 14, 2008 10:22 AM by B# .NET Blog

Monday morning: The Return of The Pattern Matcher. After an awesome weekend (well, a Saturday at least

# 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