87. 再谈变体型Variant

在 85. BASIC和LotusScript中的Variant一文中,我提到了BASIC风格的语言中的变体型Variant。由于下述种种原因,在LotusScript中经常要用到变体型。

1.      函数的返回类型不能声明为数组,有此需要时只能用变体型。

2.      自定义对象的方法不支持重载,需要传入多种类型的参数时只能用变体型。

3.      数组变量不能整体赋值,例如从Split()或doc.ItemName,只能用变体型。

4.      需要写对多种数据类型通用的逻辑。

LotusScript是采用类定义(class definition,与JavaScript等语言的鸭子类型duck typing相对)的类型体系,在不使用变体型时,执行编译时类型检查,即静态类型检查(static type-checking);而一旦使用变体型,类型检查就被延迟到运行时,即动态类型检查(dynamic type-checking)。两种类型检察孰优孰劣,见仁见智。但是变体型在使用时与普通数据类型相比有许多不同之处和特殊的问题,值得专门指出。

赋值

LotusScript里给变量赋值,依据变量的数据类型,分为几种情况。

标量:包括各种数值类型、字符串、日期等单值,用Let语句赋值,Let通常被省略。

对象:包括产品对象(在Notes里即如NotesDocument的各类对象)、自定义对象和OLE对象,用Set语句赋值,Set不能被省略。

数组和列表:不能整体被赋值,只能对其单个元素赋值。是否要用Set由数组或列表元素的数据类型决定。

变体型:由所赋值的具体数据类型决定,如果是对象则要加Set。

用户定义数据类型(user-defined data types):与标量一样,用Let语句赋值,Let通常被省略。但是用户定义数据类型值不能被赋予变体型变量。

由上可见,因为变体型变量既能容纳标量,又能容纳对象,所以在赋值时是否加Set要根据所赋值的具体数据类型,而如果所赋值本身就是变体型,是否为对象在编译时不知道,就可能在运行时出现错误。须加Set未加时报错:SET required on class instance assignment。不得加Set加上时报错Typemismatch。

因此在为变体型赋变体型值前,须显式判断所赋值是否为对象。

Sub SetValue(variable As Variant, value As Variant)
	If IsObject(value) Then
		Set variable=value
	Else
		variable=value
	End If
End Sub
对象类型

在Java这样的完全面向对象的语言中,判断一个对象是否是某个类型有一个专门的运算符instanceof。LotusScript里也有一个类似的运算符IsA,但是却有一定的局限性。如果你在一个脚本库lib1里定义了某个对象类型MyClass,在另一个脚本库lib2里定义的某个函数Foo用到IsA,然后在一个代理中引用这两个脚本库,声明某个变量为MyClass类型,再将该变量传到Foo中,IsA运算的结果出人意料地为False。原因是IsA只能判断它所在的脚本环境知道的对象类型,MyClass没在lib2定义,lib2也没有引用lib1,所以对它来说,MyClass是未知的。解决方法是用TypeName函数,无论它要测试的对象类型在它运行的脚本环境里是否已知,都能准确地获得自定义对象的类型名称。所以我们可以写出如下的IsA的完善版:

Function InstanceOf(v As Variant, className As String) As Boolean
	If Not IsObject(v) Then
		InstanceOf=False
	Else
		Dim dt As Integer
		dt=DataType(v)
		If dt=V_LSOBJ Or dt=V_PRODOBJ Then
			If TypeName(v)=UCase(className) Then
				InstanceOf=True
			Else
				InstanceOf=False	
			End If
		Else
			If v IsA className Then
				InstanceOf=True
			Else
				InstanceOf=False 
			End If
		End If
	End If
End Function

相等性

程序中变量的相等性(equality)可分为值的相等(value equality)和引用的相等(reference equality)。单值只有必要判断值是否相等,两个3之间没有任何区别。复合值(数组这样的容器以及对象)要比较所有成员的值是否相等,不仅代价高,而且因为私有字段,往往是不可能的。解决方案有两种,一是干脆比较对象的引用即地址是否相等,也就是任意两个对象变量只有指向的是同一个对象实例时才被认为是相等的。另一种途径是像Java中的对象那样有必要时重载Object的equals方法,提供具体的判断相等性的标准。以Java为例,==运算符用在单值时,比较值是否相等;用在对象时,比较引用是否相等。

回到LotusScript,变量的数据类型同样分成几大类。=运算符用于计算单值的相等性,Is运算符用于计算对象的相等性。数组和列表则完全不能整体比较,用哪个运算符都不允许(Type mismatch)。那么当我们要比较两个能容纳各种数据类型的变体型时,怎么办?只有分各种情况单独处理:

Public Function Equals(v1 As Variant, v2 As Variant) As Boolean
	'Check data type
	Dim type1 As Integer, type2 As Integer
	'Type conversion for numericals, lists and arrays of variants	
	type1=DataType4Equals(v1)
	type2=DataType4Equals(v2)
	
	If type1><type2 Then
		Equals=False		
		Exit Function
	End If
	
	'Empty or Null
	If type1=V_EMPTY Or type1= V_NULL Then 
		Equals=True
		Exit Function 
	End If
	
	'Scalar
	If IsScalar(v1) Then
		If v1=v2 Then
			Equals=True
		Else
			Equals=False 			
		End If
		Exit Function 
	End If
	
	'Object
	If IsObject(v1) Then
		If v1 Is v2 Then
			Equals=True
		Else
			On Error ErrNamedMemberNonExist GoTo NotEquals
			If v1.IsEqualTo(v2) Then
				Equals=True
			Else
				Equals=False 				
			End If
		End If
		Exit Function 
	End If
	
	'Array
	If IsArray(v1) Then
		'Check dimension numbers and bounds
		If Not ArrayBoundsEquals(v1, v2) Then
			Equals=False
			Exit Function			
		End If
		'Change the arrays to one dimension
		Dim a1 As Variant, a2 As Variant
		a1=ArrayToOneDimension(v1)
		a2=ArrayToOneDimension(v2)
		Dim i As Integer
		For i=LBound(a1) To UBound(a1)
			If Not Equals(a1(i), a2(i)) Then
				Equals=False
				Exit Function				
			End If
		Next
		
		Equals=True 
		Exit Function		
	End If

	'List
	If IsList(v1) Then
		Dim tag As String		
		ForAll e In v1
			tag=ListTag(e)
			If Not IsElement(v2(tag)) Then
				Equals=False
				Exit Function
			ElseIf Not Equals(e, v2(tag)) Then
				Equals=False
				Exit Function				
			End If
		End ForAll
		
		Equals=True 
		Exit Function		
	End If
	
NotEquals:
	Equals=False
End Function

Private Function DataType4Equals(v As Variant) As Integer
	Dim result As Integer
	result=DataType(v) 
	Select Case result
		Case V_BYTE, V_INTEGER, V_LONG, V_SINGLE, V_DOUBLE, V_CURRENCY
			result=V_CURRENCY
		Case Is > 8704 'Dynamic array
			result=8704
		Case Is > 8192 'Fixed array
			result=8192
		Case Is > 2048 'List
			result=2048
	End Select 
	DataType4Equals=result
End Function

上面两个函数合在一起能比较任意两个变体型是否相等。对单值,比较值的相等性。对数组和列表,依次比较每一个元素的相等性。对对象,如果该类型的对象定义了IsEqualTo方法,则调用该方法,否则比较引用的相等性。Null、Empty的比较已被覆盖。不同精度的数值型之间的转换也已考虑。

你可能感兴趣的:(Lotus,notes,LotusScript,variant)