背景:
这是对this最近的问题的后续问题,我问过如何直接从Class
项目返回Dictionary
模块属性的数组。
我现在尝试使用Property Let
和Property Get
来填充Private Array
来填充Dictionary
的方法。但是,在运行一些测试时,我遇到了Error 438
。
代码:
将TstClass
想象成一个具有以下代码的类模块:
Private lst(0 To 2) As Variant
Public Property Let Add(ByVal i As Long, ByVal NewVal As Variant)
lst(i) = NewVal
End Property
Public Property Get Val(ByVal i As Long) As Variant
Val = lst(i)
End Property
Public Function GetArray() As Variant
GetArray = vals
End Function
然后此代码在模块中进行测试:
Sub Test()
Dim x As Long, arr As Variant, lst As Class1
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
For x = 1 To 3
Set lst = New Class1
lst.Add(0) = x
lst.Add(1) = x
lst.Add(2) = x
dict.Add x, lst
Next x
For x = 4 To 3 Step -1
If dict.Exists(x) = False Then
Set lst = New Class1
lst.Add(0) = x
lst.Add(1) = x
lst.Add(2) = x
dict.Add x, lst
Else
Set lst = dict(x)
lst.Add(1) = lst.Val(1) + 2
lst.Add(2) = lst.Val(2) + 2
dict(x) = lst '< Error 438 on this line
End If
Next x
For Each key In dict.keys
arr = dict(keys).GetArray
Next key
End Sub
问题:
错误438将发生在dict(keyx) = lst
上,并告诉我该对象(字典)不支持此Property
或Method
。由于lst
对象在dict.Add x, lst
上似乎不是问题,所以这个问题对我来说似乎是狡猾的。实际上,像这样通过Item
更改Key
的方法似乎是非常common的做法。
问题:
而类似Dict.Add x, "Hello"
然后Dict(x) = "Hello World"
的东西似乎起作用。在第二种方法中使用Class
对象时,代码出错。有谁知道为什么,如果可以的话,如何处理这个问题?
谢谢你, 合资企业
答案 0 :(得分:4)
Set dict(keyx) = lst
由于变量lst
引用了一个对象,因此这里需要Set
。
答案 1 :(得分:4)
OP代码有很多错别字,未声明的变量以及可用但不正确的属性Let / Get。这就是我编写操作代码的方式。现在,经过出色的RubberDuck插件代码检查的良好培训,我不再使用“默认”属性,而是确保使用完全限定的名称。
类代码
Option Explicit
Private Type Properties
List As Variant
End Type
Private p As Properties
Public Sub Class_Initialize()
ReDim p.List(2)
End Sub
Public Property Let Item(ByVal i As Long, ByVal NewVal As Variant)
p.List(i) = NewVal
End Property
Public Property Get Item(ByVal i As Long) As Variant
Item = p.List(i)
End Property
'通常,VBA在返回数组时使用“项目名称”的复数形式。
Public Function Items() As Variant
Items = p.List
End Function
模块代码
Sub Test()
Dim x As Long, arr As Variant, lst As Class1
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
For x = 1 To 3
Set lst = New Class1
lst.Item(0) = x
lst.Item(1) = x
lst.Item(2) = x
dict.Add x, lst
Next x
For x = 4 To 3 Step -1
If dict.Exists(x) = False Then
Set lst = New Class1
lst.Item(0) = x
lst.Item(1) = x
lst.Item(2) = x
dict.Add x, lst
Else
With dict.Item(x)
.Item(1) = lst.Item(1) + 2
.Item(2) = lst.Item(2) + 2
End With
End If
Next x
Dim myKey As Variant
For Each myKey In dict.Keys
arr = dict.Item(myKey).GetArray
Next Key
End Sub
答案 2 :(得分:2)
As Tim correctly points out,此处Set
是必需的,因为lst
是一个对象。
尚不清楚的是为什么缺少Set
关键字会导致运行时错误438,该错误通常是在有问题的后期绑定代码中遇到的。总而言之,原因是 let-coercion :在没有Set
关键字的情况下,分配是隐式的Let
(值)分配。
如果Class1
具有默认成员,则dict(keyx) = lst
将强制lst
对象并将该默认成员返回的值存储在字典中
由于Class1
没有默认成员(并且引用不是Nothing
),因此引发错误438(如果lst
被设置为错误91 Nothing
),因为VBA希望使用VB_UserMemId=0
属性调用该成员,但找不到它。
错误438将在
dict(keyx) = lst
上发生,并告诉我对象(Dictionary
)不支持此属性或方法。
该对象不是这里的字典,而是您的Class1
实例;缺少的“属性或方法”是该对象的默认成员,该成员通过let-coercion隐式调用。而且由于成员调用是隐式的,因此很容易错过,并且错误编号/消息也很容易混淆。
Rubberduck(我管理的免费,开源VBIDE外接程序项目)在此处触发Value Required的检查结果,该检查结果准确地说明了发生了什么:
在需要值类型的上下文中,使用对象类型'VBAProject.Class1'的表达式'lst',但没有合适的默认成员。
在需要值的地方使用的对象
如果在需要值类型的地方使用对象并且该对象的声明类型没有合适的默认成员,则VBA编译器不会引发错误。在几乎所有情况下,这都会导致运行时错误91“对象或未设置块变量”或438“对象不支持此属性或方法”,具体取决于对象是否具有值“ Nothing”,很难检测到并表明存在错误。
换句话说,这是VBA编译器推迟运行时而不是在编译时发出警告的地方之一。因此,静态代码分析是可靠地检测出此类bug的唯一方法,然后这些bug在运行时才出现。