In this post I want to present a nice C# syntax sugar: object and collection initializers.
Object initializers
We will start with object initializers. Let’s assume we have classes:
public class Position {
public int Top { get; set; }
public int Left { get; set; }
}
public class Button {
public Position Position { get; private set; }
public string Text { get; set; }
public int FontSize { get; set; }
public Button() {
Position = new Position();
FontSize = 10;
}
}
Without object initializer we could set Button
object
properties using the following code:
var okButton = new Button();
okButton.Position.Top = 100;
okButton.Position.Left = 200;
okButton.Text = "OK";
okButton.FontSize = 13;
With object initializers we can shorten this code a bit:
var okButton = new Button {
Position = {
Top = 100,
Left = 200
},
Text = "OK",
FontSize = 13
};
Instructions generated by compiler in this two cases are exactly the same, but code using object initializer is more clear and more readable.
Now let’s dive into some syntax details:
I. We can pass any parameters to object
constructor using syntax:
var foo = new Foo(param1, param2) {
Property1 = "some value",
Property2 = "some other value",
// ...
}
II. When we call parameterless constructor we can omit parentheses
(like I did in the Button
example):
var foo = new Foo() {
// ...
};
// this is exactly the same as new Foo()
var foo2 = new Foo {
// ...
}
III. Object initializers can be nested:
var invoice = new Invoice {
ShippingAddress = new Address {
Street = "Long Street",
Number = "3A"
},
InvoiceAddress = new Address {
Street = "Short Street",
Number = "4B"
}
};
IV. We can set nested properties without creating new objects
Do you remember our first example with Button
? We use
var okButton = new Button {
Position = {
Top = 100,
Left = 200
},
// ...
};
to set properties of Button
Position
object. Notice that Position
property has private setter
public Position Position { get; private set; }
so we cannot use new
to create new Position
object
var okButton = new Button {
// v doesn't compile
Position = new Position {
Top = 100,
Left = 200
},
// ...
};
Fortunately object initializer syntax is flexible enough to allow us set properties without creating new objects.
V. Object initializer syntax works only with new
In other words following code doesn’t compile:
// doesn't compile
var foo = SomeMethodReturningFoo() {
Property1 = 1
};
Collection initializers
Now it’s time to present collection initializers. We will start with
List<T>
initializers, then we will describe Dictionary<TKey, TValue>
initializers.
As we all know C# allows to create and initialize arrays in single expression:
var funnyNames = new string[] { "foo", "bar", "baz", "yay" };
Wouldn’t it be nice if this syntax worked with List<T>
’s? Actually thanks
to collection initializers it works and not only with List
’s but also with
custom classes!
Let’s see an example:
List<string> funnyNames = new List<string> { "foo", "bar", "baz" };
this code is translated by compiler to
List<string> tmp = new List<string>();
tmp.Add("foo");
tmp.Add("bar");
tmp.Add("baz");
List<string> funnyNames = tmp;
Inside initializers we are not limited to constants, we can use complex expressions like
List<object> things = new List<object>() {
(1+3*7),
true.ToString(),
new object()
};
As we already seen compiler translates collection initializers to a couple
of Add
calls. We can make collection initializers work with custom classes by
providing Add
method and implementing IEnumerable
or IEnumerable<T>
. This
second requirement is very important - since we are using collection initializers,
compiler expects that we will initialize some kind of collection and collections
ought to implement IEnumerable
interface.
Below we present minimal generic and non-generic class that works with collections initializers:
public class CustomCollection : IEnumerable {
public void Add(object item) {
Console.WriteLine("Add({0})", item);
}
IEnumerator IEnumerable.GetEnumerator() {
yield break;
}
}
public class CustomCollection<T> : IEnumerable<T> {
public void Add(T item) {
Console.WriteLine("Add<T>({0})", item);
}
IEnumerator<T> IEnumerable<T>.GetEnumerator() {
yield break;
}
IEnumerator IEnumerable.GetEnumerator() {
yield break;
}
}
NOTE: We used yield break
to quickly provide dummy IEnumerable
implementation.
Now we can test our custom collections:
public class Program
{
public static void Main(string[] args)
{
var nonGeneric = new CustomCollection { 1, 2, 3 };
var generic = new CustomCollection<int> { 1, 2, 3 };
}
}
When ran this program will write:
Add(1)
Add(2)
Add(3)
Add<T>(1)
Add<T>(2)
Add<T>(3)
Now since collections initializers are translated to a Add
method calls, what will happen
when we write Add
method that take more than one parameter?
public class CustomCollection2 : IEnumerable {
// notice *two* parameters
public void Add(object item, object item2) {
Console.WriteLine("Add({0}, {1})", item, item2);
}
IEnumerator IEnumerable.GetEnumerator() {
yield break;
}
}
It turns out that we still can use collection initializers, but the syntax is a bit different
var coll2 = new CustomCollection2 {
{ 1, 1 },
{ 2, 2 },
{ 3, 3 }
};
This is translated by compiler to
CustomCollection2 tmp = new CustomCollection2();
tmp.Add(1, 1);
tmp.Add(2, 2);
tmp.Add(3, 3);
CustomCollection2 coll2 = tmp;
With this knowledge it should be now easy to understand how Dictionary<TKey, TValue>
initializers work. First Dictionary<TKey, TValue>
defines Add(TKey, TValue)
method,
second it implements IEnumerable<KeyValuePair<TKey, TValue>>
interface - so it fulfills all
requirements needed by collection initializers to work.
We can initialize Dictionary
with the following code
var dict = new Dictionary<string, string> {
{ "1", "one" },
{ "2", "two" },
{ "3", "three" }
};
Which as we already know is translated into
Dictionary<string, string> tmp = new Dictionary<string, string>();
tmp.Add("1", "one");
tmp.Add("2", "two");
tmp.Add("3", "three");
var dict = tmp;
C# 6 introduced more flexible syntax for Dictionary
initializers
var dict = new Dictionary<string, string> {
["1"] = "one",
["2"] = "two",
["3"] = "three"
};
This is not translated to Add
calls, but uses indexer instead
Dictionary<string, string> tmp = new Dictionary<string, string>();
tmp["1"] = "one";
tmp["2"] = "two";
tmp["3"] = "three";
var dict = tmp;
As other collection initializers it can work with custom types, only requirement is that type must have indexer
public class CustomCollection {
public object this[string key] {
get { return null; }
set { /* do something with value */ }
}
}
// usage:
var coll = new CustomCollection {
["foo"] = 1,
["bar"] = 2
};
Another feature is that we can set properties of already existing objects in collection, for example let’s assume that constructor of our collection already added some objects into collection:
public class Example : Dictionary<string, Position> {
public Example() {
Add("topLeft", new Position());
Add("bottomRight", new Position());
}
}
Then we can write
var example = new Example {
["topLeft"] = { Top = 10, Left = 20 },
["bottomRight"] = { Top = 100, Left = 220 }
};
This will be translated by compiler into
Example tmp = new Example();
tmp["topLeft"].Top = 10;
tmp["topLeft"].Left = 20;
tmp["bottomRight"].Top = 100;
tmp["bottomRight"].Left = 220;
Example example = tmp;
The last thing worth know about this new initializer syntax is ability to mix it with property initializers e.g.
var example = new Example {
["topLeft"] = { Top = 10, Left = 20 },
["bottomRight"] = { Top = 100, Left = 220 },
// normal property
Tag = new object()
};
That was plenty of knowledge, but the best way to learn about initializer is to use them in code. After a while they become second nature for C# programmers and central part of many language idioms.