Implementation inheritance in VBA? - vba

I have an interface IValidator, and I want classes that implement IValidator to have access to a shared set of properties and methods, but it seems VBA doesn't have true implementation inheritance, so instead I have a class BaseValidator with the shared resources, which is used as a delegate in the subclasses.
Interface:
'IValidator
Public Function isValid(columnName As String) As Boolean
End Function
'used for initializing delegate
Public Sub setup(fieldsDict As Dictionary)
End Sub
Base class:
'BaseValidator
Public fieldsDict As Dictionary
Public Sub setup(fieldsDict As Dictionary)
Set Me.fieldsDict = fieldsDict
End Sub
Public Function doSomethingWithFieldsDict(columnName as string) as variant
'do something
End Function
Example implementation:
'Validator_FeatureNumber
Private bv As New BaseValidator
Implements IValidator
Public Sub IValidator_setup(fieldsDict As Dictionary)
bv.setup fieldsDict
End Sub
Function IValidator_isValid(columnName As String) As Boolean
IValidator_isValid = IsNumeric(bv.doSomethingWithFieldsDict(columnName))
End Function
This works, but it means I have to duplicate the IValidator_setup() code block in every implementation, which seems like a bad idea for code maintainability. Is there any way to have a subclass inherit methods from a superclass in VBA?

The short answer is no.
Welcome to the the fun and exiting world of 1991:) VBA is a subset of VB6. Inheritance was not supported at that time, and because Microsoft based VBA on VB6 and then abandoned* it when they went to .Net, that means it likely never will be:(
*They did update it somewhat to cope w/64 bit API calls, but that was pretty much it.

Related

VB.NET Class with COM Interop, missing most properties when used from VBA (Access)

I have written a VB.NET class that has COM Interop enabled so it can be utilized in VBA - specifically, MS Access.
The class works fine in VB.NET.
With Accees, I can add the reference to it, instantiate the main object and set and return some properties.
But Access does not recognize anything relating to the sub-classes underneath the main class. VB.NET has no problem exposing these classes, but not VBA.
Is this simply a limitation of COM Interop and/or VBA?
Is there a work-around?
No you can’t get interop to generate the sub classes for you (to appear in VBA)
However, keep in mind that nested classes are really the same as non-nested. That sub class instance HAS to be initialized anyway. And there is nothing you can't do if the classes were to be separated. And you can well place many classes in one code module.
So this is purely a syntax preference you are looking for.
However, what you can do declare a pubic instance of any sub class in the main class (variables area as public).
Take this simple example.
Imports System.Runtime.InteropServices
<ClassInterface(ClassInterfaceType.AutoDual)>
Public Class Class1
Private m_Company As String = ""
Public Function MyHello()
MsgBox("Hello world")
End Function
Public Property Company As String
Get
Return m_Company
End Get
Set(value As String)
m_Company = value
End Set
End Property
<ClassInterface(ClassInterfaceType.AutoDual)>
Public Class Class2
Private m_FirstValue As Integer = 2
Public Property V1 As Integer
Get
Return m_FirstValue
End Get
Set(value As Integer)
m_FirstValue = value
End Set
End Property
Public Function MyTimes2() As Integer
Return m_FirstValue * 2
End Function
End Class
End Class
NOTE above the nested class “class2” in above.
Ok, so check the make com assembly visible = True, and for testing check the “register for com interop”
Compile the above, set the reference in Access. (Note how you don’t have to build a custom interface either!!!).
Now, in VBA you get this in intel-sense.
NOTE carefully how the sub class Class2 does not appear.
If you really want the intel-sense and sub class to appear, then to the above vb.net class, simple add this;
Public Class Class1
Private m_Company As String = ""
Public SClass2 As New Class2 <--- add this line to expose as public
Private m_Company As String = ""
.etc. etc. etc.
Now I put a “S” in from of the name – you unfortunately can’t use the same name as the nested class. (so either put something in front of the nested class, or something in front of the public instance of that class (that is what I did in above).
Now if we compile, then in VBA you get this:
Note the class2 DOES appear as a sub class
And if I hit a “dot” in VBA editor, then the sub class methods show like this:
So quite sure the above is the only way to get the sub-classes working with COM interop

Can we use Interfaces and Events together at the same time?

I'm still trying to wrap my head around how Interfaces and Events work together (if at all?) in VBA. I'm about to build a large application in Microsoft Access, and I want to make it as flexible and extendable as possible. To do this, I want to make use of MVC, Interfaces (2) (3) , Custom Collection Classes, Raising Events Using Custom Collection Classes, finding better ways to centralize and manage the events triggered by the controls on a form, and some additional VBA design patterns.
I anticipate that this project is going to get pretty hairy so I want to try to grok the limits and benefits of using interfaces and events together in VBA since they are the two main ways (I think) to really implement loose-coupling in VBA.
To start with, there is this question about an error raised when trying to use interfaces and events together in VBA. The answer states "Apparently Events are not allowed to be passed through an interface class into the concrete class like you want to using 'Implements'."
Then I found this statement in an answer on another forum: "In VBA6 we can only raise events declared in a class's default interface - we can't raise events declared in an Implemented interface."
Since I'm still groking interfaces and events (VBA is the first language I've really had a chance to try out OOP in a real-world setting, I know shudder), I can't quite work through in my mind what all this means for using events and interfaces together in VBA. It kinda sounds like you can use them both at the same time, and it kinda sounds like you can't. (For instance, I'm not sure what is meant above by "a class's default interface" vs "an Implemented interface.")
Can someone give me some basic examples of the real benefits and limitations of using Interfaces and Events together in VBA?
This is a perfect use-case for an Adapter: internally adapting the semantics for a set of contracts (interfaces) and exposing them as its own external API; possibly according to some other contract.
Define class modules IViewEvents:
Option Compare Database
Option Explicit
Private Const mModuleName As String = "IViewEvents"
Public Sub OnBeforeDoSomething(ByVal Data As Object, ByRef Cancel As Boolean): End Sub
Public Sub OnAfterDoSomething(ByVal Data As Object): End Sub
Private Sub Class_Initialize()
Err.Raise 5, mModuleName, AccessError(5) & "-Interface class must not be instantiated."
End Sub
IViewCommands:
Option Compare Database
Option Explicit
Private Const mModuleName As String = "IViewCommands"
Public Sub DoSomething(ByVal arg1 As String, ByVal arg2 As Long): End Sub
Private Sub Class_Initialize()
Err.Raise 5, mModuleName, AccessError(5) & "-Interface class must not be instantiated."
End Sub
ViewAdapter:
Option Compare Database
Option Explicit
Private Const mModuleName As String = "ViewAdapter"
Public Event BeforeDoSomething(ByVal Data As Object, ByRef Cancel As Boolean)
Public Event AfterDoSomething(ByVal Data As Object)
Private mView As IViewCommands
Implements IViewCommands
Implements IViewEvents
Public Function Initialize(View As IViewCommands) As ViewAdapter
Set mView = View
Set Initialize = Me
End Function
Private Sub IViewCommands_DoSomething(ByVal arg1 As String, ByVal arg2 As Long)
mView.DoSomething arg1, arg2
End Sub
Private Sub IViewEvents_OnBeforeDoSomething(ByVal Data As Object, ByRef Cancel As Boolean)
RaiseEvent BeforeDoSomething(Data, Cancel)
End Sub
Private Sub IViewEvents_OnAfterDoSomething(ByVal Data As Object)
RaiseEvent AfterDoSomething(Data)
End Sub
and Controller:
Option Compare Database
Option Explicit
Private Const mModuleName As String = "Controller"
Private WithEvents mViewAdapter As ViewAdapter
Private mData As Object
Public Function Initialize(ViewAdapter As ViewAdapter) As Controller
Set mViewAdapter = ViewAdapter
Set Initialize = Me
End Function
Private Sub mViewAdapter_AfterDoSomething(ByVal Data As Object)
' Do stuff
End Sub
Private Sub mViewAdapter_BeforeDoSomething(ByVal Data As Object, ByRef Cancel As Boolean)
Cancel = Data Is Nothing
End Sub
plus Standard Modules Constructors:
Option Compare Database
Option Explicit
Option Private Module
Private Const mModuleName As String = "Constructors"
Public Function NewViewAdapter(View As IViewCommands) As ViewAdapter
With New ViewAdapter: Set NewViewAdapter = .Initialize(View): End With
End Function
Public Function NewController(ByVal ViewAdapter As ViewAdapter) As Controller
With New Controller: Set NewController = .Initialize(ViewAdapter): End With
End Function
and MyApplication:
Option Compare Database
Option Explicit
Private Const mModuleName As String = "MyApplication"
Private mController As Controller
Public Function LaunchApp() As Long
Dim frm As IViewCommands
' Open and assign frm here as instance of a Form implementing
' IViewCommands and raising events through the callback interface
' IViewEvents. It requires an initialization method (or property
' setter) that accepts an IViewEvents argument.
Set mController = NewController(NewViewAdapter(frm))
End Function
Note how use of the Adapter Pattern combined with programming to interfaces results in a very flexible structure, where different Controller or View implementations can be substituted in at run time. Each Controller definition (in the case of different implementations being required) uses different instances of the same ViewAdapter implementation, as Dependency Injection is being used to delegate the event-source and command-sink for each instance at run time.
The same pattern can be repeated to define the relationship between the Controller/Presenter/ViewModel and the Model, though implementing MVVM in COM can get rather tedious. I have found MVP or MVC is usually better suited for COM-based applications.
A production implementation would also add proper error handling (at a minimum) to the extent supported by VBA, which I have only hinted at with the definition of the mModuleName constant in each module.
An interface is, strictly speaking and only in OOP terms, what an object exposes to the outside world (i.e. its callers/"clients").
So you can define an interface in a class module, say ISomething:
Option Explicit
Public Sub DoSomething()
End Sub
In another class module, say Class1, you can implement the ISomething interface:
Option Explicit
Implements ISomething
Private Sub ISomething_DoSomething()
'the actual implementation
End Sub
When you do exactly that, notice how Class1 doesn't expose anything; the only way to access its DoSomething method is through the ISomething interface, so the calling code would look like this:
Dim something As ISomething
Set something = New Class1
something.DoSomething
So ISomething is the interface here, and the code that actually runs is implemented in the body of Class1. This is one of the fundamental pillars of OOP: polymorphism - because you could very well have a Class2 that implements ISomething in a wildly different way, yet the caller wouldn't ever need to care at all: the implementation is abstracted behind an interface - and that's a beautiful and refreshing thing to see in VBA code!
There are a number of things to keep in mind though:
Fields are normally considered as implementation details: if an interface exposes public fields, implementing classes must implement a Property Get and a Property Let (or Set, depending on the type) for it.
Events are considered implementation details, too. Therefore they need to be implemented in the class that Implements the interface, not the interface itself.
That last point is rather annoying. Given Class1 that looks like this:
'#Folder StackOverflowDemo
Public Foo As String
Public Event BeforeDoSomething()
Public Event AfterDoSomething()
Public Sub DoSomething()
End Sub
The implementing class would look like this:
'#Folder StackOverflowDemo
Implements Class1
Private Sub Class1_DoSomething()
'method implementation
End Sub
Private Property Let Class1_Foo(ByVal RHS As String)
'field setter implementation
End Property
Private Property Get Class1_Foo() As String
'field getter implementation
End Property
If it's any easier to visualize, the project looks like this:
So Class1 might define events, but the implementing class has no way of implementing them - that's one sad thing about events and interfaces in VBA, and it stems from the way events work in COM - events themselves are defined in their own "event provider" interface; so a "class interface" can't expose events in COM (as far as I understand it), and therefore in VBA.
So the events must be defined on the implementing class to make any sense:
'#Folder StackOverflowDemo
Implements Class1
Public Event BeforeDoSomething()
Public Event AfterDoSomething()
Private foo As String
Private Sub Class1_DoSomething()
RaiseEvent BeforeDoSomething
'do something
RaiseEvent AfterDoSomething
End Sub
Private Property Let Class1_Foo(ByVal RHS As String)
foo = RHS
End Property
Private Property Get Class1_Foo() As String
Class1_Foo = foo
End Property
If you want to handle the events Class2 raises while running code that implements the Class1 interface, you need a module-level WithEvents field of type Class2 (the implementation), and a procedure-level object variable of type Class1 (the interface):
'#Folder StackOverflowDemo
Option Explicit
Private WithEvents SomeClass2 As Class2 ' Class2 is a "concrete" implementation
Public Sub Test(ByVal implementation As Class1) 'Class1 is the interface
Set SomeClass2 = implementation ' will not work if the "real type" isn't Class2
foo.DoSomething ' runs whichever implementation of the Class1 interface was supplied
End Sub
Private Sub SomeClass2_AfterDoSomething()
'handle AfterDoSomething event of Class2 implementation
End Sub
Private Sub SomeClass2_BeforeDoSomething()
'handle BeforeDoSomething event of Class2 implementation
End Sub
And so we have Class1 as the interface, Class2 as the implementation, and Class3 as some client code:
...which arguably defeats the purpose of polymorphism, since that class is now coupled with a specific implementation - but then, that's what VBA events do: they are implementation details, inherently coupled with a specific implementation... as far as I know.
Because bounty is already headed for Pieter's answer I'll not attempt to answer the MVC aspect of the question but instead the headline question. The answer is Events have limits.
It would be harsh to call them "syntactic sugar" because they save a lot of code but at some point if your design gets too complex then you have to bust out and manually implement the functionality.
But first, a callback mechanism (for that is what events are)
modMain, the entry/starting point
Option Explicit
Sub Main()
Dim oClient As Client
Set oClient = New Client
oClient.Run
End Sub
Client
Option Explicit
Implements IEventListener
Private Sub IEventListener_SomethingHappened(ByVal vSomeParam As Variant)
Debug.Print "IEventListener_SomethingHappened " & vSomeParam
End Sub
Public Sub Run()
Dim oEventEmitter As EventEmitter
Set oEventEmitter = New EventEmitter
oEventEmitter.ServerDoWork Me
End Sub
IEventListener, the interface contract that describes the events
Option Explicit
Public Sub SomethingHappened(ByVal vSomeParam As Variant)
End Sub
EventEmitter, the server class
Option Explicit
Public Sub ServerDoWork(ByVal itfCallback As IEventListener)
Dim lLoop As Long
For lLoop = 1 To 3
Application.Wait Now() + CDate("00:00:01")
itfCallback.SomethingHappened lLoop
Next
End Sub
So how does WithEvents work? One answer is to look in the type library, here is some IDL from Access (Microsoft Access 15.0 Object Library) defining the events to be raised.
[
uuid(0EA530DD-5B30-4278-BD28-47C4D11619BD),
hidden,
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "Microsoft.Office.Interop.Access._FormEvents")
]
dispinterface _FormEvents2 {
properties:
methods:
[id(0x00000813), helpcontext(0x00003541)]
void Load();
[id(0x0000080a), helpcontext(0x00003542)]
void Current();
'/* omitted lots of other events for brevity */
};
Also from Access IDL here is the class detailing what its main interface is and what is event interface is, look for source keyword, and VBA needs a dispinterface so ignore one of them.
[
uuid(7398AAFD-6527-48C7-95B7-BEABACD1CA3F),
helpcontext(0x00003576)
]
coclass Form {
[default] interface _Form3;
[source] interface _FormEvents;
[default, source] dispinterface _FormEvents2;
};
So what that is saying to a client is that operate me via the _Form3 interface but if you want to receive events then you, the client, must implement _FormEvents2. And believe it or not VBA will when WithEvents is met spin up an object that implements the source interface for you and then route incoming calls to your VBA handler code. Pretty amazing actually.
So VBA generates a class/object implementing the source interface for you but questioner has met the limits with the interface polymorphism mechanism and events. So my advice is to abandon WithEvents and implement you own callback interface and this is what the given code above does.
For more information then I recommend reading a C++ book that implements events using the connection point interfaces, your google search terms are connection points withevents
Here is a good quote from 1994 highlighting the work VBA does I mentioned above
After slogging through the preceding CSink code, you'll find that intercepting events in Visual Basic is almost dishearteningly easy. You simply use the WithEvents keyword when you declare an object variable, and Visual Basic dynamically creates a sink object that implements the source interface supported by the connectable object. Then you instantiate the object using the Visual Basic New keyword. Now, whenever the connectable object calls methods of the source interface, Visual Basic's sink object checks to see whether you have written any code to handle the call.
EDIT: Actually, mulling my example code you could simplify and abolish the intermediate interface class if you do not want to replicate the way COM does things and you are not bothered by coupling. It is after all just a glorified callback mechanism. I think this is an example of why COM got a reputation for being overly complicated.
Implemented Class
' clsHUMAN
Public Property Let FirstName(strFirstName As String)
End Property
Derived Class
' clsEmployee
Implements clsHUMAN
Event evtNameChange()
Private Property Let clsHUMAN_FirstName(RHS As String)
UpdateHRDatabase
RaiseEvent evtNameChange
End Property
Using in form
Private WithEvents Employee As clsEmployee
Private Sub Employee_evtNameChange()
Me.cmdSave.Enabled = True
End Sub

How do I attach to different classes with the same events at runtime?

Given two classes with identical events I'd like to be able to refer to them using one name. Similar to the following:
Public Class myclass1
Public event1()
End Class
Public Class myclass2
Public event1()
End Class
Here I'd like to be able to determine at run time which class to use:
Sub somefunction(select as integer)
Dim voidclass
if select = 1 then
voidclass = myclass1
else
voidclass = myclass2
end if
AddHandler voidclass.event1, AddressOf eventhappened
End Sub
Sub eventhappened()
msgbox("Event occured")
End Sub
Obviously there may be a better method for this example, but let's assume that class book1 already exists, and that I'm tasked with creating book2 and only modifying somefunction without modifying book1 itself.
The example above should result in the error event1 is not an event of 'Object'`.
It appears that properties and methods are fine with this, but events are not. How do I handle events in this situation?
The snippet does not match the question very well. It has an odd bug, the events in the classes are not declared Shared so the code must use a proper object reference. Not a type name. Maybe the answer is as simple as:
Private obj1 As myclass1
Private obj2 As myclass2
Sub somefunction(select as integer)
If select = 1 Then
AddHandler obj1.event1, AddressOf eventhappened
Else
AddHandler obj2.event1, AddressOf eventhappened
End If
End Sub
But presumably the real question is the same scenario, but now the variables declared as:
Private obj1, obj2
Untyped and thus Object. Yes, VB.NET does not support that. As with most quirks in VB.NET there is history behind this. VB never did support explicit late binding of events, only methods and properties. The scheme in the legacy versions was very quirky. You had to declare the variable with the WithEvents keyword and pick a specific name for the event handler. In other words:
Dim WithEvents obj1
Sub obj1_somethinghappened
'' handles the event1 event for obj1
End Sub
Or to put it another way, all events were late-bound. This scheme was not carried forward into VB.NET, too many practical problems with it. WithEvents still survives but now requires the Handles keyword on the method declaration. That doesn't do what you want it to do.
The VB.NET designers intentionally did not add late binding support to the AddHandler statement. I am not privy to the exact reason they decided this and can only guess. There is no technical reason it could not be added, it just requires the compiler to generate reflection code. One possible explanation is that they considered it too expensive. Another is that they deemed the runtime exceptions that are raised when there's a method signature mismatch too hard to interpret. I like that last one best, they are awfully ugly.
You'll have to use Reflection to get what you want. Use obj1.GetType().GetEvent("event1") to get the EventInfo, its Get/AddMethod() to add the event handler.
Fwiw, the C# language does support this in its dynamic keyword implementation. Maybe you can put a bug in their ear by asking for the feature. No real idea if this was requested before.
You can define an interface for the event and implement it in the classes then you can subscribe to this event through the interface reference. See below:
Public Interface INotifier
Event SomethingHappened()
End Interface
Public Class Class1
Implements INotifier
Public Event SomethingHappened() Implements INotifier.SomethingHappened
End Class
Public Class Class2
Implements INotifier
Public Event SomethingHappened() Implements INotifier.SomethingHappened
End Class
Module Module1
Dim notifiers As List(Of INotifier) = New List(Of INotifier) From
{
New Class1(),
New Class2()
}
Sub Main()
SubscribeToEventHandler(0)
End Sub
Private Sub SubscribeToEventHandler(ByVal index As Integer)
Dim notifier As INotifier = notifiers(index)
AddHandler notifier.SomethingHappened, AddressOf EventHandler
End Sub
Private Sub EventHandler()
End Sub
End Module

How can I use multiple class types in the same place

Say I have a function that does the following in Vb.net
For i as decimal = 0 to mstrItems.count - 1
mstrItems.item(i).activate
next
And I have classes as such that both classes, though different object type, have the function activate.
I would like to iterate my Items as above, having the list include any type of object, not just one specific type, and have the different objects all perform the activate function in the same way, though they are different object types.
Cheers!
Martin
Alternatively, you could define an interface and implement it in all your different Types:
Interface IActivateable
Sub Activate
End Interface
Class MyType1: Implements IActivateable
Sub Activate Implements IActivateable.Activate
'some implementation details here ...
End Sub
End Class
Class MyType2: Implements IActivateable
Sub Activate Implements IActivateable.Activate
'some implementation details here ...
End Sub
End Class
Then you can cast each type as the interface you want to handle:
Dim myList as new list(of IActivateable)
myList.add(new MyType1)
myList.add(new MyType2)
for each obj as IActivateable in myList
obj.Activate
end for
To give you some searchable terminology, what you're looking to do is use polymorphism to make use of the Strategy Pattern.
At its simplest, what you want is to have a base class (possibly abstract, I don't know the VB terminology for these things though) from which your multiple classes inherit. It's a kind of high-level abstraction which represents the common functionality between those classes. Something like this:
Class Car
Overridable Sub Drive()
Throw New NotImplementedException
End Sub()
End Class
Class GasPoweredCar
Inherits Car
Overrides Sub Drive()
' logic for driving a gas-powered car
End Sub
End Class
Class SolarPoweredCar
Inherits Car
Overrides Sub Drive()
' logic for driving a solar-powered car
End Sub
End Class
And so on. The idea is that there's one common thing you're trying to accomplish (Drive in this case) but multiple different implementations would accomplish it in different ways. But since all of those implementations are still a Car then you can treat them as such (which is where polymorphism comes in).
So instead of having a list of GasPoweredCar and a list of SolarPoweredCar you can have a single combined list of Car which contains both. You don't need to know what kind of car it is in order to simply invoke the Drive function.
For i As decimal = 0 To listOfCars.count - 1
listOfCars.Item(i).Drive
Next
Or more simply:
For Each car As Car In listOfCars
car.Drive
Next
The alternative to Davids excellent answer is using Interfaces.
The GasPoweredCar and SolarPoweredCar classes could implement an interface:
interface ICar { void Drive(); }.
Both classes would have their own internal implementation of the Drive method. Then when iterating over Gas or Solar cars you could cast the class to the interface and call the method.
If David's isn't ideal I am Happy to elaborate, just let me know.
An alternative to polymorphism is to use an Interface:
Module Module1
Sub Main()
Dim lstClass As New List(Of IMyInterface)
lstClass.Add(New FirstClass("A"))
lstClass.Add(New SecondClass("B"))
lstClass.Add(New FirstClass("C"))
lstClass.Add(New SecondClass("D"))
For i As Integer = 0 To lstClass.Count - 1
lstClass(i).Activate()
Next i
End Sub
Interface IMyInterface
Sub Activate()
End Interface
Class FirstClass
Implements IMyInterface
Public Property MyProperty As String
Sub New(s As String)
MyProperty = s
End Sub
Sub Activate() Implements IMyInterface.Activate
MsgBox("First class activate: " & MyProperty)
End Sub
End Class
Class SecondClass
Implements IMyInterface
Public Property MyProperty As String
Sub New(s As String)
MyProperty = s
End Sub
Sub Activate() Implements IMyInterface.Activate
MsgBox("Second class activate: " & MyProperty)
End Sub
End Class
End Module

Order of initialisation

I'm playing with the following:
Public MustInherit Class TempTable
Public Sub New()
For Each f As FieldInfo In Me.GetType().GetFields
Dim l As TypedLeaf = CType(f.GetValue(Me), TypedLeaf)
Console.WriteLine(l.Name)
Next
End Sub
End Class
Public Class JMTempTable
Inherits TempTable
Public KeyIndex As New TypedLeaf(Me, "KeyIndex", OQL.Type.AUTONUMBER)
Public Debit As New TypedLeaf(Me, "Debit", OQL.Type.DECIMAL(16, 2))
Public Sub New()
MyBase.New()
End Sub
End Class
but getting Nothing for the values retrieved. The reason seems to be that the derived class' fields do not get initialised until after the base class' constructor is called... to further complicate matters, if I were to do:
Public Class JMTempTable
Inherits TempTable
Public KeyIndex As TypedLeaf
Public Debit As TypedLeaf
Public Sub New()
KeyIndex = New TypedLeaf(Me, "KeyIndex", OQL.Type.AUTONUMBER)
Debit = New TypedLeaf(Me, "Debit", OQL.Type.DECIMAL(16, 2))
MyBase.New()
End Sub
End Class
The compiler will complain that the base class constructor must be called in the first line of the derived class' constructor...
Is there a way I can delay the base class constructor from running until after the derived class' fields have been initialised?
Here's one way (perhaps the way) to do it:
Public MustInherit Class TempTable
Public Sub New()
Initialize()
For Each f As FieldInfo In Me.GetType().GetFields
Dim l As TypedLeaf = CType(f.GetValue(Me), TypedLeaf)
Console.WriteLine(l.Name)
Next
End Sub
Protected MustOverride Sub Initialize()
End Class
Public Class JMTempTable
Inherits TempTable
Public KeyIndex As TypedLeaf()
Public Debit As TypedLeaf()
Public Sub New() ' Optional block. You don't have to explicitly define a default constructor.
MyBase.New()
End Sub
Protected Overrides Sub Initialize()
KeyIndex = New TypedLeaf(Me, "KeyIndex", OQL.Type.AUTONUMBER)
Debit = New TypedLeaf(Me, "Debit", OQL.Type.DECIMAL(16, 2))
End Sub
End Class
The abstract Initialize() method forces inheritors to have a method called Initialize(). This method is implicitly called when you call MyBase.New(). That means you can now move your initialization logic out of the constructor and into the Initialize() method to get the effect you're looking for.
This is well known behavior in managed languages in general. Surprisingly I can't find it explicitly mentioned in the VB.NET Language Specification so I'll have to wing it by explaining it myself.
The CLI has direct support for field initializers but they are not strong enough to support your fields. They can only store simple data, think value types. Initializing a reference type, like your TypedLeaf class requires executing code. And code cannot be stored in a field initializer, it can only appear inside of a method.
So the VB.NET compiler works around that restriction by moving your field initialization expression to the next logical place, the class constructor. This is entirely automatic, it actually rewrites your constructor, in case you provide one yourself, injecting the new operator calls as needed.
Now there's a choice, it could move those calls before or after the base class constructor call. You already know the choice that was made, it happens after. With the justification that a field initializer should not be able to observe members of the base class that are not yet initialized. Your attempt at a workaround is actually pretty heroic compiler writing skills, it actually checks that the base constructor is called first.
Unfortunately you found a case where you are actually happier if it happened before the base constructor call. That's justifiable but unfortunately not permitted, the language designers put their foot down and declared "we only support one way to do this". Fair call, such basics need to be predictable.
The workaround is simple. Just put a Protected method in your base class, say "Initialize", and move the code you now have in the constructor to that method. In the derived class constructor just call that method. The constructor rewriting ensures that the base constructor call is first and the field initializer code is second, making the method call third. Minus 33.3 points for having to remember to make that call so add the code to throw an InvalidOperationException when you see Nothing.