C# 3.0 Object Initializers

  • 7 minutes to read
  • edit

A few days ago, I wrote about automatic properties in the upcoming release of C#. While this is a cool feature, it does have some practical limitations once you need to go beyond simple get/set logic. Another new feature coming up is the idea of object initializers. Object initializersare an important aspect of the Linq extensions to .NET, but they aren’t limited to being used only in Linq expressions.

If you are familiar with attribute programming, you have probably used something similar. In most cases, when you declare the attribute you only provide the parameters required by the constructor:

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")]

These parameters are called positional parameters and are always defined in the public constructor for the attribute class. However, each non-static public read-write property (or field) for an attribute class defines a named parameter for the attribute class. These named parameters are available through an implied overload on the constructor which allows you to specify the values for the named parameters. (For more details see section 17.1.2 Positional and named parameters of the C# Language Specification.

To see this in action, look at the following attribute declaration:

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification="Renaming this class to end in 'Collection' would change the implied meaning.")]

This sets the same two positional parameters as before, but also sets the Justification property using a named parameter.

Now, how does this relate to object initializers? Object initializersuse a very similar syntax as named parameters and are accessible through an implied overload on the constructor of a class. If we use the same UserProfile class (from my automatic properties example),

public class UserProfile
{
    public int UserId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    
    public UserProfile() { }
}

I can create a new instance of that class and provide data to it in the “traditional” way:

UserProfile profile = new UserProfile();
profile.UserId = 123;
profile.FirstName = "Scott";
profile.LastName = "Dorman";

Remember, object initializers work with any public read-write property in you class so I can create the same instance using object initializers this way:

UserProfile profile = new UserProfile 
{
    UserId = 123,
    FirstName = "Scott",
    LastName = "Dorman"
};

At this point, you might say this is the same amount of work. What benefit does this provide?

The benefit provided is that you no longer need to provide multiple overloaded constructors for your class with different combinations of parameters. You can declare one constructor (or a few overloads) that provide the “common use” pattern and then rely on named parameters for the other combinations. The “common use” pattern is the constructor (or constructors) that represent the way your class will be instantiated 90% of the time.

You also still get the intellisense features of the IDE, which displays a list of the named parameters for you. (This is the same behavior you get for named parameters on attributes as well.) You are free to choose which parameters to use and don’t have to use any.

These benefits really become apparent when you have more complex objects or object hierarchies. Consider if we add a new class, Address, and add it as another property to UserProfile.

public class Address
{
    public string Line1 { get; set; }
    public string Line2 { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public int Zip { get; set; }
}

public class UserProfile
{
    public int UserId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public Address HomeAddress { get; set; }

    pubic UserProfile() { }
}

We can easily create a new instance using object initializers like this:

UserProfile profile = new UserProfile
{
     UserId = 123,
     FirstName = "Scott",
     LastName = "Dorman",
     HomeAddress = new HomeAddress {City = "Tampa", State = "FL"}
};

This is much simpler than creating the Address instance, assigning the properties and then creating the UserProfile instance, making sure to pass the Address instance you just created to the HomeAddress property.

Comments