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