Search
Close this search box.

C#/.NET Little Wonders: Indexer Initializer Syntax

Once again, in this series of posts I look at the parts of the .NET Framework that may seem trivial, but can help improve your code by making it easier to write and maintain. The index of all my past little wonders posts can be found here.

Visual Studio 2015 is on the horizon!  In fact, some of you may already have played with the preview and seen some of the many neat new things to come – both in the IDE and in the C# language.

As with many of these posts, I started off thinking this was going to be a quick one.  I mean, a new initializer syntax, how much time can I possibly spend writing on that?  However, as I dug more and researched what I wanted to say.  It really highlighted how much more there is to it than meets the eye. 

Note: All of the C# 6 features mentioned are current with the latest CTP of VS2015 as of the time of this writing.  The syntax and exact feature behaviors may be subject to change in the final released version.

Collection and Object Initializers

Some people look at the new Index Initializer syntax and seem to think the Indexer Initializer is just a new way to use the Collection Initializer so that initializing a dictionary looks cleaner, but that’s not the case.  It’s really a brand new feature of Object Initialization and not directly related to collections at all.

However, for me to describe this best, I feel I need to take a quick moment to illustrate how object and collection initializers currently work to give the full context.  So, bear with me while I dive into Object and Collection Initializers from the beginning to get the back-story.

Basically, initializers allow you to simplify the job of initializing an object or collection by reducing the boiler plate code you usually see of creating an object and then setting various properties or adding initial members to a collection.

For example, let’s consider the following class:

1 : public class Point
2 : {
  3 : public int X { get; set; }
  4 : public int Y { get; set; }
  5:
}

Now, if we wanted to create a new instance of Point before initializers, we would have had to do something like this:

1: var point = new Point();
   2: point.X = 13;
   3: point.Y = 42;

Of course, we could create a constructor that takes an X and Y, and that would work for a class like Point where both properties are (generally) required.  However what happens when you start having a more complex object with multiple properties, some defaultable and others not?  Do you make a dozen different constructors to handle all of the cases and default the others?

Fortunately, with object initializer syntax, you can set any property or field that has is visible by specifying its assignment in a list after the construction:

 1: // object initialization syntax.  
   2: var point = new Point { X = 13, Y = 42 };

Note that this is not a constructor syntax.  This is literally the same as our code example above it, it’s just syntactical sugar to allow us to write the same thing more concisely.  It simply is a short-hand way for writing the individual sets of properties and fields on the object right after construction.

Collection initializers do something similar, but with collections.  They allow you to specify items to add to the collection at the time you construct it.  For example, say we want to create a list with the first 8 powers of two before collection initializer syntax, we’d have to write:

1 :  // That's a lot of redundant typing
     2 : var powersOfTwo = new List<int>();
3 : powersOfTwo.Add(1);
4 : powersOfTwo.Add(2);
5 : powersOfTwo.Add(4);
6 : powersOfTwo.Add(8);
7 : powersOfTwo.Add(16);
8 : powersOfTwo.Add(32);
9 : powersOfTwo.Add(64);
10 : powersOfTwo.Add(128);

But with collection initializer syntax, this can be simplified to:

 1: // much less typing involved
   2: var powersOfTwo = new List<int> { 1, 2, 4, 8, 16, 32, 64, 128 };

Again, this is just syntactical sugar.  It is literally calling the constructor then calling the Add() method implicitly using each item in the list, just like the code above it. 

Now, while you can set any visible property or field with the Object Initializer syntax, the Collection Initializer syntax requires two things to be true with any class with which you wish use to utilize it:

  1. Your class must implement IEnumerable
  2. Your class must have an Add() method

Oddly enough, the IEnumerable interface is not utilized at all in the collection initializer syntax, it is simply used as a “verification” (of sorts) that the object is a collection.  The rationale being that many different classes can have an Add() function (such as Fraction perhaps), but that doesn’t make them a collection.  Thus, the designers of .NET felt that making the class implement IEnumerable would be a reasonable way to ensure that the type was truly a collection.

Also, note that the Add() method can take any number or type of parameters (which is why it’s not specified by an interface).  To call a version of Add() that takes more than one argument, you surround the argument lists with curly braces. 

For example, if we wanted to initialize a Dictionary<string, string> with zip codes mapped to city names:

 1: var placesByZip = new Dictionary<string, string>
   2:     {
   3:         { "63368", "Dardenne Prairie" },
   4:         { "63141", "Des Peres" },
   5:         { "63101", "St. Louis" }
   6:     };

Again, this is literally the same as creating the dictionary and calling Add(…) explicitly, like this:

1: var placesByZip = new Dictionary<string, string>();
   2: placesByZip.Add("63368", "Dardenne Prairie");
   3: placesByZip.Add("63141", "Des Peres");
   4: placesByZip.Add("63101", "St. Louis");

As you can see, the initializer syntaxes remove the need for a lot of boilerplate code in setting up an object or a collection.

But, you already knew this, right?  So why bring it up again?

It’s important to realize not just the syntax of the initializers, but what they do behind the scene and (in the case of the collection initializer) what you need to create for your class to support that syntax.

Indexer Initializers

Indexer initializers allow you to perform initialization activities on an object in a new way: by using its indexer (if it has one).  For example, consider our dictionary of zips to city names above.  Because a Dictionary also has an indexer, we could rewrite that initializer as follows:

1: var placesByZip = new Dictionary<string, string>
   2:     {
   3:         ["63368"] = "Dardenne Prairie",
   4:         ["63141"] = "Des Peres",
   5:         ["63101"] = "St. Louis"
   6:     };

This uses the indexer syntax to allow us to specify a value for each of the keys.  In many ways, it looks more elegant, as the indexer syntax clearly calls out what part is the key, and what the value being assigned is.

“So, big deal”, you say, “it’s just a more clear version of the collection initializer, right?” 

No, it’s actually a brand-new feature of the Object Initializer syntax, and that’s an important distinction. 

First of all, this syntax does not call the Add() method as the Collection Initializer does, it instead calls the object’s indexer.  That is, it is actually compiling into something like:

 1: var placesByZip = new Dictionary<string, string>();
   2: placesByZip["63368"] = "Dardenne Prairie";
   3: placesByZip["63141"] = "Des Peres";
   4: placesByZip["63101"] = "St. Louis";

Now, in Dictionary the net result is the same, but keep in mind this isn’t necessarily true for all classes. 

The nice part is that indexers are not limited to collections, so you do not need to implement IEnumerable or have an Add() method, all you need is a visible set indexer.  This is nice because if you want to create a collection that had an indexer before and use collection initialization, you’d have to also have an Add() method and implement IEnumerable whether it made sense or not.  Now, however, if our class has an indexer, that’s all we need to use the Indexer Initializer syntax.

For example, what if we wanted to create a class called BitFlipinator that allowed us to flip specific bits.  For example, I could say I want to flip the 1st, 2nd, and 4th bits (from least significant side) to get the value of 11 (1 + 2 + 8).

If I wanted to build this before with support for the collection initializer, I would have to do something like  this:

 1: public class BitFlipinator : IEnumerable
   2: {
   3:     public int Value { get; private set; }
   4:  
   5:     // Add doesn't make sense in this context, but needed for
   6:     // collection initialization syntax
   7:     public void Add(int bit, int value)
   8:     {
   9:         Set(bit, value);
  10:     }
  11:  
  12:     // doesn't *need* to do anything, just needs to be here to use collection initialization
  13:     public IEnumerator GetEnumerator()
  14:     {
  15:         throw new NotImplementedException();
  16:     }
  17:  
  18:     // our method to set a bit to a 1 or 0
  19:     public void Set(int bit, int value)
  20:     {
  21:         if (value < 0 || value > 1) throw new ArgumentOutOfRangeException();
  22:         if (bit < 1 || bit > 32) throw new ArgumentOutOfRangeException();
  23:     
  24:         var filterBit = 0x01 << (bit - 1);
  25:         Value = (value == 1) ? Value | filterBit
  26:             : Value & ~filterBit;
  27:      }
  28: }

In order to be able to do this:

1: var bitinator = new BitFlipinator
   2:     {
   3:         { 1, 1 },   // set the 1s bit
   4:         { 2, 1 },   // set the 2s bit
   5:         { 4, 1 }    // set the 8s bit
   6:     };

That’s a lot of boilerplate that doesn’t make sense.  And yes, you could argue whether a collection initializer really makes sense in this example, but the main point was that previously, we only had two choices for initializers: object and collection.

However, consider how we could now write this with an indexer and index initializer syntax:

   1: public class BitFlipinator 
   2: {
   3:     public int Value { get; set; }
   4:  
   5:     public int this[int bit]
   6:     {
   7:         set { Set(bit, value); }
   8:     }
   9:     
  10:     // our method to set a bit to a 1 or 0
  11:     public void Set(int bit, int value)
  12:     {
  13:         if (value < 0 || value > 1) throw new ArgumentOutOfRangeException();
  14:         if (bit < 1 || bit > 32) throw new ArgumentOutOfRangeException();
  15:  
  16:         var filterBit = 0x01 << (bit - 1);
  17:         Value = (value == 1) ? Value | filterBit
  18:             : Value & ~filterBit;
  19:     }
  20: }

Which allows us to write this:

 1: var bitinator = new BitFlipinator
   2:     {
   3:         [1] = 1,   // set the 1s bit
   4:         [2] = 1,   // set the 2s bit
   5:         [4] = 1    // set the 8s bit
   6:     };

Which is much more clear.  Notice that we no longer had to implement an IEnumerable, nor did we have to have an Add method which didn’t really make logical sense to begin with.  All we needed was a visible, settable indexer and we can use the new syntax.

Again, I wish to reiterate that the Indexer Initializer is not a type of Collection Initializer, it’s a type of Object Initializer.  Why do I keep saying this?  Why is this distinction important?  Because you cannot mix object initializers and collection initializers in the same initialization list. 

So, we can’t initialize a collection and set a property in the same initialization list, but we can set a property, field, and indexer in the same initialization list since they are all valid Object Initializers.

For example, my BitFlipinator has a property Value and an indexer.  I could take advantage of this to seed the value with a lot of 1s, and then clear our a zero bit.  For example, what if I want the number 255 but with the 4th bit (the 8s position) turned off, I could write:

 1: // sets bits 1 thru 8, then clears bit 4
   2: var bitinator = new BitFlipinator
   3:     {
   4:         Value = 255,
   5:         [4] = 0
   6:     };

So we can mix indexer, field, and property initializers in an object initializer list, but we cannot mix indexer initializers with collection initializers:

  1: // syntax error, one is collection initializer, one is
   2: // an object initializer setting an indexer
   3: var placesByZip = new Dictionary<string, string>
   4:     { "63368", "Dardenne Prairie" },
   5:     ["63141"] = "Des Peres"
   6: };

Pitfall: Know What Your Indexer Does

Now that you know you can use Indexer Initializers, the question is whether that is always logical.  The answer is it really depends on your type and what its indexer allows.  For example, consider the following code:

 1: // I'd like to only set elements 0, 2, and 5
   2: var spottyList = new List<int>
   3:     {
   4:         [0] = 13,
   5:         [2] = 42,
   6:         [5] = 100
   7:     };

This compiles — it’s perfectly legal syntax.  The problem is it will blow up at runtime because List<T> does not allow you to index beyond the current size.  Since the constructed list has a size of zero, we can’t even set the 0th element.

Again, this is not a collection initializer syntax, it’s an object initializer that happens to be calling the indexer, like this:

  1: var spottyList = new List<int>();
   2: // blows up
   3: spottyList[0] = 13;
   4: spottyList[2] = 42;
   5: spottyList[5] = 100;

This is a place where the collection initializer makes more sense because it calls Add() which allows the list to grow, instead of the indexer which expects the list to be large enough for the index to be valid.

So remember, you should always know what that indexer’s expected behavior is before utilizing it (though this is true with using the indexers directly as well).

Summary

Okay, so we’ve seen there’s a new object initializer syntax to allow you to set indexers in an initialization list.  This gives us a lot of power to be able to initialize indexed types in a way that is very expressive and easy to read.  As a consequence, it can clarify the intension when initializing things like dictionaries.  To use an indexer initializer, your class need only have a visible, settable indexer.

Thanks for reading and stay tuned for more features of C# 6!

This article is part of the GWB Archives. Original Author: James Michael Hare

Related Posts