BASIC和LotusScript中的Variant



BASIC和LotusScript中的Variant。

缘起最近我回看以前记的关于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,只能用变体型。

为变体型赋数组值时,它指向的不是原数组,而会复制一个新数组,所以原数组的数据变更不能从它读取到。

  1. Dim logger As Log4Dom  
  2. Set logger=GetLogger(Nothing )  
  3. Dim v         
  4. Dim fa(0) As String  
  5. v=fa  
  6. logger.PrintMsg(v(0))   ‘[1]14:56:03:63 -   
  7. fa(0)=“fa”              ‘[1]14:56:07:67 -   
  8. logger.PrintMsg(v(0))  

由上可见当原数组fa的内容已更改时,变体型v包含的数组未受影响。问题

用变体型虽然有上面说的是非曲折,但还都有规可循,能够理解。下面就来说说我文章开头提到的既没有文档记录我也理解不了的行为。

通过视图查找NotesDocument或NotesViewEntry,再据它们读取相关数据是编程中最常见的情况。这时如果读取的是视图显示的字段或计算值,通过视图的列值也就是索引(84. 从视图索引说Notes数据库(下))会比通过文档读取快速。例如:

  1. Dim v         
  2. Dim fa(0) As String  
  3. v=fa  
  4. fa(0)=“fa”  
  5. Dim da() As String  
  6. ReDim da(0)  
  7. da(0)=“da”  
  8.   
  9. Dim container(2) As Variant  
  10. ‘v=container ’No error  
  11. ‘Print ”DataType(container): ” & DataType(container) ’8204  
  12. ‘Print ”DataType(v): ” & DataType(v) ’8716  
  13.   
  14. ‘container(0)=1  
  15. ‘v=container ’No error  
  16.   
  17. ‘Set container(0)=New NotesSession  
  18. ‘v=container ’No error  
  19.       
  20. ‘container(0)=fa  
  21. ‘container(1)=da  
  22. ‘v=container ’Error  

由 上可见给一个变体型变量赋值一个变体型数组时,如果如果型数组的元素是Empty、数、字符串这些Scalar值,或者NotesDocument之类的 对象值,都没问题。如果元素本身是数组,就会出错。换一种角度就是说,变体型变量不能容纳两层或以上的嵌套数组。这一点是否从BASIC继承而来,原因为 何,我现在都还不知道。所以遇到要用ColumnValues又有多值列的时候,就不能将其赋给一个变体型变量,而只能一直如此写

  1. forall e in entry.ColumnValues (1)  
  2.     print e  
  3. end forall  

注1:在VB6之前除了定长字符串和用户定义类型外,其他数据类型都能容纳。VB6的变体型也能容纳用户定义类型。

注 2:在VB中,因为经常要和OLE对象交互,而其中的诸如Collection类型和IDispatch接口都涉及编译时类型不能确定的对象,必须使用变 体型。VB的Optional(可选)函数参数因为在调用时可能省略而没有内容,也须定义为变体型。此外,变体型对象的方法调用是晚期绑定,可用以写出更 通用的代码。