错误438对象不支持此属性或方法-带字典的类对象

时间:2019-11-13 19:25:00

标签: arrays excel vba class dictionary

背景:

这是对this最近的问题的后续问题,我问过如何直接从Class项目返回Dictionary模块属性的数组。

我现在尝试使用Property LetProperty 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上,并告诉我该对象(字典)不支持此PropertyMethod。由于lst对象在dict.Add x, lst上似乎不是问题,所以这个问题对我来说似乎是狡猾的。实际上,像这样通过Item更改Key的方法似乎是非常common的做法。

问题:

而类似Dict.Add x, "Hello"然后Dict(x) = "Hello World"的东西似乎起作用。在第二种方法中使用Class对象时,代码出错。有谁知道为什么,如果可以的话,如何处理这个问题?

谢谢你, 合资企业

3 个答案:

答案 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在运行时才出现。