我有声明可变字典的代码,但是当我尝试更改元素时会出现错误。
编码:
let layers =
seq {
if recipes.ContainsKey(PositionSide.Short) then yield! buildLayerSide recipes.[PositionSide.Short]
if recipes.ContainsKey(PositionSide.Long) then yield! buildLayerSide recipes.[PositionSide.Long]
}
|> Seq.map (fun l -> l.Id, l)
|> dict
Run Code Online (Sandbox Code Playgroud)
这将创建一个IDictionary
. 我知道对象本身是不可变的,但字典的内容应该是可变的。
当我通过显式初始化字典来更改代码时,它就会变得可变:
let layers =
let a =
seq {
if recipes.ContainsKey(PositionSide.Short) then yield! buildLayerSide recipes.[PositionSide.Short]
if recipes.ContainsKey(PositionSide.Long) then yield! buildLayerSide recipes.[PositionSide.Long]
}
|> Seq.map (fun l -> l.Id, l)
|> dict
let x = Dictionary<string, Layer>()
a
|> Seq.iter (fun kvp -> x.[kvp.Key] <- kvp.Value)
x
Run Code Online (Sandbox Code Playgroud)
这是为什么?
IDictionary
是一个接口,而不是一个类。这个接口可能有多种不同的实现。你甚至可以自己制作一个。
Dictionary
确实是这些实现之一。它支持界面的全部功能。
但这不是dict
函数返回的实现。让我们试试这个:
> let d = dict [(1,2)]
> d.GetType().FullName
"Microsoft.FSharp.Core.ExtraTopLevelOperators+DictImpl`3[...
Run Code Online (Sandbox Code Playgroud)
事实证明,该dict
函数返回的实现是Microsoft.FSharp.Core.ExtraTopLevelOperators.DictImpl
——一个DictImpl
在 F# 标准库内部定义的类。
碰巧的是,该接口上的某些方法会抛出一个NotSupportedException
:
> d.Add(4,5)
System.NotSupportedException: This value cannot be mutated
Run Code Online (Sandbox Code Playgroud)
那是设计使然。它是故意这样做的,以支持“默认情况下的不变性”。
如果你真的想要一个可变版本,你可以使用Dictionary
的构造函数之一创建一个副本:
> let m = Dictionary(d)
> m.Add(4,5) // Works now
Run Code Online (Sandbox Code Playgroud)
Map
和之间的区别在于Dictionary
实现,这意味着内存和运行时特性。
Dictionary
是一个哈希表。它提供恒定时间的插入和检索,但要为此付出代价,它依赖于其密钥的一致散列,其更新是破坏性的,这也伴随着线程不安全。
Map
被实现为一棵树。它提供对数插入和检索,但作为回报具有持久数据结构的好处。此外,它要求密钥具有可比性。尝试这个:
> type Foo() = class end
> let m = Map [(Foo(), "bar")]
error FS0001: The type 'Foo' does not support the 'comparison' constraint
Run Code Online (Sandbox Code Playgroud)
比较键对于构建树是必不可少的。