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. 

 

Filed under: , ,

Comments

# re: Fixing Default Instances

Monday, January 24, 2005 4:03 PM by bill

I admire the way you incorporated hookers into your code.

# re: inability to turn it off

Tuesday, January 25, 2005 12:09 PM by bill

If they _have_ to do it (and I wish they didn't) why couldn't they just write a 'Option Defaults On/Off'? While your <VBNoDefaualtInstance> attribute is nice, it's limiting those people who want to (sadly) use it.

With Option Strict On, only the code i write gets checked, and errors generated at design time if i forget to type something.

With an Option Default Off, same deal - if a call is made on something that would ordinarily be using a default instance, a compiler error is thrown, and we get a red squiggly underline.

Or am I missing something? smile

# re: Fixing Default Instances

Tuesday, January 25, 2005 1:05 PM by bill

Thnx Jeff

# re: Fixing Default Instances

Tuesday, January 25, 2005 1:11 PM by bill

Hi Geoff,

Options can be applied to a code file. When you consider partial classes, an option allows coding within each partial class as per the code fiel, not the entire class. For example you could have one partial class with Option Strict Off and party down with late binding, and in the other partial class have Strict On.

So Options are not type/entity specific. That is, if you had Option Default Off in one code file, and Option Default On in another, then if we have partial types, then what rules are applied and where ? Which form gets a Defualt Instance generated for it, and in which code file can you call on Default Isntances using the shortened syntax ?

This is why i went with attributes as they are entity/type specific.

I think if we can get the Vb team to see the light on this, we will have macros and tempaltes otu there very quickly that will turn these things off for the experienced VB developer



# re: Fixing Default Instances

Tuesday, January 25, 2005 1:31 PM by bill

I get what you're saying, but I'm not sure I really see the problem.

There'd be a global setting like there is now for the Options - so by default you'd have option defaults on or off, as your preference.

Let's say we default to setting it off.

If in one code file, i manually add a option default on line to the top, then that's exactly what i mean - only in the code in this file does the compiler allow the the compile to happen if any default instances are referenced.

So now let's say we default to on.

If it's manually overridden in particular code file to off, then the compiler throws errors in that one code file saying that the defaults used there can't be.

In both cases, i've manually adjusted one specific file to allow or not allow an action, and in both cases that's exactly what i wanted.

I don't see how partial classes are a problem - each is a code file, and each has it's own requirements - surely if i have a partial class split over two files, I can have option explicit on one and not on the other?

In all cases when defaults are an issue and not wanted, it's the code that _we_ are writing at the time that has to be on guard. If i write a form and someone uses it 'defaultly' (hey, i'm inventing english here!) that's netiher a problem of mine or a concern of mine. However, if _i_ don't want defaults, I can choose to not use them or not. Having a compiler stop you from touching those areas is as good enough for me as having them removed. However, (and i'm using a metaphor very very loosely here) just like I don't think mormons should come to my door preaching god to me (it's up to me, not them, whether i want to beleive in god) I have a real issue with stopping _others_ from touching my stuff in the way I see fit. They should do what they want with it, and if they want to touch it via defaults, they should be allowed to. I might not like them doing it, but I shouldn't stop them.

I'm sure there's a bigger picture here, and i'm really only thinking about forms, not anything else in the My namespace, and I'm sure you still totally disagree with me smile That's ok, tho. The real point I guess I'm trying to make is that this is something that _must_ have been requested a lot for them to bring it in, even if we see no evidence of it now - so i'm happy to just adjust my habits to welcome the addition, but never actually call it myself. That way, i'm happy because i never get stuck with the issues that defaults bring in, and other people are happy, because they can call their my.forms.form1 and eat it too smile

(Also note: I'm fighting you on this mostly for the principal of the thing - debate is useful to ensure that you're happy with your own justification, and I couldn't help but take a different approach just to make sure that you can fully justify your reasoning *grin* I quite like your idea really, jsut trying to come up with different solutions, especially this late in the whidbey cycle where large changes are likely not to happen smile

I'll shut up now...until you decide to respond anyway

(PS: Do you have any control ove rthe CAPTCHA control? I can't tell if it's a zero or an oh i'm supposed to type, and so far i've got it wrong everytime smile

# re: Fixing Default Instances

Tuesday, January 25, 2005 1:53 PM by bill

hi Geoff,

Okay so I see you are viewing the Option Default On/Off as being consuming code only ? That is, it dictates whether or not you can write Form1.Show, but doesn't dictate which forms are actually default instances.

One of the main reasons I choose attributes was so as I could have granular control over what forms appear in My.Forms. That is, I might only allow certain default instances such as my option form, or there may be a special form I really don't want a default instance of used (yeh I know, all of them )

So through Imports and attributes we have fine granular control which means we also have extensibility.. who knows what the future holds right ?

Oh, and the CAPTCHA, nope, no control at all.. it gets me sometimes too I think machines probably can read it better than we humans

# re: Fixing Default Instances

Tuesday, January 25, 2005 2:19 PM by bill

Yes, that's what i'm thinking - compiler protection, just like option strict smile

Well then just to flush out your attribute a bit more(*), what about inheritance?

The only forms that get put into the my.forms collection are those that are in the current executable project only. So what if using visual inheritance the form is declared in a different assembly, but but my new form inherits from it, what happens?

If the derived form has the attribute, obviously it's excluded from the forms collection.
But what if the base form has the attribute? Obviously the guy that wrote the base form didn't want it touched - so does the form go into the forms collection or not? If it _does_ go in, that's a waste of time for the guy that wrote the base form to even include. If it _doesn't_ go in, why would the guy who wrote it in C# care enough or even know that he _could_ add the attribute?

For that point, I don't have a VS2k5 instance nearby to check, but do inherited forms even make into the forms collection anyway? I would hope so smile

Actually, i think this brings up the biggest problem. Something as big as this, which can completely change the way you write code (dependant on your retardation - sorry, i mean skill - level) is fundamental to the framework. This shouldn't just be a VB thing - it should have been an all or nothing thing, even if it _is_ only vb6 gumbies that want it. That way all the C# and C++ and other .net language people will also have voted in the PFC to not include it, and they would have listened.

OK, well it makes sense to me!

* still just stirring the pot smile

# re: Fixing Default Instances

Tuesday, January 25, 2005 2:42 PM by bill

inheritance would work without any issues smile

the Attribute would be marked as being inherited and being applicable only to classes and assemblies. So let's say you have class foo, and class bar: inhertis foo. If foo is written as :

<VbNoDefaultInstance()>_
Public Class Foo

Then Bar would inherit that attribute by default. But Bar coudl also overwrite that attribute, e.g.

<VbNoDefaultInstance(False)>_
Public Class Bar : Inherits Foo

And hence Bar could be a default instance but foo could not...... and they all lived cross language cross assembly with visual inheritance happily ever after

(and yes inehrited forms do get def instanced)






# re: Singletons and Default Instances

Wednesday, January 26, 2005 5:10 PM by TrackBack

# re: Singletons and Default Instances

Wednesday, January 26, 2005 7:22 PM by TrackBack

# Default Instance Considered Harmful...?

Friday, April 08, 2005 11:05 AM by TrackBack

# Re: Default Instances...

Thursday, April 21, 2005 8:37 AM by TrackBack