VB.NET is a fully object oriented language so, as such, basically everything you use is an object. Each object is an instance of its type, e.g. a String object is an instance of the String class. Creating an instance of a type is also known as instantiating that type. When dealing with classes, with the exception of the String class, the only way to instatntiate a type is to invoke a constructor, which is done with the New keyword:
Dim someVariable As SomeType = New SomeType
That code declares a variable whose type is SomeType, creates an instance of the SomeType type and then assigns the object to the variable. VB.NET also supports the following abbreviated syntax, which is functionally equivalent:
Dim someVariable As New SomeType
In your code, you may use various means to get new objects, e.g. some classes may have a Create method that returns a new instance, but all such methods will be using constructors internally. Forms are a special case in VB.NET as of VB 2005, which introduced the concept of default instances.
Every time you add a new form to your project you are creating a new class that inherits the System.Windows.Forms.Form class. Like other classes, forms have constructors and you can create an instance by invoking a constructor, but you can also use the class’s default instance. The default instance is an object of that type that the VB application framework creates and manages for you. For instance, conventionally you would do this to show a form:
Dim myForm As New SomeForm
myForm.Show()
If you use the default instance then you don’t need to invoke a constructor explicitly. You simply access the default instance directly via the My.Forms object:
My.Forms.SomeForm.Show()
or just using the class name:
SomeForm.Show()
It should be noted that, once compiled, this code will still cause a constructor to be invoked in order to create the default instance. The significance of this will be explained later.
One of the main goals of VB has always been to provide power while make programming as easy as possible for as many people as possible. The introduction of default form instances is in furtherance of that goal. Many people new to OOP use objects in VB.NET but they don’t really comprehend them properly. As such, they can have a great deal of difficulty dealing with forms as objects and making forms interact with each other. This is also true of some more-experienced VB6 developers who have made the move to VB.NET. VB.NET default instances behave like VB6 default instances so they feel familiar to VB6 developers making the move to VB.NET.
When should you use default instances?
If you’re going to use default instances at all then you should use them pretty much all the time. I’ll qualify that statement later but, for the moment, let’s say that you should use them all the time or not at all.
One important point to note is that, if you leave the application framework enabled when you create a Windows Forms Application project, your startup form is the default instance of its type. If you don’t know what the application framework is then you can safely assume that it’s enabled and not worry about it.
The fact that your startup form is a default instance is quite useful. For instance, let’s say that you are displaying multiple records in a DataGridView in the main form and you open a new form for the user to edit one of those records. Once the editing is done, you want to update the main form with the new data before closing the dialogue. How do you do it? This is the sort of thing that causes great confusion to those new to OOP and VB.NET. In .NET OO terms, the dialogue needs a reference to the main form in order to update it. Providing that reference is actually not too difficult but the mechanism is not immediately obvious to many. With default instances it’s easy. The dialogue form can access the default instance of the main form’s type using the class name, so it can access the main form, e.g.
Private Sub okButton_Click(ByVal sender As Object, _
ByVal e As EventArgs) Handles okButton.Click
With Form1.DataGridView1.CurrentRow.Cells
.Item(0).Value = Me.TextBox1.Text
.Item(1).Value = Me.TextBox2.Text
End With
Me.Close()
End Sub
In this case, when the user clicks the OK button, the dialogue will directly access the grid on the main form and update the cells of the current row.
Another example of where default instances are helpful is in a multiple-document interface (MDI) application. The child forms in an MDI application are not inherently aware of each other. Let’s say that your MDI application consists of a parent form and two child forms. Let’s say that making a change on one child form needs to cause a change on the other child form. If the children don’t inherently know about each other, how can this be done? The answer is, again, that the first child form simply references the default instance of the second child form’s type via the class name. This will allow changes to be made to the second child form, assuming that it is the default instance of its type.
That last sentence touches on an important point and a source of confusion for some. There’s no point updating the default instance of a form class if you haven’t actually displayed the default instance of that class in the first place. This is one of the reasons I say that, if you’re going to use default instances at all, you should use them all the time. For instance, if you create a form and display it like this:
Dim myForm As New SomeForm
myForm.Show()
then you can’t then refer to the default instance of the SomeForm class and expect the form on your screen to be affected. You’ve created one instance and displayed that, then the system creates the default instance. They are two different objects so making changes to one will not affect the other. If you’re going to make changes by referring to the default instance then you need to have displayed the default instance in the first place:
SomeForm.Show()
Now, default instances don’t hold much appeal for the experienced VB.NET developer. The situations I’ve used as examples up to now should never occur in a well-designed application. For instance, the first example involves a dialogue updating a DataGridView on the main form. In a well-designed application the dialogue shouldn’t have to even know that the main form exists. The dialogue should simply make the new data available via public properties and close. The main form would then retrieve the data itself and update its own UI. In the second example, where a change on one MDI child form causes a change in another child form, the child forms should, again, not have to have knowledge of each other. In a well-designed application the first child would raise an event that can be handled by the parent form, which can then update the other child. The parent form created both the children so it has to have knowledge of both, so it should have no problem accessing both.
There is one situation though, where using the default instance can provide a small benefit to the experienced developer. Because there is always one and only one default instance, they can be useful where you want your form to exhibit singleton-like behaviour. For instance, let’s say that you have a menu item that is supposed to display a form. If the form isn’t already open you want it to be displayed but, if it is already open, you want the existing form to receive focus. Normally you would have to retain a reference to the form when you open it and then, when the menu items is clicked, you need to check whether there is an existing form and, if there is, whether it has already been disposed:
Private toolWindow As SomeForm
Private Sub OpenToolWindowToolStripMenuItem_Click(ByVal sender As Object, _
ByVal e As EventArgs) _
Handles OpenToolWindowToolStripMenuItem.Click
If Me.toolWindow Is Nothing OrElse _
Me.toolWindow.IsDisposed Then
'The tool window has not been opened or it has been opened and closed.
Me.toolWindow = New SomeForm
Me.toolWindow.Show()
Else
'A Tool window is currently open.
Me.toolWindow.Activate()
End If
End Sub
Using the default instance, that code simplifies to this:
Private Sub OpenToolWindowToolStripMenuItem_Click(ByVal sender As Object, _
ByVal e As EventArgs) _
Handles OpenToolWindowToolStripMenuItem.Click
'Make sure the tool window is displayed.
SomeForm.Show()
'Make sure the tool window has focus.
SomeForm.Activate()
End Sub
That does neaten the code a bit, although it’s not a huge improvement, especially if you need to handle events of the form and/or access its methods and/or properties. Personally, I’d just stick with the first style of code but it’s good to know your options.
When can’t you use default instances?
I said earlier that, if you’re going to use default instances at all, you should use them all the time. That is generally true but there are certain situations where you can’t use them. For less experienced developers particularly, these situations will be very much in the minority and may not even be encountered at all in many projects, but it’s important to be aware of them.
1. You need to display multiple instances of the same form class simultaneously.
An example of this would be an MDI application where multiple documents are opened at the same time, all using the same form class. I said earlier that there is always one and only one default instance so, obviously, that presents a problem if you need multiple instances open simultaneously. If you’re going to have to create one or more instances explicitly then you should create them all explicitly. Consistency is a good thing and, if all instances are equivalent, they should be treated in exactly the same way.
2. You need to pass data to the form when you create it.
When you access the default instance for the first time the system must create it and to do that it must invoke a constructor. Specifically, it invokes the constructor that has no parameters. If you want to create a form by invoking a constructor with parameters then you must do so explicitly. It’s also worth noting that if you remove the parameterless constructor and leave only one or more constructors with parameters then your form class will have no default instance. Trying to access it in that case will cause a compilation error.
3. You need to access a form from a secondary thread.
In order to access a form from a secondary thread you generally need to test its InvokeRequired property and then call its Invoke method. I said earlier that there is only ever one default instance of a form class. That’s not strictly true. In fact, default instances are thread-specific, so there is only ever one default instance per thread. As such, if you test the InvokeRequired property of the default instance you will always be accessing the default instance for the current thread, not the one that was displayed on the main thread.
While experienced .NET developers will probably never feel the need to use default instances, those new to OOP or VB.NET may find them useful. They do have their pitfalls though so, like anything, it’s good to understand what you’re dealing with.