最近我回看以前记的关于Notes的笔记,发现在Notes bugs类别下,两条编号间隔一的竟然是相差无几的内容。
2. 'Type mismatch' occurs if an nested array e.g. when an item of ColumnValues is an array, is assigned to a variant or passed as an argument of a function.
4. When an item of ColumnValues is an array, 'Type mismatch' occurs if it is assigned to a variant, e.g.columns=entry.Columnvalues
这至少说明两件事。一是当我初次遇到以上错误,记录下之后,并未注意避免,甚至完全忘记了,下次再撞上时,还会当成新发现记录。二是我的英语保持在同样的水准上,在叙述同一件事情上风格基本一致。
对这个在同一地方跌倒两次的教训,我觉得有必要说一说。既可以提醒大家不要重蹈我的覆辙,也能加深我自己的印象,以免下次又记上一条重复的笔记。
LotusScript中Variant之来源
为了全面理解这个问题和多凑些字数,要从variant(变体型)说起。Visual Basic中变体型是一种能容纳各种数据类型的数据结构【注1】。技术上说,它是taggedunion(加标签的联合),占用十六字节的内存,前两字节保存数据类型的编码(即标签),第三至八字节为了对齐而空白,第九至十六共八个字节保存实际的数据(即联合)。前两字节的内容可以用VarType函数返回如下:
Constant |
Value |
Description |
vbEmpty |
0 |
Empty (uninitialized) |
vbNull |
1 |
Null (no valid data) |
vbInteger |
2 |
Integer |
vbLong |
3 |
Long integer |
vbSingle |
4 |
Single-precision floating-point number |
vbDouble |
5 |
Double-precision floating-point number |
vbCurrency |
6 |
Currency value |
vbDate |
7 |
Date value |
vbString |
8 |
String |
vbObject |
9 |
Object |
vbError |
10 |
Error value |
vbBoolean |
11 |
Boolean value |
vbVariant |
12 |
Variant (used only with arrays of variants) |
vbDataObject |
13 |
A data access object |
vbDecimal |
14 |
Decimal value |
vbByte |
17 |
Byte value |
vbLongLong |
20 |
LongLong integer (Valid on 64-bit platforms only.) |
vbUserDefinedType |
36 |
Variants that contain user-defined types |
vbArray |
8192 |
Array |
因为变体型能包容各种数据类型,立刻就带来正反两方面的效果。缺点是取消了编译时类型检查,容易引入错误。好处是没有静态类型的束缚,可以写出更灵活的代码【注2】。
用处和特点
LotusScript是BASIC风格的脚本语言,继承了它被发明时BASIC的features,包括变体型。LotusScript中的变体型能包容除用户定义类型之外所有的数据类型。确定具体数据类型的DataType函数的返回值如下:
Return |
Value type |
Constant |
Variants only |
0 |
EMPTY |
V_EMPTY |
Yes |
1 |
NULL |
V_NULL |
Yes |
2 |
Integer |
V_INTEGER |
|
3 |
Long |
V_LONG |
|
4 |
Single |
V_SINGLE |
|
5 |
Double |
V_DOUBLE |
|
6 |
Currency |
V_CURRENCY |
|
7 |
Date/Time |
V_DATE |
Yes |
8 |
String |
V_STRING |
|
9 |
OLE object or NOTHING |
V_DISPATCH |
Yes |
10 |
OLE error |
V_ERROR |
Yes |
11 |
Boolean |
V_BOOLEAN |
|
12 |
Variant list or array |
V_VARIANT |
|
13 |
IUNKNOWN (OLE value) |
V_IUNKNOWN |
Yes |
17 |
Byte |
V_BYTE |
|
34 |
User-defined object |
V_LSOBJ |
|
35 |
Product object |
V_PRODOBJ |
|
2048 |
List |
|
|
8192 |
Fixed array |
|
|
8704 |
Dynamic array |
对于上表中作为容器的最后三项,DataType的返回值为它们对应的值加上它们所包含的元素的类型值所得的和。例如,对字符串固定数组,得8192+8=8200;对变体型动态数组,得8704+12=8716。注意变体型的类型值12只用在变体型列表和数组时,因为对单个变体型应用DataType时,得到的是它所包容的具体数据类型的值。
我偏好静态类型变量带来的确定感和编译时类型检查确保的安全感,所以除非特殊情况并不喜用变体型。但是LotusScript语法上的局限性使得在一些场合下不得不使用。
1. 函数的返回类型不能声明为数组,有此需要时只能用变体型。
2. 自定义对象的方法不支持重载,需要传入多种类型的参数时只能用变体型。
3. 数组变量不能整体赋值,例如从Split()或doc.ItemName,只能用变体型。
为变体型赋数组值时,它指向的不是原数组,而会复制一个新数组,所以原数组的数据变更不能从它读取到。
Dim logger As Log4Dom Set logger=GetLogger(Nothing ) Dim v Dim fa(0) As String v=fa logger.PrintMsg(v(0)) '[1]14:56:03:63 - fa(0)="fa" '[1]14:56:07:67 - logger.PrintMsg(v(0))由上可见当原数组fa的内容已更改时,变体型v包含的数组未受影响。
问题
用变体型虽然有上面说的是非曲折,但还都有规可循,能够理解。下面就来说说我文章开头提到的既没有文档记录我也理解不了的行为。
通过视图查找NotesDocument或NotesViewEntry,再据它们读取相关数据是编程中最常见的情况。这时如果读取的是视图显示的字段或计算值,通过视图的列值也就是索引(84. 从视图索引说Notes数据库(下))会比通过文档读取快速。例如:
Dim v Dim fa(0) As String v=fa fa(0)="fa" Dim da() As String ReDim da(0) da(0)="da" Dim container(2) As Variant 'v=container 'No error 'Print "DataType(container): " & DataType(container) '8204 'Print "DataType(v): " & DataType(v) '8716 'container(0)=1 'v=container 'No error 'Set container(0)=New NotesSession 'v=container 'No error 'container(0)=fa 'container(1)=da 'v=container 'Error
由上可见给一个变体型变量赋值一个变体型数组时,如果如果型数组的元素是Empty、数、字符串这些Scalar值,或者NotesDocument之类的对象值,都没问题。如果元素本身是数组,就会出错。换一种角度就是说,变体型变量不能容纳两层或以上的嵌套数组。这一点是否从BASIC继承而来,原因为何,我现在都还不知道。所以遇到要用ColumnValues又有多值列的时候,就不能将其赋给一个变体型变量,而只能一直如此写
forall e in entry.ColumnValues (1) print e end forall
注1:在VB6之前除了定长字符串和用户定义类型外,其他数据类型都能容纳。VB6的变体型也能容纳用户定义类型。
注2:在VB中,因为经常要和OLE对象交互,而其中的诸如Collection类型和IDispatch接口都涉及编译时类型不能确定的对象,必须使用变体型。VB的Optional(可选)函数参数因为在调用时可能省略而没有内容,也须定义为变体型。此外,变体型对象的方法调用是晚期绑定,可用以写出更通用的代码。