(I’m giving up with the numbering now, unless anyone particularly wants me to keep it up. What was originally going to be a limited series appears to be growing without end…)
As Chris Nahr pointed out in my previous post, my earlier idea about staged initialization was very half-baked. As he’s prompted me to think further about it, I’ve come up with another idea. It’s slightly more baked, although there are lots of different possibilities and open questions.
Let’s take a step back, and look at my motivation: I like immutable types. They’re handy when it comes to thread safety, and they make it a lot easier to reason about the world when you know that nothing can change a certain value after it’s been created. Now, the issues are:
- We really want to be able to fully construct the object in the constructor. That means we can mark all fields as
initonlyin the generated IL, potentially giving the CLR more scope for optimisation. - When setting more than two or three values (while allowing some to be optional) constructor overloading ends up being a pain.
- Object initializers in C# 3 only apply to properties and fields, not method/constructor arguments – so we can’t get the clarity of naming.
- Ideally we want to support validation (or possibly other code) and automatic properties.
- The CLR won’t allow
initonlyfields being set anywhere other than in the constructor – so even if we made sure we didn’t call any setters other than in the constructor, we still couldn’t use them to set the fields. - We want to allow simple construction of immutable types from code other than C#. In particular, I care about being able to use projects like Spring.NET and Castle/Windsor (potentially after changes to those projects) to easily create instances of immutable types without resorting to looking up the order of constructor parameters.
The core of the proposal is to be able to mark properties as initonly, and get the compiler to create an extra type which is thoroughly mutable, and contains those properties – as well as a constructor which accepts an instance of the extra type and uses it to populate the immutable instance of the main type before returning.
Extra syntax could then be used to call this constructor – or indeed, given that the properties are actually readonly, thus avoiding any ambiguity, normal object initializers could be used to create instances.
Just as an example, imagine this code:
{
public string Line1 { get; initonly set; }
public string Line2 { get; initonly set; }
public string Line3 { get; initonly set; }
public string County { get; initonly set; }
public string State { get; initonly set; }
public string Country { get; initonly set; }
public string ZipCode { get; initonly set; }
// Business methods as normal
}
// In another class
Address addr = new Address
{
Line1=“10 Fairview Avenue”,
Line3=“Makebelieve Town”,
County=“Mono County”,
State=“California”,
Country=“US”
};
That could be transformed into code a bit like this:
// Let tools (e.g. the compiler!) know how we
// expect to be initialized. Could be specified
// manually to avoid using the default class name
[InitializedWith(typeof(Address.Init))]
public class Address
{
// Nested mutable class used for initialization
[CompilerGenerated]
public class Init
{
public string Line1 { get; set; }
public string Line2 { get; set; }
public string Line3 { get; set; }
public string County { get; set; }
public string State { get; set; }
public string Country { get; set; }
public string ZipCode { get; initonly set; }
}
// Read-only “real” properties, automatically
// implemented and backed with initonly fields
public string Line1 { get; }
public string Line2 { get; }
public string Line3 { get; }
public string County { get; }
public string State { get; }
public string Country { get; }
public string ZipCode { get; }
// Automatically generated constructor, using
// backing fields directly
public Address(Address.Init init)
{
<>_line1 = init.Line1;
<>_line2 = init.Line2;
<>_line3 = init.Line3;
<>_county = init.County;
<>_state = init.State;
<>_country = init.Country;
<>_zipCode = init.ZipCode;
}
// Business methods as normal
}
// In another class
Address addr = new Address(new Address.Init
{
Line1=“10 Fairview Avenue”,
Line3=“Makebelieve Town”,
County=“Mono County”,
State=“California”,
Country=“US”
});
That’s the simple case, of course. Issues:
- Unlike other compiler-generated types (anonymous types, types for iterator blocks, types for anonymous functions) we do want this to be public, and have a name which can be used elsewhere. We need to find some way of making sure it doesn’t clash with other names. In the example above, I’ve used an attribute to indicate which type is used for initialization – I could imagine some way of doing this in the “pre-transform” code to say what the auto-generated type should be called.
- What happens if you put code in the setter, instead of making it automatically implemented? I suspect that code should be moved into the setter of the initialization class – but at that point it won’t have access to the rest of the state of the class (beyond the other properties in the initialization class). It’s somewhat messy.
- What if you want to add code to the generated constructor? (Possibly solution: allow constructors to be marked somehow in a way that means “add on the initialization class as a parameter at the end, and copy all the values as a first step.)
- How can you indicate that some parameters are mandatory, and some are optional? (The mandatory parameters could just be marked as
readonlyproperties rather thaninitonly, and then the initialization class specified as an extra parameter for a constructor which takes all the mandatory ones. Doesn’t feel elegant though, and leaves you with two different types of initialization code being mixed in the client – some named, some positional.) - How do you specify default values? (They probably end up being the default values of the automatically generated properties of the initialization class, but there needs to be some syntax to specify them.)
I suspect there are more issues too – but I think the benefits would be great. I know the C# team has been thinking about immutability, but I’ve no idea what kind of support they’re currently envisioning. Unlike my previous ideas, which were indeed unpalatable for various reasons, I think this one has real potential. Mind you, given that I’ve come up with it after only mulling this over in “spare” time, I highly doubt that it will be a new concept to the team…