in

Foo Theory

Partners in Community - serving up some ice cold Kool-Aid!
Welcome to footheory.com.  The bloggers and contributing members on this site are consultants, project/program managers and software architects working across the US.  Our community will focus on Microsoft technologies, .NET architecture, software patterns & practices and just plain stream of consciousness.

Bennie's Weblog

New Features in C# 3.0, Part 3: Automatic Properties, Object Initializers and Collection Initializers

Introduction

This post does not focus on one particular new feature is the rich new feature set of  C# 3.0, but rather studies a set of features, which are loosely related, and play a roll in the enablement of LINQ in the .NET 3.5 framework, which is ultimately the focus of this series of posts.

In this post, we will study the following topics:

  1. Automatic properties. Automatic properties allow you to declare class properties using a very terse syntax, increasing your productivity and reducing the amount of  typing that  you have to do.
  2. Object Initializers. Object initializers allow you to use a focused, "short hand" notation to initialize the properties of a class.
  3. Collection Initializers. Like object Initializers allow you to use a "short hand" syntax for the initialization of a class, collection initializers allow you to do the same thing for your collections.

Previous posts is this series are:

The code for this feature can be downloaded here.

Ok enough talk, let's have a look at our first "feature of the day": Automatic Properties!

Automatic Properties

Overview

Note: the code for this feature is contained in the "AutomaticProperties" console application of the code.

 Everybody has written typical "data container" classes before. These are the typical classes which contains simple fields, and properties with both a getter and a setter for each fields. And every develop I know simply HATES writing these things, because they are what I call "monkey code", code that could be written by semi-intelligent primates (or even a project manager ;-).

Below are some simple examples of such classes:

1 namespace AutomaticProperties.Classic 2 { 3 /// <summary> 4 /// This is our "classic, old style" CoffeeShop class 5 /// </summary> 6 public class CoffeeShop 7 { 8 private string _shopName; 9 private Address _address; 10 private decimal _priceOfLatte; 11 private string _baristaName; 12 13 public string ShopName 14 { 15 get 16 { 17 return _shopName; 18 } 19 set 20 { 21 _shopName = value; 22 } 23 } 24 25 public Address Address 26 { 27 get 28 { 29 return _address; 30 } 31 set 32 { 33 _address = value; 34 } 35 } 36 37 public Decimal PriceOfLatte 38 { 39 get 40 { 41 return _priceOfLatte; 42 } 43 set 44 { 45 _priceOfLatte = value; 46 } 47 } 48 49 public string BaristaName 50 { 51 get 52 { 53 return _baristaName; 54 } 55 set 56 { 57 _baristaName = value; 58 } 59 } 60 } // class AutomaticProperties.Classic.CoffeeShop 61 } 62

1 namespace AutomaticProperties.Classic 2 { 3 /// <summary> 4 /// This is our "classic, old style" Address class 5 /// </summary> 6 public class Address 7 { 8 private string _streetName; 9 private int _streetNumber; 10 private string _city; 11 private string _state; 12 private int _zipCode; 13 14 public string StreetName 15 { 16 get 17 { 18 return _streetName; 19 } 20 set 21 { 22 _streetName = value; 23 } 24 } 25 26 public int StreetNumber 27 { 28 get 29 { 30 return _streetNumber; 31 } 32 set 33 { 34 _streetNumber = value; 35 } 36 } 37 38 public string City 39 { 40 get 41 { 42 return _city; 43 } 44 set 45 { 46 _city = value; 47 } 48 } 49 50 public string State 51 { 52 get 53 { 54 return _state; 55 } 56 set 57 { 58 _state = value; 59 } 60 } 61 62 public int ZipCode 63 { 64 get 65 { 66 return _zipCode; 67 } 68 set 69 { 70 _zipCode = value; 71 } 72 } 73 } // class AutomaticProperties.Classic.Address 74

The code above defines two classes, both of which are in the AutomaticProperties.Classic namespace:

  1. CoffeeShop
  2. Address

Automatic properties allow you to avoid having to manually declare a private field and write the get/set logic -- instead the compiler will automatically creating the private field and the default get/set operations for you.  Below are the CoffeeShop and Address classes rewritten using Automatic Properties (note that both classes are in the AutomaticProperties.New namespace):

1 public class CoffeeShop 2 { 3 public string ShopName { get; set; } 4 public Address Address { get; set; } 5 public decimal PriceOfLatte { get; set; } 6 public string BaristaName { get; set; } 7 }

1 public class Address 2 { 3 public string StreetName { get; set; } 4 public int StreetNumber { get; set; } 5 public string City { get; set; } 6 public string State { get; set; } 7 public int ZipCode { get; set; } 8 }

That's it. Note that we went from 100+ lines of code to a grand total of 15!

When the C# 3.0 compiler encounters an empty get/set property implementation like we created above in the CoffeeShop and Address classes, it will now automatically generate a private field for us within our class, and implement a public getter and setter property implementation for it.  The benefit of this is that from a type-contract perspective, the class looks exactly like it did with my first (much more verbose) implementation shown above.  This means that -- unlike resorting to public fields -- I can in the future add validation logic within my property setter implementation without having to change any external component that references my class.

Usage Example

Now, using "classic" classes, and classes that use automatic properties is completely identical. To provide this, I have included a small piece of client code in the download file, that looks as follows:

1 using System; 2 3 // Comment out one of the using statements below 4 // to either use the "Classic" or the "New-Style" 5 // Automatic Properties-style classes 6 7 using AutomaticProperties.Classic; // uses class classes 8 //using AutomaticProperties.New; // uses automatic properties 9 10 namespace AutomaticProperties 11 { 12 class Program 13 { 14 static void Main(string[] args) 15 { 16 Address address = new Address(); 17 address.StreetNumber = 50; 18 address.StreetName = "Passover Lane"; 19 address.ZipCode = 6703; 20 address.State = "MO"; 21 address.City = "Missoula"; 22 23 CoffeeShop coffeeShop = new CoffeeShop(); 24 coffeeShop.Address = address; 25 coffeeShop.PriceOfLatte = 3.95M; 26 coffeeShop.ShopName = "It's a Grind!"; 27 coffeeShop.BaristaName = "6 Shot Lucie"; 28 } 29 } 30 } 31

You can comment out either the "using AutomaticProperties.Classic" or the "AutomaticProperties.New" line, and depending on which "using" line is not commented out, the code will use  the classic classes, or the classes with the automatic properties. Note though that the rest of code is unchanged.

There is one interesting thing to try though. If you use the "classic" objects, and you set a breakpoint on a line that sets a property, for example:

MainBreakPoint

and you "Step In" (F11), then you will actually go to the setter of the property implementation, as shown below:

StreetNumberBreak

this makes sense, since we provided this setter in C#!

Automatic Properties in detail

Now, when you switch to the "automatic properties" version of the objects, and you set the same breakpoint and you step in, you notice that nothing actually happens, which makes sense, because there is only direct IL code, generated by the compiler behind the setter implementation, and no C# code, so the compiler has nowhere to go!

The easiest way to verify this is to bring out our old friend ILDASM.exe, and open our "automatic properties" version Address class is this tool. It would look something like this:

ILDASM_Address

now, we can compare this to the "classic" style Address class:

ILDASM_ADDRESS_Classic

The we notice that on the surface things very much look the same. Both classes have their properties, and their getters and setters. BUT the big difference is the the "classic" version has the actual fields as we defined them, while for the "automatic properties" version,the compiler injected  a private member variable, prefixed with <k>BackingField, like this one for the City property:

1 .field private string '<City>k__BackingField' 2 .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )

The CompilerGeneratedAttribute is useful if you have a tool that would like to find out if something is auto-generated. The getter IL implementation look like this:

1 .method public hidebysig specialname instance string 2 get_City() cil managed 3 { 4 .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 5 // Code size 11 (0xb) 6 .maxstack 1 7 .locals init (string V_0) 8 IL_0000: ldarg.0 9 IL_0001: ldfld string AutomaticProperties.New.Address::'<City>k__BackingField' 10 IL_0006: stloc.0 11 IL_0007: br.s IL_0009 12 IL_0009: ldloc.0 13 IL_000a: ret 14 } // end of method Address::get_City

Note the reference to the backing field in line 9. The setter IL looks very similar:

1 .method public hidebysig specialname instance void 2 set_City(string 'value') cil managed 3 { 4 .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 5 // Code size 8 (0x8) 6 .maxstack 8 7 IL_0000: ldarg.0 8 IL_0001: ldarg.1 9 IL_0002: stfld string AutomaticProperties.New.Address::'<City>k__BackingField' 10 IL_0007: ret 11 } // end of method Address::set_City

Again note the setting of the backing field at line 9. So, as you can see, the "automatic property" version of the code has NO C# code behind the getters and setters of the properties, which is why we could not step into the code with the debugger.

One last important note: Automatic properties should have both a getter and a setter. If you leave out one of the two, you will get a compiler error like this:

Error 1 'AutomaticProperties.New.Address.City.get' must declare a body because it is not marked abstract or extern. Automatically implemented properties must define both get and set accessors.

When you think about it this really makes sense, the whole concept behind automatic properties is to allow you to quickly write typical "data container" or "property bag" classes, which typically have read/write properties.

Object Initializers

Overview

Note: the code for this feature is contained in the "ObjectInitializers" console application of the code.

 When we write code that deals with simple objects, we tend to rely heavily on the use of properties. The sample code for this section contains a class named "CarModel" which represents the model of a car. The class is shown below:

1 public class CarModel 2 { 3 private string _make; 4 private string _model; 5 private int _year; 6 7 public string Make 8 { 9 get { return _make; } 10 set { _make = value; } 11 } 12 13 public string Model 14 { 15 get { return _model; } 16 set { _model = value; } 17 } 18 19 public int Year 20 { 21 get { return _year; } 22 set { _year = value; } 23 } 24 25 public override string ToString() 26 { 27 return String.Format( 28 "Make: {0}, Model: {1}, Year: {2} ", Make, Model, Year); 29 } 30 } 31

In a typical approach, we would initialize a new instance of this class as follows:

1 private static void CreateCarModelTraditional() 2 { 3 CarModel carModel = new CarModel(); 4 carModel.Make = "Acura"; 5 carModel.Model = "TL"; 6 carModel.Year = 2007; 7 Console.WriteLine("Traditional CarModel: " + carModel); 8 9 } // method CreateCarModelTraditional

While this is a tried-and-true approach that has served us well for many years, it is very verbose. C# 3.0 now offers a new feature called "Object Initializers", which allows us to write the above code as follows:

1 private static void CreateCarModelWithInitializer() 2 { 3 CarModel carModel = new CarModel {Make = "Acura", Model = "TL", Year = 2007}; 4 Console.WriteLine("\nCarModel with Initializer: " + carModel); 5 6 } // method CreateCarModelWithInitializer

All this is is really "Syntactic Sugar", which at the IL level results in the same code as the tradition approach. Indeed, the compiler will automatically generate all of the property-setter code, which has exactly the same implementation as our first, more verbose example (more in on this in our "In-Depth" section).

Note that Object Initializers will also work with more complex objects, which have nested sub-objects. For example, below is the code for a "Car" class, which contains an embedded "CarModel" instance:

1 public class Car 2 { 3 public CarModel CarModel { get; set; } 4 public int EngineCC { get; set; } 5 public int HoursePower { get; set; } 6 public string Color { get; set; } 7 8 public override string ToString() 9 { 10 return String.Format( 11 "Make: {0}, Model: {1}, Year: {2}, \n\tEngineCC: {3}, HorsePower: {4}, Color: {5}", 12 CarModel.Make, CarModel.Model, CarModel.Year, EngineCC, HoursePower, Color); 13 } 14 }

Note that is this case, I used the more compact "Automatic Properties" approach described in the first part of this post . Indeed Object Initializers work directly with the property setters, so they don't know or care that you used the traditional approach, or the "Automatic Properties" approach to create your properties.

Below is an example of an object initializer for a Car instance:

1 private static void createCompoundObject() 2 { 3 // Create a Car instance with an initializer 4 Car car = new Car { 5 Color = "White", 6 HoursePower = 320, 7 EngineCC = 3600, 8 CarModel = new CarModel {Make = "Acura", Model = "TL Type S", Year = 2007}}; 9 10 Console.WriteLine("\nComplete Car With Initializer: " + car + Environment.NewLine); 11 12 } // method createCompoundObject

In line 8, you can see how we embedded the initializer for the CarModel directly into the initializer for the Car instance.

Object Initializers in depth

In this section, we  will look at the code that is generated by the compiler when we write an object initializer. For example, is we pull out ILDASM (yes, always have that one on your tool belt ;-), and look at the code for the method CreateCarModelWithInitializer(), we see the following:

1 .method private hidebysig static void CreateCarModelWithInitializer() cil managed 2 { 3 // Code size 63 (0x3f) 4 .maxstack 2 5 .locals init ([0] class ObjectInitializers.CarModel carModel, 6 [1] class ObjectInitializers.CarModel '<>g__initLocal0') 7 IL_0000: nop 8 IL_0001: newobj instance void ObjectInitializers.CarModel::.ctor() 9 IL_0006: stloc.1 10 IL_0007: ldloc.1 11 IL_0008: ldstr "Acura" 12 IL_000d: callvirt instance void ObjectInitializers.CarModel::set_Make(string) 13 IL_0012: nop 14 IL_0013: ldloc.1 15 IL_0014: ldstr "TL" 16 IL_0019: callvirt instance void ObjectInitializers.CarModel::set_Model(string) 17 IL_001e: nop 18 IL_001f: ldloc.1 19 IL_0020: ldc.i4 0x7d7 20 IL_0025: callvirt instance void ObjectInitializers.CarModel::set_Year(int32) 21 IL_002a: nop 22 IL_002b: ldloc.1 23 IL_002c: stloc.0 24 IL_002d: ldstr "\nCarModel with Initializer: " 25 IL_0032: ldloc.0 26 IL_0033: call string [mscorlib]System.String::Concat(object, 27 object) 28 IL_0038: call void [mscorlib]System.Console::WriteLine(string) 29 IL_003d: nop 30 IL_003e: ret 31 } // end of method Program::CreateCarModelWithInitializer 32

Now, when we compare this code with the code for the CreateCarModeTraditional():

1 .method private hidebysig static void CreateCarModelTraditional() cil managed 2 { 3 // Code size 61 (0x3d) 4 .maxstack 2 5 .locals init ([0] class ObjectInitializers.CarModel carModel) 6 IL_0000: nop 7 IL_0001: newobj instance void ObjectInitializers.CarModel::.ctor() 8 IL_0006: stloc.0 9 IL_0007: ldloc.0 10 IL_0008: ldstr "Acura" 11 IL_000d: callvirt instance void ObjectInitializers.CarModel::set_Make(string) 12 IL_0012: nop 13 IL_0013: ldloc.0 14 IL_0014: ldstr "TL" 15 IL_0019: callvirt instance void ObjectInitializers.CarModel::set_Model(string) 16 IL_001e: nop 17 IL_001f: ldloc.0 18 IL_0020: ldc.i4 0x7d7 19 IL_0025: callvirt instance void ObjectInitializers.CarModel::set_Year(int32) 20 IL_002a: nop 21 IL_002b: ldstr "Traditional CarModel: " 22 IL_0030: ldloc.0 23 IL_0031: call string [mscorlib]System.String::Concat(object, 24 object) 25 IL_0036: call void [mscorlib]System.Console::WriteLine(string) 26 IL_003b: nop 27 IL_003c: ret 28 } // end of method Program::CreateCarModelTraditional 29

We note the the ONLY difference between the generated IL is the following:

.locals init ([0] class ObjectInitializers.CarModel carModel, [1] class ObjectInitializers.CarModel '<>g__initLocal0')

The second line in this code block is some bookkeeping for the compiler to remember that  Object Initializers were used for this code, but for the rest the code is identical, and the total size of the the general IL is almost the same (61 and 63 bytes), so there is really no overhead in using object initializers.

One case where you need to pay a bit of attention is if you have any object that has a non-default constructor, as show in the example below:

1 public class Engine 2 { 3 public int Horsepower { get; set; } 4 public int CC { get; set; } 5 6 public Engine(int horsePower) 7 { 8 Horsepower = horsePower; 9 } 10 } // class Engine 11

If you would attempt to use an object initializer in this scenario like this, you will run into some problems:

1 private static void objectWithCtorExample() 2 { 3 Engine engine = new Engine { Horsepower = 120, CC = 2600 }; 4 }

Indeed, you will get the following compilation error:

Error 1 'ObjectInitializers.Engine' does not contain a constructor that takes '0' arguments

You have two solutions for this:

  1. Add a default constructor to your class (in all  of our previous examples, this default constructor was automatically written for us by the compiler).
  2. Use a syntax that uses the constructor, and uses an object initializer for the remaining properties, as is show below:

1 private static void objectWithCtorExample() 2 { 3 Engine engine = new Engine(120) { CC = 2600 }; 4 }

SO, the above code is a bit of a "hybrid" (to use a popular expression -) between the use of constructors and object initializers.

Collection Initializers

Overview

Note: the code for this feature is contained in the "CollectionInitializers" console application of the code.

The concept of Object Initializers can be expanded to collections with C# 3.0. Using a new feature, called "Collection Initializers" you an use a compact syntax to initialize a collection.

For example, take the following class:

1 public class Dog 2 { 3 public string Breed { get; set; } 4 public string HairColor { get; set; } 5 public int Weight { get; set; } 6 public bool IsFriendly { get; set; } 7 8 public override string ToString() 9 { 10 return String.Format("\tDog: Breed: {0}, HairColor: {1}, Weight : {2}, IsFriendly: {3}", 11 Breed, HairColor, Weight, IsFriendly); 12 } 13 } // class Dog

in a traditional approach, you could create a collection of dogs as follows:

1 private static void createDogListTraditional() 2 { 3 List<Dog> dogs = new List<Dog>(); 4 dogs.Add(new Dog {Breed = "Great Dane", HairColor = "Fawn", Weight = 165, IsFriendly = true}); 5 dogs.Add(new Dog { Breed = "Mud", HairColor = "DirtyBlack", Weight = 15, IsFriendly = false }); 6 7 } // method createDogListTraditional 8

So, here we do what we always do, we create a collection, and then add new instances to the collection. The approach with a collection initializer results in more compact code, as is shown below:

1 private static void createDogListCollectionInitializer() 2 { 3 List<Dog> dogs = new List<Dog>() { 4 new Dog { Breed = "Great Dane", HairColor = "Fawn", Weight = 165, IsFriendly = true }, 5 new Dog { Breed = "Mud", HairColor = "DirtyBlack", Weight = 15, IsFriendly = false } 6 }; 7 8 } // method createDogListCollectionInitializer 9

So, we no longer need the "dogs.Add", statements, and our code looks more natural and compact.

Collection Initializers in depth

I looked at the generated IL code in ILDASM, and the code sizes for each of the methods was as follows:

  • CreateDogListTraditional(): 128 bytes
  • CreateDogListCollectionInitializer(): 130 bytes

So again, there is no noticeable overhead in using collection initializers in your code, and I would greatly recommend that you do so to improve the readability of your code.

Conclusion

I hope this article provided you with a good overview of automatic properties, object initializers and collection initializers. These 3 features are used in every LINQ query, and are actually also very useful when you are writing regular production code.

In our next post, we will look at C# 3.0 lambda expressions, which go directly to the core of the how LINQ expressions are generated by the compiler.

Technorati Tags: ,
Published Aug 02 2007, 11:05 PM by bennie
Filed under: , ,

Comments

No Comments

Leave a Comment

(required)