How can I create a new instance of ImmutableDictionary?
Asked Answered
T

9

86

I would like to write something like this:

var d = new ImmutableDictionary<string, int> { { "a", 1 }, { "b", 2 } };

(using ImmutableDictionary from System.Collections.Immutable). It seems like a straightforward usage as I am declaring all the values upfront -- no mutation there. But this gives me error:

The type 'System.Collections.Immutable.ImmutableDictionary<TKey,TValue>' has no constructors defined

How I am supposed to create a new immutable dictionary with static content?

Trying answered 4/6, 2014 at 9:22 Comment(0)
O
82

You can't create immutable collection with a collection initializer because the compiler translates them into a sequence of calls to the Add method. For example if you look at the IL code for var d = new Dictionary<string, int> { { "a", 1 }, { "b", 2 } }; you'll get

IL_0000: newobj instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string, int32>::.ctor()
IL_0005: dup
IL_0006: ldstr "a"
IL_000b: ldc.i4.1
IL_000c: callvirt instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string, int32>::Add(!0, !1)
IL_0011: dup
IL_0012: ldstr "b"
IL_0017: ldc.i4.2
IL_0018: callvirt instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string, int32>::Add(!0, !1)

Obviously this violates the concept of immutable collections.

Both your own answer and Jon Skeet's are ways to deal with this.

// lukasLansky's solution
var d = new Dictionary<string, int> { { "a", 1 }, { "b", 2 } }.ToImmutableDictionary();

// Jon Skeet's solution
var builder = ImmutableDictionary.CreateBuilder<string, int>();
builder.Add("a", 1);
builder.Add("b", 2);   
var result = builder.ToImmutable();
Oxidate answered 4/6, 2014 at 9:30 Comment(2)
Note that I've now changed my code, as the previous code wouldn't have compiled :(Predation
Oh, that's revealing answer! Thanks!Sheehan
P
66

Either create a "normal" dictionary first and call ToImmutableDictionary (as per your own answer), or use ImmutableDictionary<,>.Builder:

var builder = ImmutableDictionary.CreateBuilder<string, int>();
builder.Add("a", 1);
builder.Add("b", 2);
var result = builder.ToImmutable();

It's a shame that the builder doesn't have a public constructor as far as I can tell, as it prevents you from using the collection initializer syntax, unless I've missed something... the fact that the Add method returns void means you can't even chain calls to it, making it more annoying - as far as I can see, you basically can't use a builder to create an immutable dictionary in a single expression, which is very frustrating :(

Predation answered 4/6, 2014 at 9:29 Comment(13)
Yeah, a little irritating, but at the same time that is something that the developer can encapsulate themselves in a helper class or something.Memorandum
@AdamHouldsworth: I'd be interested in the reasoning behind the lack of constructors for builders. For the immutable collections themselves, that's fine - but not for the builder...Predation
@AdamHouldsworth: In fact, my previous post wouldn't have worked due to the return type of Add being void. Ick! See the horrible edit...Predation
Collection initialiser for the builder ;-) of course, I jest.Memorandum
Thanks for the answer, I am going to play with builders now. :-)Sheehan
@JonSkeet The builder has an AddRange method for key value pairs, which is pretty close to the constructor for it, but I think by this point it'd be easier to use the extension methods that were also published with the library.Memorandum
@AdamHouldsworth: It's not nearly as useful as either making the call fluent or providing a parameterless constructor to use collection initializers though. The Builder type seems nearly useless at the moment, IMO.Predation
@JonSkeet Yeah I agree generally, but I think with the extension methods the problem is fairly limited.Memorandum
@AdamHouldsworth: It raises the question about why the builder types exist when their usefulness is so limited. And they could be so much more useful, very easily - either with fluent Add methods or public constructors.Predation
The "normal" dictionary + ToImmutableDictionary approach seems like one to avoid, because there's considerable overhead in building a Dictionary<TKey, TValue> - it creates a hashtable internally to enable efficient lookup. But as far as I can tell, ToImmutableDictionary just iterates over the content, so all that work to build the hashtable is effectively wasted.Ceremonial
@IanGriffiths: Yes, it depends on whether this is is in a piece of code which is performance sensitive. For one-time setup, for example (static readonly fields) it's probably the simplest approach - especially as it allows for the collection initializer. I like your helper idea... it's just a shame it's needed :(Predation
Absurd that the Add method isn't fluent. Who wrote that builder class? It should be ImmutableDictionary.Builder.Add("aKey","aValue").Add("anotherKey","anotherValue").Build() like every other builder ever written.Brutify
@KevinWong, I actually think supporting a dictionary initializer (making the Builder ctor public) would be even better than fluent Add methods here as the final syntax is vastly simplified.Placencia
T
25

So far I like this most:

var d = new Dictionary<string, int> { { "a", 1 }, { "b", 2 } }.ToImmutableDictionary();
Trying answered 4/6, 2014 at 9:22 Comment(1)
Be aware that this will cause unnecessary extra work at runtime - see my answer https://mcmap.net/q/238420/-how-can-i-create-a-new-instance-of-immutabledictionary for whyCeremonial
C
16

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.

Ceremonial answered 11/2, 2015 at 6:43 Comment(4)
Note that you can't define a public parameterless constructor on a struct, so you'd have to make this a class.Somerset
Ah. You were allowed parameterless constructs on a struct in the preview of C# 6 that was available when I originally wrote that. Using a class adds an extra allocation, meaning that this is more than just syntactic sugar - it would then impose a runtime overhead. That's why I made it a struct. So I've made a slightly clunky update that enables this to continue to work efficiently, but with the slightly unfortunate need to add a useless ctor arg. (The JIT compiler optimizes the argument out of existence in release builds, retaining the runtime efficiency of the original.)Ceremonial
Just wanted to point out that implementing IEnumerable is not needed assuming one uses the index-based initialization only. Before looking at this answer, I actually didn't know the index-based initialization was based on the presence of an indexer in the class... I thought it was only possible to use with IDictionary.Placencia
C# 12 (.NET 8.0) has primary constructors that can be parameterless, so the struct definition can be public readonly struct MyDictionaryBuilder<TKey, TValue>() : IEnumerable where TKey : notnull { ... }, make _builder readonly and initialized with a field initializer, and drop the constructor in the body.Hungnam
M
14

Or this

ImmutableDictionary<string, int>.Empty
   .Add("a", 1)
   .Add("b", 2);

There is also AddRange method available.

Mizzle answered 7/2, 2017 at 16:11 Comment(2)
For the record, as per the documentation, you're creating 2 immutable dictionary here, once every time you call Add.Bare
Of course, it's immutable after all. For small loads it really doesn't matter as they are short lived as per larger there is always AddRange.Mizzle
R
2

The following method works quite well for me:

var dictionary = ImmutableDictionary.CreateRange(
    new []
    {
         KeyValuePair.Create("a", 1),
         KeyValuePair.Create("b", 2),
    });

Although it requires creating a KeyValuePair for each input it is a simple way to create a ImmutableDictionary and can be easilty abstracted away using an extension method if needed.

Readiness answered 21/11, 2023 at 12:19 Comment(1)
It also creates an extra intermediate array, something that the solution of Ian Griffiths does not do. But it has a natural beauty of not requiring any extra helper types defined by the user. If you want to reduce verbosity of the repeated KeyValuePair.Create and don't mind being explicit about the key and value types, you can rephrase the statement as: var dictionary = ImmutableDictionary.CreateRange(new KeyValuePair<string, int>[] { new("a", 1), new("b", 2)}); (blame StackOverflow for lack of multiline code samples in comments).Hungnam
N
1

I prefer this syntax:

var dict = ImmutableDictionaryEx.Create<int, string>(
                (1, "one"),
                (2, "two"),
                (3, "three"));

Can be easily achieved using this method:

public static class ImmutableDictionaryEx {

    /// <summary>
    /// Creates a new <see cref="ImmutableDictionary"/> with the given key/value pairs.
    /// </summary>
    public static ImmutableDictionary<K, V> Create<K, V>(params (K key, V value)[] items) where K : notnull {
        var builder = ImmutableDictionary.CreateBuilder<K, V>();
        foreach (var (key, value) in items)
            builder.Add(key, value);
        return builder.ToImmutable();
    }

}
Narcisanarcissism answered 19/2, 2021 at 9:29 Comment(0)
V
1

There is another answer I don't see here (maybe it's a new method?):

Just use the CreateRange method to create a new ImmutableDictionary using an IEnumerable<KVP>.

// The MyObject record example
record MyObject(string Name, string OtherStuff);


// The init logic for the ImmutableDictionary
var myObjects = new List<MyObject>();
var dictionary = ImmutableDictionary
   .CreateRange(myObjects.Select(obj => new KeyValuePair<string, MyObject>(obj.Name, obj)));
Vasileior answered 17/6, 2021 at 7:15 Comment(0)
T
0

You could write a custom ImmutableDictionaryLiteral class with an implicit operator to make a syntax very close to what you want.

public class ImmutableDictionaryLiteral<TKey, TValue> : Dictionary<TKey, TValue>
{
    public static implicit operator ImmutableDictionary<TKey, TValue>(ImmutableDictionaryLiteral<TKey, TValue> source)
    {
        return source.ToImmutableDictionary();
    }
}

Then you call it by declaring a ImmutableDictionary<TKey, TValue> variable and initializing with a ImmutableDictionaryLiteral<TKey, TValue> value.

ImmutableDictionary<string, string> dict = new ImmutableDictionaryLiteral<string, string>()
{
    { "key", "value" },
    { "key2", "value2" }
};

This also works with object initializer syntax

ImmutableDictionary<string, string> dict = new ImmutableDictionaryLiteral<string, string>()
{
    ["key"] = "value",
    ["key2"] = "value2"
};
Terenceterencio answered 11/1, 2021 at 22:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.