Error 438 Object doesn't support this property

2020-04-20 09:31发布

问题:

Background:

This is a follow-up question on this recent question I asked on how to return an array of Class module properties directly from an Dictionary item.

I have now tried my way to work with Property Let and Property Get to populate a Private Array to populate the Dictionary with. However, running some test I encountered an Error 438.

Code:

Imagine TstClass as a class module with the following code:

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

Then this code to test in a Module:

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

Problem:

The error 438 will occur on dict(keyx) = lst and tells me that the object (a Dictionary) doesn't support this Property or Method. The problem seems dodgy to me as the lst Object didn't seem to be a problem on dict.Add x, lst. As a matter of fact, the method to change an Item through its Key like this seems to be a very common practice.

Question:

Whereas something like Dict.Add x, "Hello" and then Dict(x) = "Hello World" seems to work. The code errors out on using a Class object in the second method. Does anybody know why and if so, how to deal with this problem?

Thank you, JvdV

回答1:

Set dict(keyx) = lst 

Since the variable lst refers to an object, Set is required here.



回答2:

The OP code has quite a few typos, undeclared variables and usable but not 'correct' property Let/Get. This is how I would write the code of the op. Having now been well trained by the code inspections of the fantastic RubberDuck addin I no longer use 'default' properties but ensure that I use the fully qualified name.

Class code

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

' Typically VBA uses the plural of the 'item name' when returning the array.

Public Function Items() As Variant
    Items = p.List
End Function

Module code

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


回答3:

As Tim correctly points out, Set is required here, because lst is an object.

What isn't clear, is why a missing Set keyword would cause run-time error 438, an error that's typically encountered with buggy late-bound code. The reason is, in a word, let-coercion: in the absence of a Set keyword, an assignment is an implicit Let (value) assignment.

If Class1 had a default member, dict(keyx) = lst would be let-coercing the lst object and storing the value returned by that default member in the dictionary.

Since Class1 has no default member (and the reference isn't Nothing), error 438 is raised (would be error 91 if lst was Nothing), because VBA is looking to invoke the member with a VB_UserMemId=0 attribute, and cannot find it.

The error 438 will occur on dict(keyx) = lst and tells me that the object (a Dictionary) doesn't support this Property or Method.

The object isn't the dictionary here, but your Class1 instance; the missing "property or method" is that object's default member, implicitly invoked through let-coercion. And since the member call is implicit, it's easily missed, and the error number/message is easily confusing.

Rubberduck (free, open-source VBIDE add-in project that I manage) fires an inspection result for Value Required here, that explains exactly what's going on:

In a context that requires a value type, the expression 'lst' of object type 'VBAProject.Class1' is used that does not have a suitable default member.

Object used where a value is required

The VBA compiler does not raise an error if an object is used in a place that requires a value type and the object's declared type does not have a suitable default member. Under almost all circumstances, this leads to a run-time error 91 'Object or With block variable not set' or 438 'Object doesn't support this property or method' depending on whether the object has the value 'Nothing' or not, which is harder to detect and indicates a bug.

In other words, that's one of many places where the VBA compiler defers to run-time instead of warning of something fishy at compile-time. Static code analysis is therefore the only way to reliably detect such bugs before they show up at run-time.