Cause this VB6-like smell, which VB.NET allows, to error instead: WinFormType.InstanceProp=Value [DISABLE My.Forms]

I have notice something very obnoxious with VB.Net's treatment of Winform objects.

This has trashed several hours of our time. It will only get worse as we have more of our VB6 programmers that are used to doing things like this, and autoconverted code which brings the construct straight over from vb6.

Here is an acceptable way to do things:

Dim FormInstance as New FormClassName
If FormInstance.ShowDialog() = DialogResult.OK then
    TheAnswer = FormInstance.TextBox1.Text
EndIf

However, it allows this:

If FormClassName.ShowDialog() = DialogResult.OK then
    TheAnswer = FormClassName.TextBox1.Text
EndIf

Bear in mind, the properties and methods are not Shared. Turning of Application Framework doesn't matter. It seems that behind the scenes, VB instantiates a global copy of the form, and re-routes this syntax to that global reference. You can imagine the havoc this wreaks on a modern program! Often a developer will throw it in or we'll miss cleaning up some obscure code from conversion (yes, I am looking for this now, so that helps).

Any setting I can make to cause this to throw an error message, e.g., Reference to a non-shared member requires an object reference, like it ought to?

Here's the solution:

I chose to select the answer of jmoreno because he pointed out the culprit for me: My.Forms. Fixing it was as easy as putting this in a module:

Namespace My.MyProject.MyForms
End Namespace

Then you get the exact error I mentioned above. Just like you should. If you need that for legacy apps (a good thing), then don't do this! I thought Gserg might just be VB bashing (fun but not helpful) but he mentioned all this right away, and since I found the answer, there we're good again about vb not sucking unless you are just unfamiliar with it.

Note if you use the application framework you'll get an error you don't want in application.designer. The fix:

    Protected Overrides Sub OnCreateMainForm()
        ''//was: Me.MainForm = Global.WindowsApplication2.Form1
        Me.MainForm = New Form1
    End Sub

Hopefully that would be it for any bad side effects!

JMoreno's reflection, etc.

The above is so simple that I'd hate to suggest anything else, but if you are curious, here are improvements on that code to (1) add reflection to omit having to hardcode in each form you make and (2) make it automatically enforcing (with just one call to this sub at program startup). Just put this in a module:

Public Sub FixMyForms()
    For Each pi As System.Reflection.PropertyInfo In GetType(My.MyProject.MyForms).GetProperties
        Dim obj As Object = pi.GetValue(My.Forms, Nothing)
        If TypeOf obj Is Form Then
            AddHandler CType(obj, Form).Load, AddressOf Complainer
        End If
    Next
End Sub

Private Sub Complainer(ByVal sender As Object, ByVal e As System.EventArgs)
    MsgBox("WRONG!")
End Sub

Solution 1:

No. This confusing behaviour is by design.

It has been possible in VB to use one instance of a form without creating it, and this behaviour happily made it to VB.NET. To me, this is one of the worst design choices.
But it does make converting legacy projects easier. And it does a favour to those who never even knew a form could be explicitly instantiated.

But you are not required to use this trick, as you never were in VB6. In both languages you can use explicitly created instances, in which case the default instance will not be created. Just forget this "feature" exists and only let it back to your mind when enlightening others.

Solution 2:

Sorta. You could create a function or override ShowDialog that checks the My.Forms collection to see if the current instance is in it and if it is thrown an exception. There's not a setting that can be used to prevent it from being used.

EDIT: adding sample code illustrating the concept (using a beep instead of an exception).

    Shared Function FormIsInMyForms(formName As String, frm As Form)
        If formName = "Form1" Then
            If frm.Equals(Form1) Then
                Return True
            Else
                Return False
            End If
        End If
        Return False

    End Function

    Public Overloads Sub ShowDialog()

        If FormIsInMyForms("Form1", Me) Then
            Beep()
        End If

        MyBase.ShowDialog()
    End Sub

Doing the same thing via reflection is left as an exercise for the reader... :)