Reputation: 10226
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?
Upvotes: 1
Views: 249
Reputation: 14113
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.
Upvotes: 1
Reputation: 942040
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.
Upvotes: 1