我有声明可变字典的代码,但是当我尝试更改元素时会出现错误。
代码:
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
这将创建一个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
这是为什么?
IDictionary
是一个接口,而不是一个类。该接口可以具有多种不同的实现。你甚至可以自己做一个。
Dictionary
确实是这些实现之一。它支持界面的全部功能。
但这不是dict
函数返回的实现。让我们尝试一下:
> let d = dict [(1,2)]
> d.GetType().FullName
"Microsoft.FSharp.Core.ExtraTopLevelOperators+DictImpl`3[...
事实证明,该dict
函数返回的实现是Microsoft.FSharp.Core.ExtraTopLevelOperators.DictImpl
-DictImpl
在F#标准库的内部深入定义的名为class的类。
碰巧的是,该接口上的某些方法抛出了NotSupportedException
:
> d.Add(4,5)
System.NotSupportedException: This value cannot be mutated
那是设计使然。故意这样做是为了支持“默认情况下的不变性”。
如果你确实想拥有一个可变的版本,则可以使用Dictionary
的构造函数之一来创建一个副本:
> let m = Dictionary(d)
> m.Add(4,5) // Works now
Map
和之间的区别Dictionary
是实现,这意味着内存和运行时特性。
Dictionary
是一个哈希表。它提供了固定时间的插入和检索,但是要付出代价,它依赖于其键的一致散列,并且其更新是破坏性的,这也带来了线程不安全的问题。
Map
被实现为一棵树。它提供对数插入和检索,但作为回报,它具有持久数据结构的优点。此外,它要求密钥具有可比性。尝试这个:
> type Foo() = class end
> let m = Map [(Foo(), "bar")]
error FS0001: The type 'Foo' does not support the 'comparison' constraint
比较键对于构建树是必不可少的。
一切都澄清了;很长一段时间还不清楚。因此,F#有两个不可变的字典(Dictionary和Map),而可变的BCL是一个。也称为Dictionary,但完全不同。这就提出了另一个问题:为什么我可以做:xx |> dict但不做xx |> Dictionary,而我却可以做xx |> Map,但是xx |> map不存在?另外,由于名称相同,当我打开System.Collections.Generic时,“词典”是否会更改含义(F#与BCL)?
您可能会感到困惑
Dictionary
,IDictionary
我知道它们全部都属于IDictionary接口,但是如果实例化一个Dictionary,并且如果包含System.Collections.Generic,则Keywords Dictionary不会指向BCL类型而不是F#类型吗?
它不是关键字,而是标识符,但是,确实可以标识BCL类。但是,F#标准库未公开此类标识符。