乐于分享
好东西不私藏

Excel VBA 编程基础 — 结构化数据类型(九)- 字典(二)

Excel VBA 编程基础 — 结构化数据类型(九)- 字典(二)

前面我们介绍了字典的基本操作。相对于集合来说,字典的操作更为完备,不仅键对应的值可以更新,甚至键本身也可以更新。在众多的字典操作中,只有如下几种情况才会导致错误:
  • dict.Add(key, item):当 key 已经存在时,发生错误
    • 这符合 Add 操作的本义:Add 操作增加的是全新的 key/item,如 key 已存在,而字典不允许重复键,因此发生错误。
  • dict.Remove(key):当 key 不存在时,发生错误
    • 这符合 Remove 操作的本义:删除操作只能删除已存在的 key/item。
  • dict.Key(key) = newKey:当 key 不存在或 newKey 也存在时,发生错误
    • 这符合键更新的本义:只能更新已存在的键,但又不允许重复键。
字典已经最小化了错误发生的情形,譬如 dict(key) 或 dict.Item(key),如果 key 不存在,则在字典中新增 key/空值,然后返回 key 对应的 item(空值),而不是报错。但这种因 key 不存在而访问 dict(key) 时导致的新增 key/空值 的操作,也算是一种副作用吧。要消除这种副作用,可以使用 dict.Exists(key) 判断 key 是否存在,但这又增加了访问 dict(key) 的负担。Microsoft Scripting Runtime 对字典的这种设计,也是刻意为之,毕竟 dict(key) 是更为频繁的操作,如果设计成 key 不存在时报错,就会大大增加访问 dict(key) 时错误处理的成本。
前文我们说,要使用字典,必须先引用 Microsoft Scripting Runtime。引用之后,我们就可以如下声明字典类型(Dictionary 是 Scripting.Dictionary 的简称)的变量:
Dim dict As New Dictionary
或者
Dim dict As DictionarySetdict = New Dictionary
这两种方式,在只有一个 dict 对象时,是等价的。但如果要构造多个 dict 对象,譬如在循环中构造字典对象并用这些对象组装数组时,就要使用第二种方式:只声明一个字典类型的变量,但使用 Set … New Dictionary 语句生成多个字典对象。
我们把这种引用 Microsoft Scripting Runtime 然后在类型声明中使用 Dictionary 作为类型的做法叫做早绑定(early binding)。早绑定的好处是,VBA 在编译阶段就知道 dict 的具体类型,这有利于类型检查,并且执行效率也要高一些。与之相反的,称为晚绑定(late binding),即不引用 Microsoft Scripting Runtime,而是通过 CreateObject 引入 Scripting.Dictionary:
Dim dict As ObjectDim k As VariantSet dict = CreateObject("Scripting.Dictionary")dict.Add "Name", "Musk"dict("Title") = "Tesla CEO"ForEach k in dict.Keys  Debug.Print k & " => " & dict(k)Next kDebug.Print TypeName(dict)   ' DictionaryDebug.Print VarType(dict)    ' 9=> vbObject
早绑定因为需要引用 Microsoft Scripting Runtime,但如果 Excel 文件丢失了这个引用,VBA 代码执行时就会发生错误。晚绑定则不会有这个问题,因为 CreateObject 函数是在运行时动态加载 Microsoft Scripting Runtime 的。另外,开发时的 Excel 版本和运行时的 Excel 版本如果不一致,也可能会导致问题。
可以在开发时使用早绑定,这样就可以享受 VBA 类型检查、AutoComplete以及符号常量的好处。而在分发给客户时使用晚绑定,这样可以避免由于引用丢失或 Excel 版本不一致而导致的问题。
可以使用 VBA 的条件编译区分早绑定和晚绑定:
#Const EarlyBinding = 1  ' 若使用晚绑定,则 0...#If EarlyBinding = 1 Then  Dim dict As New Scripting.Dictionary#Else  Dim dict As ObjectSetDict = CreateObject("Scripting.Dictionary")#End Ifdict.Add "Name""Musk"...
这里用到了 VBA 的条件编译设施:
  • #Const:定义条件编译的常量,一般用于下面的 #If 语句
  • #If … Then … #Else … #End If:与运行时执行的条件语句不同,这个带 # 号的 If 语句是在编译时执行的。VBA 编译器根据 #If condition 语句的 condition 的值,决定编译哪部分——Then 部分还是 #Else 部分(如果有的话)——代码。
下面,我们用 Dictionary 来重写 Collection 中的例子。
例1. 使用 Dictionary 剔除 Excel 数据集中的同名记录
图1 Excel 数据集
代码如下:
图2 使用 Dictionary 剔除 Excel 数据集中的同名记录
对比使用集合的例子(参见《集合(二)》图4),二者的代码基本类似,但因为字典本身具有判断 Key 是否存在的函数,不需要再写自己的 KeyExists 函数代码,因此,代码更加简洁。
相关阅读
Excel VBA 编程基础 — 结构化数据类型(八)- 字典
Excel VBA 编程基础 — 结构化数据类型(七)- 集合(二)