我想写这样的东西:
var d = new ImmutableDictionary<string, int> { { "a", 1 }, { "b", 2 } };
(使用System.Collections.Immutable中的ImmutableDictionary
)。这似乎是一个简单的用法,因为我预先声明了所有的价值 - 那里没有变异。但这给了我错误:
类型“
System.Collections.Immutable.ImmutableDictionary<TKey,TValue>
”没有定义构造函数
我应该如何用静态内容创建一个新的不可变字典?
答案 0 :(得分:36)
您无法使用集合初始值设定项创建不可变集合,因为编译器会将它们转换为对Add
方法的一系列调用。例如,如果您查看var d = new Dictionary<string, int> { { "a", 1 }, { "b", 2 } };
的IL代码,您将获得
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)
显然这违反了不可变集合的概念。
你自己的回答和Jon Skeet都是解决这个问题的方法。
// 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();
答案 1 :(得分:28)
首先创建“普通”词典并拨打ToImmutableDictionary
(根据您自己的回答),或使用ImmutableDictionary<,>.Builder
:
var builder = ImmutableDictionary.CreateBuilder<string, int>();
builder.Add("a", 1);
builder.Add("b", 2);
var result = builder.ToImmutable();
遗憾的是,构建器没有公共构造函数,因为它阻止你使用集合初始化器语法,除非我错过了某些东西...... {{1方法返回Add
意味着你甚至无法将调用链接到它,使它更烦人 - 据我所知,你基本上不能使用构建器在单个表达式中创建不可变字典,这非常令人沮丧:(
答案 2 :(得分:11)
您可以使用这样的帮助:
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();
}
}
(我正在使用新的C#6表达式成员,所以如果你想在旧版本上编译它们,你需要将它们扩展为正式成员。)
使用该类型,您可以使用集合初始化程序语法,如下所示:
var d = new MyDictionaryBuilder<int, string>(0)
{
{ 1, "One" },
{ 2, "Two" },
{ 3, "Three" }
}.ToImmutable();
或者如果你正在使用C#6,你可以使用对象初始化器语法,以及对索引器的新支持(这就是为什么我在我的类型中包含了一个只写索引器):
var d2 = new MyDictionaryBuilder<int, string>(0)
{
[1] = "One",
[2] = "Two",
[3] = "Three"
}.ToImmutable();
这结合了两个优点的好处:
Dictionary<TKey, TValue>
构建一个完整的Dictionary<TKey, TValue>
的问题在于构造它有一大堆开销;传递基本上是键/值对的列表是一种不必要的昂贵方式,因为它会仔细设置一个哈希表结构,以实现您永远不会实际使用的高效查找。 (您将要执行查找的对象是您最终得到的不可变字典,而不是您在初始化期间使用的可变字典。)
ToImmutableDictionary
将迭代遍历字典的内容(通过Dictionary<TKey, TValue>
内部工作方式呈现 less 效率的过程 - 执行此操作需要更多工作比起一个简单的列表所做的那样,完全没有从构建字典的工作中获益,然后必须做同样的工作,如果你直接使用了构建器。
Jon的代码避免了这种情况,仅使用构建器,它应该更有效。但他的方法不允许你使用初始化器。
我同意Jon的沮丧,即不可变的集合无法提供开箱即用的方法。
编辑2017/08/10 :我必须将零参数构造函数更改为接受它忽略的参数的构造函数,并在您使用它的任何地方传递虚拟值。 @ gareth-latty在评论中指出struct
不能有零参数构造函数。当我最初编写这个不成立的例子时:有一段时间,C#6的预览允许你提供这样的构造函数。这个功能在C#6发布之前被删除了(显然是在我写完原始答案之后),可能是因为它令人困惑 - 有些情况下构造函数无法运行。在这种特殊情况下使用它是安全的,但遗憾的是语言功能已不复存在。 Gareth的建议是把它改成一个类,但是然后使用它的任何代码都必须分配一个对象,导致不必要的GC压力 - 我使用struct
的全部原因是为了能够使用这个语法而没有额外的运行时开销。
我尝试对此进行修改以执行_builder
的延迟初始化,但事实证明JIT代码生成器不够智能,无法优化它们,因此即使在发布版本中,它也会检查每个_builder
你添加的项目。 (并且它内联检查和对CreateBuilder
的相应调用,结果产生了大量具有大量条件分支的代码)。最好进行一次性初始化,如果您希望能够使用此初始化程序语法,则必须在构造函数中进行初始化。因此,使用此语法而无需额外成本的唯一方法是在其构造函数中初始化_builder
的结构,这意味着我们现在需要这个丑陋的伪参数。
答案 3 :(得分:9)
到目前为止,我最喜欢这个:
var d = new Dictionary<string, int> { { "a", 1 }, { "b", 2 } }.ToImmutableDictionary();
答案 4 :(得分:7)
或者这个
ImmutableDictionary<string, int>.Empty
.Add("a", 1)
.Add("b", 2);
还有AddRange方法可用。
答案 5 :(得分:0)
您可以编写一个带有隐式运算符的自定义 ImmutableDictionaryLiteral
类,使语法非常接近您想要的。
public class ImmutableDictionaryLiteral<TKey, TValue> : Dictionary<TKey, TValue>
{
public static implicit operator ImmutableDictionary<TKey, TValue>(ImmutableDictionaryLiteral<TKey, TValue> source)
{
return source.ToImmutableDictionary();
}
}
然后您通过声明一个 ImmutableDictionary<TKey, TValue>
变量并使用 ImmutableDictionaryLiteral<TKey, TValue>
值进行初始化来调用它。
ImmutableDictionary<string, string> dict = new ImmutableDictionaryLiteral<string, string>()
{
{ "key", "value" },
{ "key2", "value2" }
};
这也适用于对象初始值设定项语法
ImmutableDictionary<string, string> dict = new ImmutableDictionaryLiteral<string, string>()
{
["key"] = "value",
["key2"] = "value2"
};
答案 6 :(得分:0)
我更喜欢这种语法:
var dict = ImmutableDictionaryEx.Create<int, string>(
(1, "one"),
(2, "two"),
(3, "three"));
使用此方法可以轻松实现:
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();
}
}
答案 7 :(得分:0)
我在这里没有看到另一个答案(也许是一种新方法?):
只需使用 CreateRange
方法通过 IEnumerable<KVP>
创建一个新的 ImmutableDictionary。
// 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)));