You could use a helper like this:
public struct MyDictionaryBuilder<TKey, TValue> : IEnumerable
{
private ImmutableDictionary<TKey, TValue>.Builder _builder;
public MyDictionaryBuilder(int dummy)
{
_builder = ImmutableDictionary.CreateBuilder<TKey, TValue>();
}
public void Add(TKey key, TValue value) => _builder.Add(key, value);
public TValue this[TKey key]
{
set { _builder[key] = value; }
}
public ImmutableDictionary<TKey, TValue> ToImmutable() => _builder.ToImmutable();
public IEnumerator GetEnumerator()
{
// Only implementing IEnumerable because collection initializer
// syntax is unavailable if you don't.
throw new NotImplementedException();
}
}
(I'm using the new C# 6 expression-bodied members, so if you want this to compile on older versions, you'd need to expand those into full members.)
With that type in place, you can use collection initializer syntax like so:
var d = new MyDictionaryBuilder<int, string>(0)
{
{ 1, "One" },
{ 2, "Two" },
{ 3, "Three" }
}.ToImmutable();
or if you're using C# 6 you could use object initializer syntax, with its new support for indexers (which is why I included a write-only indexer in my type):
var d2 = new MyDictionaryBuilder<int, string>(0)
{
[1] = "One",
[2] = "Two",
[3] = "Three"
}.ToImmutable();
This combines the benefits of both proposed advantages:
- Avoids building a full
Dictionary<TKey, TValue>
- Lets you use initializers
The problem with building a full Dictionary<TKey, TValue>
is that there is a bunch of overhead involved in constructing that; it's an unnecessarily expensive way of passing what's basically a list of key/value pairs, because it will carefully set up a hash table structure to enable efficient lookups that you'll never actually use. (The object you'll be performing lookups on is the immutable dictionary you eventually end up with, not the mutable dictionary you're using during initialization.)
ToImmutableDictionary
is just going to iterate through the contents of the dictionary (a process rendered less efficient by the way Dictionary<TKey, TValue>
works internally - it takes more work to do this than it would with a simple list), gaining absolutely no benefit from the work that went into building up the dictionary, and then has to do the same work it would have done if you'd used the builder directly.
Jon's code avoids this, using only the builder, which should be more efficient. But his approach doesn't let you use initializers.
I share Jon's frustration that the immutable collections don't provide a way to do this out of the box.
Edited 2017/08/10: I've had to change the zero-argument constructor to one that takes an argument that it ignores, and to pass a dummy value everywhere you use this. @gareth-latty pointed out in a comment that a struct
can't have a zero-args constructor. When I originally wrote this example that wasn't true: for a while, previews of C# 6 allowed you to supply such a constructor. This feature was removed before C# 6 shipped (after I wrote the original answer, obviously), presumably because it was confusing - there were scenarios in which the constructor wouldn't run. In this particular case it was safe to use it, but unfortunately the language feature no longer exists. Gareth's suggestion was to change it into a class, but then any code using this would have to allocate an object, causing unnecessary GC pressure - the whole reason I used a struct
was to make it possible to use this syntax with no additional runtime overhead.
I tried modifying this to perform deferred initialization of _builder
but it turns out that the JIT code generator isn't smart enough to optimize these away, so even in release builds it checks _builder
for each item you add. (And it inlines that check and the corresponding call to CreateBuilder
which turns out to produce quite a lot of code with lots of conditional branching). It really is best to have a one-time initialization, and this has to occur in the constructor if you want to be able to use this initializer syntax. So the only way to use this syntax with no additional costs is to have a struct that initializes _builder
in its constructor, meaning that we now need this ugly dummy argument.