Object Initializers

Until C# 3.0, the only real solution to this was to write one or more factory methods. These are described in the sidebar below. But now we have another option.

With C# 3.0 the language was extended to support object initializers—an extension to the new syntax that lets us set up a load of properties, by name, as we create our object instance.

Example 3-35 shows how an object initializer looks when we use it in our Main function.

Example 3-35. Using object initializers

static void Main(string[] args)
{
    Plane someBoeing777 = new Plane("BA0049")
                          {
                              Direction = DirectionOfApproach.Approaching,
                              SpeedInMilesPerHour = 150
                          };

    Console.WriteLine(
        "Your plane has identifier {0}," +
        " and is traveling at {1:0.00}mph [{2:0.00}kph]",
        // Use the property getter
        someBoeing777.Identifier,
        someBoeing777.SpeedInMilesPerHour,
        someBoeing777.SpeedInKilometersPerHour);

        someBoeing777.SpeedInKilometersPerHour = 140.0;

    Console.WriteLine(
        "Your plane has identifier {0}," +
        " and is traveling at {1:0.00}mph [{2:0.00}kph]",
        // Use the property getter
        someBoeing777.Identifier,
        someBoeing777.SpeedInMilesPerHour,
        someBoeing777.SpeedInKilometersPerHour);

    Console.ReadKey();

}

Note

Object initializers are mostly just a convenient syntax for constructing a new object and then setting some properties. Consequently, this only works with writable properties—you can’t use it for immutable types,[11] so this wouldn’t work with our PolarPoint3D.

We still use the constructor parameter for the read-only Identifier property; but then we add an extra section in braces, between the closing parenthesis and the semicolon, in which we have a list of property assignments, separated by commas. What’s particularly interesting is that the purpose of the constructor parameter is normally identifiable only by the value we happen to assign to it, but the object initializer is “self-documenting”—we can easily see what is being initialized to which values, at a glance.

The job isn’t quite done yet, though. While there’s nothing technically wrong with using both the constructor parameter and the object initializer, it does look a little bit clumsy. It might be easier for our clients if we allow them to use a default, parameterless constructor, and then initialize all the members using this new syntax. As we’ll see in Chapter 6, we have other ways of enforcing invariants in the object state, and dealing with incorrect usages. Object initializers are certainly a more expressive syntax, and on the basis that self-documenting and transparent is better, we’re going to change how Plane works so that we can initialize the whole object with an object initializer.

Note

As with any design consideration, there is a counter argument. Some classes may be downright difficult to put into a “default” (zero-ish) state that isn’t actively dangerous. We’re also increasing the size of the public API by the changes we’re making—we’re adding a public setter. Here, we’ve decided that the benefits outweigh the disadvantages in this particular case (although it’s really a judgment call; no doubt some developers would disagree).

First, as Example 3-36 shows, we’ll delete the special constructor from Plane, and then make Identifier an ordinary read/write property. We can also remove the _identifier backing field we added earlier, because we’ve gone back to using an auto property.

Example 3-36. Modifying Plane to work better with object initializers

class Plane
{
    // Remove the constructor that we no longer require
    // public Plane(string newIdentifier)
    // {
    //    Identifier = newIdentifier;
    // }

    public string Identifier
    {
        get;
        // remove the access modifier
        // to make it public
         set;
    }

    // ...
}

We can now use the object initializer syntax for all the properties we want to set. As Example 3-37 shows, this makes our code look somewhat neater—we only need one style of code to initialize the object.

Example 3-37. Nothing but object initializer syntax

Plane someBoeing777 = new Plane
                          {
                              Identifier = "BA0049",
                              Direction = DirectionOfApproach.Approaching,
                              SpeedInMilesPerHour = 150
                          };

Object initializer syntax provides one big advantage over offering lots of specialized constructors: people using your class can provide any combination of properties they want. They might decide to set the Position property inline in this object initializer too, as Example 3-38 does—if we’d been relying on constructors, default or named arguments wouldn’t have helped if there was no constructor available that accepted a Position. We’ve not had to provide an additional constructor overload to make this possible—developers using our class have a great deal of flexibility. Of course, this approach only makes sense if our type is able to work sensibly with default values for the properties in question. If you absolutely need certain values to be provided on initialization, you’re better off with constructors.

Example 3-38. Providing an extra property

Plane someBoeing777 = new Plane
                          {
                              Identifier = "BA0049",
                              Direction = DirectionOfApproach.Approaching,
                              SpeedInMilesPerHour = 150,
                              Position = new PolarPoint3D(20, 180, 14500)
                          };

So, we’ve addressed the data part of our Plane; but the whole point of a class is that it can encapsulate both state and operations. What methods are we going to define in our class?



[11] This is a slight oversimplification. In Chapter 8, we’ll encounter anonymous types, which are always immutable, and yet we can use object initializers with those. In fact, we are required to. But anonymous types are a special case.