2

I am just getting started using classes in VBA and I'm following the "Inheritance by construction" method outlined here. My example uses a simple class that contains a value (as a variant) and a value type (as a string). I created a subclass in which value type is set to string in the constructor. Here is my code:

Interface Class (IVal)

'IVal interface class (from https://www.theartofquantfinance.com/inheritance-by-construction-in-vba/)
Option Explicit

'-----------------------------------
'Accessor methods for ValType
'-----------------------------------
Public Property Get ValType() As String
End Property
Public Property Let ValType(ByVal RHS As String)
End Property
'-----------------------------------
'Accessor methods for Val
'-----------------------------------
Public Property Get Val() As Variant
End Property
Public Property Let Val(ByVal RHS As Variant)
End Property

'Add other methods here as "Public Sub"
'i.e.
'Public Sub HonkHorn()
'End Sub

Base Class (CBaseVal)

'CBaseVal base class - implements IVal interface (from https://www.theartofquantfinance.com/inheritance-by-construction-in-vba/)
Option Explicit
Implements IVal

'------------------------------
'Private Fields
'------------------------------
Private valType_ As String
Private val_ As Variant

'------------------------------
'Constructors and destructors
'------------------------------
Private Sub Class_Initialization()
    valType_ = "Base Val"
    val_ = Nothing
End Sub
'------------------------------
'Class Properties And methods - public, visible to all modules
'These would be declared in the interface class
'------------------------------
Public Property Get ValType() As String
    ValType = valType_
End Property
Public Property Let ValType(ByVal RHS As String)
    valType_ = RHS
End Property

Public Property Get Val() As Variant
    Val = val_
End Property
Public Property Let Val(ByVal RHS As Variant)
    val_ = RHS
End Property

'Add additional class methods here
'e.g.
'Public Sub HonkHorn()
'   MsgBox prompt:="Beep!!!"
'End Sub

'------------------------------
'Interface property and method implementation - private, only visible to this module
'Delegate interface properties and methods to this (base) class using "Me"
'------------------------------
Private Property Let IVal_Val(ByVal RHS As Variant)
    Me.Val = RHS
End Property

Private Property Get IVal_Val() As Variant
    IVal_Val = Me.Val
End Property

Private Property Let IVal_ValType(ByVal RHS As String)
    Me.ValType = RHS
End Property

Private Property Get IVal_ValType() As String
    IVal_ValType = Me.ValType
End Property

'End Property
'Add additional IF methods here
'i.e.
'Private Sub ICar_HonkHorn()
'   Call Me.HonkHorn
'End Sub

Child Class (CStringVal)

'CStringVal class - implements IVal interface and delegates to CVal as appropriate (from https://www.theartofquantfinance.com/inheritance-by-construction-in-vba/)
Option Explicit
Implements IVal

'------------------------------
'Private Fields
'------------------------------
Private baseVal_ As IVal 'contains an instance of the IVal class (why isn't this "As CBaseVal"?)

'------------------------------
'Constructors and destructors
'------------------------------
Private Sub Class_Initialization()
    Set baseVal_ = New CBaseVal 'initialize private field of type "val class"
    baseVal_.ValType = "string"
    baseVal_.Val = Nothing
End Sub
'------------------------------
'Class Properties And methods (here's where you over-ride the base class implementation)
'These would be declared in the base class and the interface implementation below will delegate to either here or base class
'------------------------------
'e.g.
'Public Sub HonkHorn()
'   MsgBox prompt:="Beep!!!"
'End Sub


'------------------------------
'Interface property and method implementation - declared in base class and either:
'=> most delegate to base class
'=> some may override base class and delegate here
'------------------------------
Private Property Let IVal_Val(ByVal RHS As Variant)
    baseVal_.Val = RHS 'Delegate to base class
End Property

Private Property Get IVal_Val() As Variant
    IVal_Val = baseVal_.Val
End Property

Private Property Let IVal_ValType(ByVal RHS As String)
    baseVal_.Val = RHS 'Delegate to base class
End Property

Private Property Get IVal_ValType() As String
    IVal_ValType = baseVal_.ValType 'Delegate to base class
End Property

'Add additional interface methods here
'i.e.
'Private Sub ICar_HonkHorn()
'   Call Me.HonkHorn 'Overrides base class implementation, delegates to class method above
'End Sub

And here is the code I'm using to test it:

Public Sub testStringValClass()
    Dim interfaceClassVal As IVal
    Dim baseClassVal As CBaseVal
    Dim stringClassVal As CStringVal

    Set interfaceClassVal = New IVal
    Set baseClassVal = New CBaseVal
    Set stringClassVal = New CStringVal

    a = interfaceClassVal.ValType
    b = baseClassVal.ValType
    c = stringClassVal.ValType
End Sub

The first problem is a compile error that "Method or data member not found" for the line

c = stringClassVal.ValType

If I comment out that line, the code runs but using Watches it appears that valType_ is not being set, either as "Base Val" for the base class object or as "string" for the string class object. Additionally, there are "Object variable or With block variable not set" errors for the following properties:

stringClassVal.IVal_Val
stringClassVal.IVal_ValType

I'm kind of at a loss here... I assume it's something simple like a misspelling but I can't find it.

1 Answers1

1

That's simply because your class CStringValdoes not implement the ValType property. Ok, your implemented the property ValType of the interface IVal but not the explicit ValType of the class itself.

To access the interface's method, you need somehow to cast your object to the interface's type. For example:

Dim itf As IVal
Dim stringClassVal As New CStringVal
Set itf = stringClassVal '<-- casts the object to the interface's type
' and now:
c = itf.ValType

or you can use the defined name:

c = stringClassVal.IVal_ValType

But notice you did not initialize the fields of the variable yet (use the letters before the getters).

Yes, inheritance in VBA/VB6 is somehow tricky and not very natural. When a class implements an interface, the interface's methods are accessible through a reference to the interface, not to the implementor object, unless the latter explicitly redefines the method/property

Also notice that the initializer val_ = Nothing is useless and somehow wrong. An uninitialized Variant is created as an empty variant. Nothing basically is used for objects, not for basic variables (including strings).

Also as @TimWilliams said the constructor's name is Class_Initialize not Class_Initialization.

A.S.H
  • 29,101
  • 5
  • 23
  • 50
  • Let me ask a couple of follow-up questions: (1) What's the best way to ask follow-up questions? (2) Is it worth it the trouble to use classes, given that VBA is polymorphically challenged? – James Alesi Feb 15 '17 at 17:02
  • @JamesAlesi (1) you can ask follow-up questions here, naturally. (2) For this kind of tasks classes are not the way to go, and surely not interfaces. Classes are useful when you have complex enough objects that hold state and have many state-dependent actions. Interfaces are useful for type erasure, that is, when you have different things that do the same list of actions but each on its own way, and these different objects may reside in some container. Nothing of this is needed in your intended use case. Hope this clarifies :) – A.S.H Feb 15 '17 at 17:04
  • Therefore, no, classes are not a natural choice to program with VBA, although they may come occasionally useful. – A.S.H Feb 15 '17 at 17:06
  • 1
    I have posted a follow-up question here http://stackoverflow.com/questions/42263734/follow-up-to-vba-inheritance-via-construction-constructor-not-working so I can take advantage of code formatting. – James Alesi Feb 16 '17 at 02:29