Friday, May 1, 2009

Object initializers and Collection initializers- Features of C# 3.0 (Part - 4)

In my previous blog we saw some of the new features introduced in .NET 3.0 like Extension Methods, Automatic Properties and Implicitly Typed Variable. This blog we will see Object initializers and Collection initializers.

We all have worked with objects and properties. If you are a .NET developers then there is no escaping from these. Suppose you have a class named car with the following structure.

public class Car
{
    public string Color
    { get; set; }
    public string Model
    { get; set; }
    public int NoOfDoors
    { get; set; }
}

The Car class in the above code has three properties (properties are defined using Automatic Property) namely Color, Model and NoOfDoors. Now if you want to assign values to these properties then we would write something like this.

Car car = new Car();
car.Model = "Ferrari F430 Spider";
car.NoOfDoors = 2;
car.Color = "Ferrari Red";

In the above code, we are first declaring an object of type Car and assigning the properties of the object one by one. Wont it be nice to trim some lines of code from the above code? This can be achieved with the new C# 3.0’ object initializer feature. Using Object initializer you can rewrite your code something like this.

Car car = new Car { Model = "Ferrari F430 Spider", Color = "Ferrari Red", NoOfDoors = 1 };

As you can see from the above code, object initializer feature helps us to assign the public properties while creating the object without having to call the constructor. To assign the property one has to type the property name followed by the equal to sign and value to be assigned within the curly braces. Now what happens if you have overloaded constructors which take arguments and you want use them along with object intializers? Don’t worry, one can very well use the overloaded constructor along with object initializers as shown below.

//Overloaded Constructor of Car class
public Car(string theModel) { this.Model = theModel };

Code using overloaded constructor along with object initializers.

//Passing the value for Model property through the overloaded constructor.
Car car2 = new Car("Ferrari F430") { Color = "Ferrari Red", NoOfDoors = 2 };

From the above code one can see we are calling the overloaded constructor with the value which needs to be assigned to the Model property of the Car class. Object initializers can be used with public fields as well. An e.g. is shown below.

//Bike class having public fields.
public class Bike
{
    public string Color;       
    public int CubicCapacity;       
}

//Assigning values to public fields using object initializers.
Bike bike = new Bike () { Color = "Red", CubicCapacity = 300 };

One can see that while using object initializers along with Bike object I have made use of “()” i.e. constructor call. This syntax is optional, even without the brackets the code will work perfectly fine. One can make use of object initializers even with complex objects, say for e.g. you have a CarEngine property (which is of type Engine class) inside the car class, you can declare the Car object with Engine object as a nested object as shown below.

//Car class with a complex Engine object as one of the property.
public class Car
{
    public Car(){}
    //Overloaded Constructor of Car class
    public Car(string theModel) { this.Model = theModel; }
    public string Color { get; set; }
    public string Model { get; set; }
    public int NoOfDoors { get; set; }
    public Engine CarEngine { get; set; }
}
//Engine class.
public class Engine
{
    public int CubicCapacity  { get; set; }
    public int NoOfValves { get; set; }
    public int BHP { get; set; }
}

//Nested object declaration using object initializers
Car car2 = new Car("Ferrari F430") { Model = "test", Color = car.Color, NoOfDoors = car.NoOfDoors, CarEngine = new Engine { CubicCapacity = 4550, BHP = 250, NoOfValves = 12 } };

The above code shows the revised Car class with CarEngine as a property which is of Engine class type. In the last three lines we can see the Car object has been declared using object initializer feature with a nested Engine class.

Object Initializers with Anonymous types

Object initializers can be used with Anonymous types as well. I know I have promised in my previous blog that my next blog will be on Anonymous types, but, I thought before explaining Anonymous types it is worth that people should know object initializers. For the time being just understand as the name suggests anonymous types don’t have any type. The below code will give you an idea of what is Anonymous types.

//Declaring Anonymous type.
var Employee = new { FirstName = "Sandeep", LastName = "P.R", Dept = "Software Developement" };
//Writing the value of the Anonymous object property value to the console.
Console.WriteLine("Name: {0}\nDepartment: {1}", Employee.FirstName, Employee.Dept);

Object initializers combined with Anonymous types can be used to create runtime objects without any concrete type. The scenario where this can be efficiently used is when you have complex object with lots of properties and you want to just make use of only two or three properties. Creating an instance of such a complex object is pure waste of memory space so instead of that one can create an Anonymous object with the required property.

Collection Initializers

Till now we had a look at object initialization, wont it be good if we had similar thing in arrays. Guess what, with C# 3.0 arrays/collection objects can be initialized in the same way. Sample code is shown below.

List<string> name = new List<string>() { "Sandeep", "Microsoft", "Windows XP", "Windows Vista" };

System.Collections.ArrayList oddArray = new System.Collections.ArrayList { new Car { Model = "BMW 7 Series" }, new Car{Model="BMW 5 Series", Color="Black"} };

The same was not possible in the early versions of C#. In C# 3.0 the compiler does the extra work of generating the code necessary to add the elements to the collection. The IL code doing the extra work for the above code is pasted below.

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 3
    .locals init (
        [0] class [mscorlib]System.Collections.Generic.List`1<string> name,
        [1] class [mscorlib]System.Collections.ArrayList oddArray,
        [2] class [mscorlib]System.Collections.Generic.List`1<string> <>g__initLocal0,
        [3] class [mscorlib]System.Collections.ArrayList <>g__initLocal1,
        [4] class Blog_Using.Car <>g__initLocal2,
        [5] class Blog_Using.Car <>g__initLocal3)
    L_0000: nop
    L_0001: newobj instance void [mscorlib]System.Collections.Generic.List`1<string>::.ctor()
    L_0006: stloc.2
    L_0007: ldloc.2
    L_0008: ldstr "Sandeep"
    L_000d: callvirt instance void [mscorlib]System.Collections.Generic.List`1<string>::Add(!0)
    L_0012: nop
    L_0013: ldloc.2
    L_0014: ldstr "Microsoft"
    L_0019: callvirt instance void [mscorlib]System.Collections.Generic.List`1<string>::Add(!0)
    L_001e: nop
    L_001f: ldloc.2
    L_0020: ldstr "Windows XP"
    L_0025: callvirt instance void [mscorlib]System.Collections.Generic.List`1<string>::Add(!0)
    L_002a: nop
    L_002b: ldloc.2
    L_002c: ldstr "Windows Vista"
    L_0031: callvirt instance void [mscorlib]System.Collections.Generic.List`1<string>::Add(!0)
    L_0036: nop
    L_0037: ldloc.2
    L_0038: stloc.0
    L_0039: newobj instance void [mscorlib]System.Collections.ArrayList::.ctor()
    L_003e: stloc.3
    L_003f: ldloc.3
    L_0040: newobj instance void Blog_Using.Car::.ctor()
    L_0045: stloc.s <>g__initLocal2
    L_0047: ldloc.s <>g__initLocal2
    L_0049: ldstr "BMW 7 Series"
    L_004e: callvirt instance void Blog_Using.Car::set_Model(string)
    L_0053: nop
    L_0054: ldloc.s <>g__initLocal2
    L_0056: callvirt instance int32 [mscorlib]System.Collections.ArrayList::Add(object)
    L_005b: pop
    L_005c: ldloc.3
    L_005d: newobj instance void Blog_Using.Car::.ctor()
    L_0062: stloc.s <>g__initLocal3
    L_0064: ldloc.s <>g__initLocal3
    L_0066: ldstr "BMW 5 Series"
    L_006b: callvirt instance void Blog_Using.Car::set_Model(string)
    L_0070: nop
    L_0071: ldloc.s <>g__initLocal3
    L_0073: ldstr "Black"
    L_0078: callvirt instance void Blog_Using.Car::set_Color(string)
    L_007d: nop
    L_007e: ldloc.s <>g__initLocal3
    L_0080: callvirt instance int32 [mscorlib]System.Collections.ArrayList::Add(object)
    L_0085: pop
    L_0086: ldloc.3
    L_0087: stloc.1
    L_0088: ret
}

If you carefully examine the above IL one can see the C# compiler has done the following extra work

  • Generated the code to call the constructor of the objects we have initialized using object intializer.
  • Generated the code to assign the properties.
  • Generated the code to add the object to the collection object.

So with this we can say, behind the scene the compiler is doing most of the hard work which we as developers use to do repeatedly and it was quite mundane. So with C# 3.0’ object initializer feature Microsoft has relieved developers from writing the same code again and again and given the onus of doing that to the good old compiler. Don’t you think that’ this a great think to do, as we developers have lot of other great stuff to do than to sit and write tons of code to just initialize objects and collections. So what are you waiting for, start making use of these new features and write terse code and don’t forget, always try to learn more.

Next blog anonymous types.

Sandeep.

1 comment:

  1. This is excellent article on object initialization.

    ReplyDelete

Please provide your valuable comments.