Wednesday, 5 November 2008

Immutability and optional parameters

(caveat: based on the October 2008 public VS2010 CTP; everything subject to change)

C# 3.0 made it very easy to initialize objects, in particular via "object initializer" syntax. This allows us to set the properties for an object (and child objects) very simply.

I'll give a simple example - but note that this makes a lot more sense where the class has lots of properties... obviously that isn't condusive to blogging, so you'll have to use your imagination ;-p

public sealed class Person {
public string Forename { get; set; }
public string Surname { get; set; }
// snip: other properties
static void Main() {
Person p = new Person {
Forename = "Fred",
Surname = "Flintstone"
};
}
}

In the Main method, we create and initialize the Person via the object initializer, setting the Forename and Surname properties.

OK; all good - but what about immutability? Unfortunately, it starts to break down.

public sealed class Person {
private readonly string forename, surname;
public string Forename { get {return forename; }}
public string Surname { get {return surname; }}
// snip: other properties

static void Main() {
Person p = new Person {
Forename = "Fred",
Surname = "Flintstone"
};
}
}

Error 1 Property or indexer 'Forename' cannot be assigned to -- it is read only
Error 2 Property or indexer 'Surname' cannot be assigned to -- it is read only

The problem is that object initializers work by invoking the setter, which can't happen if there *is* no setter.
Obviously we could add a constructor that sets both the forename and surname, but keep in mind that we might have 10 properties, and different callers might want to set different permutations of properties. We don't really want to add large numbers of constructors to support each caller's whim.

So what about C# 4.0? This introduces optional and named parameters... as well as making COM a lot more friendly, we can use this on our constructors to provide something close to an object initializer:

public sealed class Person {
// readonly fields/properties
private readonly string forename, surname;
public string Forename { get { return forename; } }
public string Surname { get { return surname; } }
// snip: other properties

// constructor with optional arguments
public Person(
string forename = "", string surname = ""
/* snip: other parameters */) {

this.forename = forename;
this.surname = surname;
// snip: other fields
}
static void Main() {
Person p = new Person(
forename: "Fred",
surname: "Flintstone");
}
}

Here, the Main method creates a new immutable Person, specifying just the properties that they want to assign. The rest are defaulted to whatever the "=foo" says in the constructor declaration.

Simple; easy.

5 comments:

Marc Gravell said...

Now... if only the compiler would write the constructor for us ;-p

Roger Lipscombe said...

If the language added "readonly" to the property syntax, that would be a great improvement:

public class Person {
public string FirstName {
get;
readonly set;
}
}

Marc Gravell said...

Oh, absolutely. But there is no sign of that in the C# 4.0 time-frame.

Ken Kozman said...

This is probably saying something you guys already know but...

You can make an automatically implemented property use a readonly backing store by using public int Foo { get; private set; } (see: http://msdn.microsoft.com/en-us/library/bb308966.aspx#csharp3.0overview_topic22).

Unfortunately this still results in an error when using the initializer ctor form (eg. var x = new Thingie { Foo = 12 };) The deal being that that is just syntactical sugar for creating the object and calling the properties (at which point you can not call set as readonly is invalid).

It might be possible at some point in the future to support this. Unfortunately I think it is more than just a language change at that point as readonly turns into initonly which is a CLR level attribute. Meaning it could require changes to the CLR not just the language (although I guess it depends on how it was done)...

Marc Gravell said...

Indeed - but even if we forgive the fact that it is only immutable on the *public* API, it still (as you say) doesn't make for simple initialization.