標籤:lotusscript variant vb basic
緣起
最近我回看以前記的關於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 Log4DomSet logger=GetLogger(Nothing )Dim vDim fa(0) As Stringv=falogger.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 vDim fa(0) As Stringv=fafa(0)="fa"Dim da() As StringReDim 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 eend forall
注1:在VB6之前除了定長字串和使用者定義型別外,其他資料類型都能容納。VB6的變體型也能容納使用者定義型別。
注2:在VB中,因為經常要和OLE對象互動,而其中的諸如Collection類型和IDispatch介面都涉及編譯時間類型不能確定的對象,必須使用變體型。VB的Optional(可選)函數參數因為在調用時可能省略而沒有內容,也須定義為變體型。此外,變體型對象的方法調用是晚期綁定,可用以寫出更通用的代碼。
85. BASIC和LotusScript中的Variant