Fun with Object and Collection Initializers

Gosh it feels like a long time since I’ve blogged – particularly since I’ve blogged anything really C#-language-related.

At some point I want to blog about my two CodeMash 2013 sessions (making the C# compiler/team cry, and learning lessons about API design from the Spice Girls) but those will take significant time – so here’s a quick post about object and collection initializers instead. Two interesting little oddities…

Is it an object initializer? Is it a collection initializer? No, it’s a syntax error!

The first part came out of a real life situation – FakeDateTimeZoneSource, if you want to look at the complete context.

Basically, I have a class designed to help test time zone-sensitive code. As ever, I like to create immutable objects, so I have a builder class. That builder class has various properties which we’d like to be able to set, and we’d also like to be able to provide it with the time zones it supports, as simply as possible. For the zones-only use case (where the other properties can just be defaulted) I want to support code like this:

var source = new FakeDateTimeZoneSource.Builder
{
    CreateZone("x"),
    CreateZone("y"),
    CreateZone("a"),
    CreateZone("b")
}.Build();

(CreateZone is just a method to create an arbitrary time zone with the given name.)

To achieve this, I made the Builder implement IEnumerable<DateTimeZone>, and created an Add method. (In this case the IEnumerable<> implementation actually works; in another case I’ve used explicit interface implementation and made the GetEnumerator() method throw NotSupportedException, as it’s really not meant to be called in either case.)

So far, so good. The collection initializer worked perfectly as normal. But what about when we want to set some other properties? Without any time zones, that’s fine:

var source = new FakeDateTimeZoneSource.Builder
{
    VersionId = "foo"
}.Build();

But how could we set VersionId and add some zones? This doesn’t work:

var invalid = new FakeDateTimeZoneSource.Builder
{
    VersionId = "foo",
    CreateZone("x"),
    CreateZone("y")
}.Build();

That’s neither a valid object initializer (the second part doesn’t specify a field or property) nor a valid collection initializer (the first part does set a property).

In the end, I had to expose an IList<DateTimeZone> property:

var valid = new FakeDateTimeZoneSource.Builder
{
    VersionId = "foo",
    Zones = { CreateZone("x"), CreateZone("y") }
}.Build();

An alternative would have been to expose a propert of type Builder which just returned itself – the same code would have been valid, but it would have been distinctly odd, and allowed some really spurious code.

I’m happy with the result in terms of the flexibility for clients – but the class design feels a bit messy, and I wouldn’t have wanted to expose this for the "production" assembly of Noda Time.

Describing all of this to a colleague gave rise to the following rather sillier observation…

Is it an object initializer? Is it a collection initializer? (Parenthetically speaking…)

In a lot of C# code, an assignment expression is just a normal expression. That means there’s potentially room for ambiguity, in exactly the same kind of situation as above – when sometimes we want a collection initializer, and sometimes we want an object initializer. Consider this sample class:

using System;
using System.Collections;

class Weird : IEnumerable
{
    public string Foo { get; set; }
    
    private int count;
    public int Count { get { return count; } }
        
    public void Add(string x)
    {
        count++;
    }
            
    IEnumerator IEnumerable.GetEnumerator()
    {
        throw new NotSupportedException();
    }    
}

As you can see, it doesn’t actually remember anything passed to the Add method, but it does remember how many times we’ve called it.

Now let’s try using Weird in two ways which only differ in terms of parentheses. First up, no parentheses:

string Foo = "x";
Weird weird = new Weird { Foo = "y" };
    
Console.WriteLine(Foo);         // x
Console.WriteLine(weird.Foo);   // y
Console.WriteLine(weird.Count); // 0

Okay, so it’s odd having a local variable called Foo, but we’re basically fine. This is an object initializer, and it’s setting the Foo property within the new Weird instance. Now let’s add a pair of parentheses:

string Foo = "x";
Weird weird = new Weird { (Foo = "y") };
    
Console.WriteLine(Foo);         // y
Console.WriteLine(weird.Foo);   // Nothing (null)
Console.WriteLine(weird.Count); // 1

Just adding those parenthese turn the object initializer into a collection initializer, whose sole item is the result of the assignment operator – which is the value which has now been assigned to Foo.

Needless to say, I don’t recommend using this approach in real code…

10 thoughts on “Fun with Object and Collection Initializers”

  1. Seems like one possible solution would be something like this:

    var invalid = new FakeDateTimeZoneSource.Builder
    (
    versionId: “foo”
    )
    {
    CreateZone(“x”),
    CreateZone(“y”)
    }.Build();

    Where you could just support the properties as optional parameters to the constructor.

  2. @Ben: Yes, that would have been one option, I guess. I’m trying to stay away from C# 4 features for the moment, although admittedly using optional parameters wouldn’t prevent anyone from using it with .NET 3.5… it would just make it a pain for them to set the properties.

  3. One way of abusing initializers is this:

    FakeDateTimeZoneSource.Builder has method Add(DateTimeZone), and also method Add(ISetting). Then, you implement string-holder objects such as class VersionId : ISetting { public readonly string value; public VersionId(string value); }, and you can call it as such:

    var odd = new FakeDateTimeZoneSource.Builder
    {
    VersionId(“foo”),
    CreateZone(“x”),
    CreateZone(“y”)
    }.Build();

    It doesn’t have to have an ISetting interface – it can be a specific overload for every setting if that’s easier to write (e.g. if there’s only one or two possible settings other than the zone list).

  4. @Vidhyut: No – Java doesn’t have object initializers, collection initializers or properties, so none of this applies.

  5. Interesting. I thought that you were saying weird.Foo was somehow being set to null in your last example (the ‘result’ of assigning a value to the local variable). But when I gave Weird.Foo a default value of “a” it remains as “a”, which is what you were saying all along!

  6. That first example of “incidental” Property initialization by use of the assignment operator, while creating an instance of an IEnumerable was surprising to me:

    Weird weird = new Weird { Foo = “y” };

    It wasn’t surprising I couldn’t access the internal collection of ‘weird, since there’s no “real” GetEnumerator implemented.

    So, with the second call you have created a collection with 1 member, but you can’t access it ! I am not sure what I am supposed to learn from that :)

    What would seem to make sense to me would be that anytime I create a Class meant to be an Enumerator, that maintained some Public state Properties, which might be initialized as instances of the Class are created, that I would put a constructor in the Class to handle initialization of those Properties.

    public Weird(string foo){Foo = foo;}

    Then I can easily create a new instance in which the initialization of state variables, and the creation of internal Enumerable elements, is semantically distinct:

    Weird weird = new Weird(foo:”some string”){“1″,”2″};

    Or, create an instance with state variable set, but internal collection empty:

    Weird weird = new Weird(foo:”some string”);

    That does “lock me into” having to provide an argument(s) to create an instance of ‘Weird, unless I throw in an optional parameterless calling form:

    public Weird(){}

    thanks, Bill

Comments are closed.