Finally, Parameterised Constructors are possible in VBA.
Background:
One rainy day, I came up with an idea: take a static class and expose one property as default (like it was done in the Reverse for each loop for the .Item() property. This time however, I thought I'll make a PieceOfMe()
Get Property the default which is capable of
taking no parameters aka. acting like a static class
optional parameters aka. Let property of VBA class modules - is it possible to have multiple arguments?
ParamArray * careful with this one though! You've got to trust yourself passing a ParamArray...
Pros
simple to implement and use
Parameterised Constructors ;)
Cons - as far as I am concern at this point...
Default member as well as
PieceOfMe()
doesn't support a full intelli-sense. It shows the expected parameters count but it doesn't tell what type etc. I think that's fixable though haven't had time yet to play with it.No way to hide
PieceOfMe()
from intelli-sense and Object BrowserAlways have to export your class (or write it in) to a text editor and add attributes etc.
Let's go!
Constructor.cls
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "Constructor"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Option Explicit
Private myName as String
Private Sub Class_Initialize()
myName = Cstr(Int((100) * rnd + 1))
End Sub
Public Property Get Name() as String
Name = myName
End Property
Public Property Get PieceOfMe(Optional value as String) As Constructor
Attribute Item.VB_UserMemId = 0
myName = value
Set PieceOfMe = Me
End Property
This class basically is the best to start off with:
it's made static by
VB_PredeclaredId = True
Private myName as String
is private because I wanted my encapsulation to only allow the default member ->PieceOfMe()
to assign a value via the parameter passed.Optional value as String
that's now the way to assign themyName
variableAttribute Item.VB_UserMemId = 0
makes it the default member
*By combining two concepts - making the class static and exposing a Get
Property as default I am allowed now to use syntax like: ...Constructor("strParam")...
...Get PieceOfMe(...) as Constructor
- the asConstructor
to have the property return an instance of theConstructor
- in other words: to return itself which isSet PieceOfMe = Me
Testing!
Copy-paste the above into a text editor and save it as Constructor.cls
. Start Excel, then VBE and import file -> Constructor.cls
. Also add a Module1
Module1
Option Explicit
' vba4all.com
Public Enum TestType
eStatic
eInstance
[_Min] = eStatic
[_Max] = eInstance
End Enum
Sub Main()
' Debug.Print Constructor
' *NOTE : fails because Constructor's default GET returns an instance of Constructor
' Constructor.Name = "newName"
' *NOTE2 : Explicit assignment of the .Name property not possible (removed to improve the class encapsulation)
' : To assign a new name you have to use the default property i.e Set T = T("New Name")
Dim testOption As TestType
Dim i As Long
' * I am only presenting 2 tests so the Enum thingy is a bit of an overkill
For i = TestType.[_Min] To TestType.[_Max]
testOption = i
' reset static defualt
Set Constructor = New Constructor
' Singleton pattern - as a VBA static class
If testOption = eStatic Then
Debug.Print "***STATIC"
' whenever calling this it will always return the defualt (random number)
Debug.Print Constructor.Name, "Default call to Constructor.Name"
' unless it has been changed by one of the following calls throughout the life of the Excel application
' *this call pretty much means that if you ever change the default this call will always reflect the last change
' ****
' *** SETTING THE DEFAULT OR AN INSTANCE BY CALLING THE CLASS NAME AND PASSING PARAMETERS
Set Constructor = Constructor("foo")
Debug.Print Constructor.Name, "Set Constructor = Constructor(""foo"") and debug call Constructor.Name"
' ** however, you can reset the Constructor.Name (ask for a new, default by calling
Set Constructor = New Constructor
Debug.Print Constructor.Name, "Set Constructor = new Constructor -> [reset] assigned a new random"
' Working with INSTANCES
Else
Debug.Print "***INSTANCES"
Debug.Print Constructor.Name, "Constructor.Name -> Current static default [reseted]"
' create an new variable of Constructor type
Dim T As Constructor
' Assing to the currently stored value
Set T = Constructor
Debug.Print T.Name, "Set T = Constructor -> Should equal the current default"
' Create a new Constructor instance ( grabs the new random number )
Set T = New Constructor
' or
'Dim T1 As New Constructor - same behaviour (grabs new random) but one line declaration
Debug.Print T.Name, "Set T = new Constructor - > new random for this instance"
' *NOTE - it's possible that this number is the same as previous because the Random Range is 100
' *** PARAMETARISED CONSTRUTOR
' new instance calling the static name and passing a parameter
' that changes the default as well as the instance
Dim T1 As Constructor
Set T1 = Constructor("fooInst")
Debug.Print T1.Name, "T1 = Constructor(""fooInst"") -> assigned through the static class"
Debug.Print Constructor.Name, "Constructor.Name -> static is now equal to what T1 is"
' another separate instance using NEW Constructor
Dim T2 As New Constructor
Debug.Print T2.Name, "Dim T2 as New Test = just to verify, a new instance via a NEW Constructor"
Debug.Print Constructor.Name, "Constructor.Name -> did NOT change, still equal to T1 due to the last assignment"
' changing instance via another instance
Set T1 = T2
Debug.Print T1.Name, "T1 result of: Set T1 = T2"
Debug.Print T2.Name, "T2 result of: Set T1 = T2 -> T1 and T2 are equal"
' changing instance via another instance + PARAMETER
Set T1 = T2("both")
Debug.Print T1.Name, "T1.Name -> due to: Set T1 = T2(""both"")"
Debug.Print T2.Name, "T2.Name -> due to: Set T1 = T2(""both"") - > T1 and T2 are now equal as T1 points to the T2"
Debug.Print Constructor.Name, "Constructor.Name -> did NOT change, still equal to itself because it hasn't been used by other instances"
End If
Next
End Sub
Immediate Window output in my case:
This could have been a lot shorter I know.... I wanted it to be longer because I have added a lot of comments to help you understand different scenarios. Feel free to set breakpoints and step-through if the output in the Immediate Window is not enough.
More (Optional) Parameters
That's quite easy just create a new file, name it MultipleParams.cls
and copy-paste the below example
MultipleParams.cls
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "MultipleParams"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Option Explicit
Private myName as String
Private myNumber as Long
Private Sub Class_Initialize()
myName = Cstr(Int((100) * rnd + 1))
End Sub
Public Property Get Name() as String
Name = myName
End Property
Public Property Get PieceOfMe(Optional newName as String, Optional theNumber as Long) As MultipleParams
Attribute PieceOfMe.VB_UserMemId = 0
myName = newName
myNumber = theNumber
Set PieceOfMe = Me
End Property
Test it
Sub Main()
Dim multiParams As MultipleParams
Set multiParams = MultipleParams("the Name", 1)
End Sub
ParamArray
ParramArrayConstructor.cls
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "ParamArrayConstructor"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Option Explicit
Private Sub Class_Initialize()
myName = Cstr(Int((100) * rnd + 1))
End Sub
Public Property Get Name() as String
Name = myName
End Property
Public Property Get PieceOfMe(paramArray arr() as Variant) As ParamArrayConstructor
Attribute PieceOfMe.VB_UserMemId = 0
' handle the paramArray
Dim v as Variant
For each v in arr
Debug.? v
Next
Set PieceOfMe = Me
End Property
Test it:
Sub Main()
Dim param As ParamArrayConstructor
Set param = ParamArrayConstructor("oh", "my", "lord", "paramarray", "works too!")
End Sub