Fixing Default Instances
Posted
Mon, Jan 24 2005 9:53
by
bill
About six months ago I blogged about some of the issues with Default Instances. More recently Paul started to blog about Default Instances. Now judging from the feedback Paul has gotten, and the conversations I have had, there seems to be a lot of people unhappy about the current implementation... so... in this blog entry I am going to put forward a set of proposals that I think address all the major issues. Then finally I will add some comments to the fray ;)
Issues to date are :
+ Ability to test for null
+ Preservation of data
+ Namespace mangling (interference with Shared methods)
+ inability to turn it off
Ability to test for null
To their credit the VB team has added a little bit of magic that now allows Default Instances to be tested for null. That is, when they parse the code they change the code to use the backing field when the code is like
If My.Forms.Form1 Is Nothing Then
It compiles to:
If My.Forms.m_Form1 Is Nothing Then
The problems with this little bit of magic are many, but the main two issues I have with it are it’s (a) not extensible, and (b) it compromises encapsulation.
(a) making it extensible would have been to make this available to any developers code, not just the My generated classes. To do this, it would have meant just using an attribute, something like <VBNullTestField(“m_Form1”)> would have done the trick, and would make more sense if they decide to add other default instances further down the track.
(b) This is where it gets a bit tricky… To expose the backing field for a null test, it means it can’t be marked private. You can see the weirdness this causes today in the beta bits.. For example, if you have My.Form1, you’ll notice that you can’t set that to be any other instance or derived instance as the property set only allows Nothing as the value. But because m_Form1 is actually exposed, you can change the backing store value. This means you can actually write code that bypasses the property Set !!! (not a good thing !!)
So rather than expose the backing field, I suggest they use an attribute that exposes a delegate. The delegate signature would have no parameters and a Boolean return type:
Delegate Function NullTest() As Boolean
The attribute would then become:
<VBNullTest(AddressOf(MyNullTestFunction))>
This would mean that a method was called, and the backing store would NOT be exposed. Encapsulation is one of the primary OO rules, so I think this modification is crucial !!
Preservation of data
As I outlined in my earlier blog posting, default instances have “issues” with preservation of data. With WinForms once a form is closed and dispose is called, you can no longer re-show that form. SO the Default Instance has to re-incarnate the form and hence all data is lost. What I propose is that the following class be added to the VB runtime…
' KeepAliveFormHook class.
' purpose: monitors a form's handle created and destroyed,
' and overrides wndProc preventing wm_close calling dispose
' by destroying the handle and setting visible to false
Public Class KeepAliveFormHook
Inherits NativeWindow
WithEvents m_frm As Form
Public Sub New(ByVal form As Form)
SetForm(form)
End Sub
Public Sub SetForm(ByVal frm As Form)
m_frm = frm
hook()
End Sub
Private Sub hook()
If Not m_frm Is Nothing AndAlso m_frm.IsHandleCreated Then
MyBase.AssignHandle(m_frm.Handle)
Else
MyBase.ReleaseHandle()
End If
End Sub
Private Sub frm_HandleCreated(ByVal sender As Object, ByVal e As System.EventArgs) Handles m_frm.HandleCreated
MyBase.AssignHandle(m_frm.Handle)
End Sub
Private Sub frm_HandleDestroyed(ByVal sender As Object, ByVal e As System.EventArgs) Handles m_frm.HandleDestroyed
MyBase.ReleaseHandle()
End Sub
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
Const WM_CLOSE As Int32 = &H10
If m.Msg = WM_CLOSE Then
Me.DestroyHandle()
m_frm.Visible = False
Else
MyBase.WndProc(m)
End If
End Sub
End Class
Code for the Default Instance would then work something like this:
Public Property Form3() As Form3
Get
If m_Form3 Is Nothing OrElse m_Form3.IsDisposed Then
m_Form3 = New Form3
SetHook(m_Form3Hook, m_Form3)
End If
Return m_Form3
End Get
Set(ByVal Value As Form3)
m_Form3 = Value
SetHook(m_Form3Hook, m_Form3)
End Set
End Property
Private Sub SetHook(ByRef hooker As KeepAliveFormHook, ByVal frm As Form)
If hooker Is Nothing Then
hooker = New KeepAliveFormHook(frm)
Else
hooker.SetForm(frm)
End If
End Sub
So it’s just a minor addition., but the change in functionality is huge. With this, Default Instances can now be used as data stores, so reading a field value after a default instance is closed will actually work :)
Namespace mangling
This issue is the one that gets me the most. Personally I hate the Form1.Show as it means code like Form1.Foo is no longer clear … is that the default instance or is Foo a Shared member ? From discussion with other people it seems this issue is big with them as well and for a variety of reasons. Some feel the Form1 default instance is actually confusing for a lot of people. I tend to agree, and I think that was actually one of my huge learning hurdles I hit when I first learnt VB … Anyway, enough on the problems, let’s look at solutions ..
To me the simplest solution for this is to use the Imports statement. So for code upgraded from Vb6 (and perhaps standard project templates), there would be an :
Imports My.Forms
at project level. With that in place, Form1.show works. Remove it, and the code has to be My.Forms.Form1.Show.
inability to turn it off
Finally, the ability to turn it off is also a must have. Default Instance code might not be wanted for some forms, so we need a way to hide them. Once again I think an attribute would do the trick, e.g:
<VBNoDefaualtInstance>
If you put that on a form, then no default instance would be generated for that form. This would be an opt out scheme. Ideally this attribute would also be allowed at assembly level, hence turning off all default instances for that assembly.
-----------------------------------------------------------
Okay, so now onto the comments… Personally I am not a big fan of Default Instances as they were in VB6. That doesn’t mean the concept is not potentially useful ! For cross form communication (think an Options form etc, etc) then Default Instances can really make life a lot easier. But the key to their success is the way they are implemented. I think if the above modifications are adopted, they become a win-win for everyone.