Question accessing a class methods without an instance...

divjoy

Well-known member
Joined
Aug 25, 2013
Messages
159
Programming Experience
1-3
Hi,

I'm confused about a few things about classes. I have created a class called Global Variables (see below) I want the whole application to to access the members of this class as required.
VB.NET:
Public Class GlobalVariables
    Private UName As String = Environment.UserName
    Private MachineName As String = Environment.MachineName


    ReadOnly Property UserName() As String
        Get
            Return UName
        End Get
    End Property


    ReadOnly Property MachName() As String
        Get
            Return MachineName
        End Get
    End Property


    Public Shared Function Add(ByVal x As Integer, ByVal y As Integer) As Integer
        Return x + y
    End Function
End Class
This works fine if I make the data members Public Static. I can create an instance of of the class and use them.

But if I make the data members private and add getters I cannot use and instance to access them methods I need to refer to the class name see code below.


VB.NET:
Public Class Form1


    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim GV As New GlobalVariables()
        MsgBox(GV.UserName)  ' Works


        MsgBox(GV.MachName)   'Works


        MsgBox(GV.Add(2, 4))  'Doesnt work !!!!


        MsgBox(GlobalVariables.Add(2, 4))  'Works !!!!


    End Sub
End Class
Can someone explain whats going on please???
 
This works fine if I make the data members Public Static. I can create an instance of of the class and use them.

No. Firstly, you mean Shared, not Static. Static in VB means something else. `static` in C# is equivalent to Shared in VB but C# has no equivalent to Static in VB. In this context, forget Static as it has no relevance.

In VB, a member is either Shared or it's an instance member. Shared members belong to the class itself, rather than any particular instance of the class, while instance members are, as the name suggests, members of each instance. In your GlobalVariables class, your UserName and MachName properties are instance members. As such, you can only access them via an instance of the GlobalVariables class. If you create multiple instances, each instance will have distinct values for those properties. Your Add method, on the other hand, is a Shared member. That means that you can only call it on the class itself and not on an instance. This line:
MsgBox(GlobalVariables.Add(2, 4))
is calling the method on the class so it works. This line:
MsgBox(GV.Add(2, 4))
is trying to call a Shared method via an instance so it doesn't work.

Presumably that method was just something you were playing around with because it's basically useless. The point of that class is presumably the properties. Given that name of that class, defining it as you have doesn't really make sense. If those properties are supposed to be global then you want one value for each and those values should accessible anywhere, any time. As such, you have a number of options:

1. Use a module instead of a class. A module cannot be instantiated even if you want to, so there's no confusion between instance and Shared members. All members of a module are declared without the Shared keyword but it is implicit. You can't create an instance so instance members would be pointless. Every member is implicitly Shared, i.e. you access the members via the module itself, not via an instance. In fact, you can even omit the module name and use the member name unqualified. In fact, you already are. The MsgBox method is actually a member of the Microsoft.VisualBasic.Interaction module.

2. Use a class but declare your properties Shared. That way, you can access those properties anywhere, any time via the class itself and never have to create an instance. You still can create an instance though, which is not a big problem but not ideal. To prevent that, it's a good idea to declare a single, Private constructor in such classes, so that they cannot be instantiated from the outside.

3. Use a class that implements the Singleton pattern. In that case, you declare instance members but you then ensure that there is never more than one instance and that that instance is always accessible, anywhere, any time. To do that, you use Shared members. That might sound confusing but here's an example:
Public Class GlobalVariables

    'The one and only instance of the class.
    Private Shared _instance As GlobalVariables

    Private _userName As String = Environment.UserName
    Private _machineName As String = Environment.MachineName

    'The one and only constructor is Private so an instance cannot be created from outside.
    Private Sub New()
    End Sub

    'Gets the one and only instance of the class.
    Public Shared ReadOnly Property Instance() As GlobalVariables
        Get
            'If there is no existing instance...
            If _instance Is Nothing Then
                '...create a new instance.
                _instance = New GlobalVariables
            End If

            Return _instance
        End Get
    End Property

    Public ReadOnly Property UserName As String
        Get
            Return _userName
        End Get
    End Property

    Public ReadOnly Property MachineName As String
        Get
            Return _machineName
        End Get
    End Property

End Class
You would then use it like this:
Dim userName = GlobalVariables.Instance.UserName
Dim machineName = GlobalVariables.Instance.MachineName
You access the one and only instance of the class via the Shared Instance property and then you get the instance properties of that instance.

4. On the rare occasions that I need global variables in a VB.NET app, I like to let the Application Framework work for me. If you open the Application page of the project properties and click the View Application Events button, the IDE will open a file showing a partial MyApplication class. You can already access a single instance of that class in code using My.Application. If you declare a UserName property in that file then you can access it in code anywhere, any time using My.Application.UserName. Global variables logically belong to the application so accessing them via My.Application makes sense to me. What doesn't make sense is declaring global properties in a file named ApplicationEvents.vb that is intended for application event handlers, e.g. Startup and UnhandledException. It's better if you add your own code file with another MyApplication partial definition. I'd suggest adding a code file named GlobalVariables.vb with this code:
Namespace My

    Partial Friend Class MyApplication
        'Your global fields/properties here.
    End Class

End Namespace
Note that you would declare instance members there, not Shared members.
 
Thank you jmcilhinney for your excellent reply, I have read it a number of times to get the jist of it.

There are really only 3 situation thats I can see I need to use a global variables/methods...

1) the creation of the Database Connection as a constant and is used by all sql commands throughout the application, I could add it as aline on each from but then when I needed to change it....

You may have an alternative ways of handling that.

2) User classification when they log on I give them a user number, this defines what they can access and what they can't and how the forms are drawn.

I could query the table each time the form loads , but it seems easier to capture it at application start up and pass use it with each form as required.

3) I also have a couple of methods that I use in nearly every form see example below.

VB.NET:
Sub PopDGVColumn(ByRef dgv As DataGridView)
        '...Sole purpose is to populate ALL the DGV Column PersonnelNo's before saving...
        Dim x As Integer = dgv.Rows.Count - 1
        For r = 0 To x
            If IsDBNull(dgv.Rows(r).Cells("PersonnelNo")) Then
                     dgv.Rows(r).Cells("PersonnelNo").Value = StaffID  'Global Variable
            End If
        Next r
    End Sub

So to my mind I would like to keep is somewhere that's clearly labelled as my Global Setting, that can be easily found.

I'm conscious too of OOP best practice and I dont know from the 4 above which would be considered the best.

The module option is not OOP, but would clearly work and is the simplest !

I am not sure about declaring a class and not instantiating it? Is this good OOP practice is it used regularly ?

The Singleton approach looks good but it appears to have dropped out of use? or am I wrong on that. It also looks complicated therefor when making changes requires careful thought!

Using an existing class within the application is fine but would not be as clear perhaps as a class called Global Settings ?

Anyways lots to think about and try out and would be very interested in everyone's opinions on this????
 
1) the creation of the Database Connection as a constant and is used by all sql commands throughout the application, I could add it as aline on each from but then when I needed to change it....

You may have an alternative ways of handling that.
Ideally, you would have all your data access in it's own layer so a connection would only ever be created within that. If you do have your data access code mixed up with your presentation code though, I would suggest simply creating a SqlConnection object wherever you need one. They are quite lightweight objects so trying to use one globally is misguided.
2) User classification when they log on I give them a user number, this defines what they can access and what they can't and how the forms are drawn.

I could query the table each time the form loads , but it seems easier to capture it at application start up and pass use it with each form as required.
It makes sense to put information about the current user somewhere common. I might be inclined to add a CurrentUser property to the MyApplication class and then access it via My.Application. It could be Nothing if no user was logged in and return and object containing the user's data when they were logged.
3) I also have a couple of methods that I use in nearly every form see example below.

VB.NET:
Sub PopDGVColumn(ByRef dgv As DataGridView)
        '...Sole purpose is to populate ALL the DGV Column PersonnelNo's before saving...
        Dim x As Integer = dgv.Rows.Count - 1
        For r = 0 To x
            If IsDBNull(dgv.Rows(r).Cells("PersonnelNo")) Then
                     dgv.Rows(r).Cells("PersonnelNo").Value = StaffID  'Global Variable
            End If
        Next r
    End Sub

So to my mind I would like to keep is somewhere that's clearly labelled as my Global Setting, that can be easily found.
Are you talking about the method being global or just the StaffID?

By the way, there's no good reason for that `dgv` parameter to be ByRef. If you're not assign an object to it inside the method, a reference type parameter should ALWAYS be ByVal.
I'm conscious too of OOP best practice and I dont know from the 4 above which would be considered the best.

The module option is not OOP, but would clearly work and is the simplest !

I am not sure about declaring a class and not instantiating it? Is this good OOP practice is it used regularly ?

The Singleton approach looks good but it appears to have dropped out of use? or am I wrong on that. It also looks complicated therefor when making changes requires careful thought!

Using an existing class within the application is fine but would not be as clear perhaps as a class called Global Settings ?

Anyways lots to think about and try out and would be very interested in everyone's opinions on this????
It's a fallacy that modules break OOP rules. A VB module is compiled to the exact same IL code as a C# static class and noone ever complains about C# static classes breaking OOP. Microsoft just used modules instead of Shared classes in VB for continuity with VB6. Using a module is fine.

There's no issue with having classes that never get instantiated. They are very much the minority but they are perfectly OK and logical in their place. For example, You'll see the use of MessageBox.Show throughout numerous Windows Forms applications but have you ever seen anyone create an instance of the MessageBox class?

There's nothing complicated about singletons. Once you've implemented the Singleton pattern with a Private constructor and Shared property, they are just like any other class. Every other member will be an instance member, just like instance members in any other class. You don't have to put any more thought into changing a singleton class that you do any other class. The only difference is that you access every instance member via the Instance property of the class instead of using a constructor to create an instance to access the member by.
 
Hi,

THanks again for your clarifications...also you'r right about my example method, it should be byVal, don't know how that crept in there...

Yes, I was meaning my methods are global too...i.e. any form could use them....

I'm leaning towards your option 2 , of using the class (not an instance) to access my global variables. I can still use setters and getters and so can utilise encapsulation, but would need to make my contructor Private thus disabling the ability to instantiate it!

Latest test class...getting error

Error 1 Cannot refer to an instance member of a class from within a shared method or shared member initializer without an explicit instance of the class. ......\Visual Studio 2010\Projects\GlobalClassTest\GlobalClassTest\Globals.vb 18 20 GlobalClassTest ??

Any ideas?
VB.NET:
Public Class Globals


    '...For Variablesmethods, data properties and method that are used system wide.
    Private strConn As String = "Server=NWA-108620\HCSSQLSB1"  'Live System
    Private strUsername As String = "Test"
    Private strPassword As String = ""
    Private intStaff As Integer = 0
    Private intArea As Integer = 0
    Private intClient As Integer = 0




    '...Constructor is Private so an instance cannot be created.
    Public Sub New()
    End Sub


    Public Shared ReadOnly Property ConnStr() As String
        Get
            Return strConn
        End Get
    End Property
    Public Shared Property UserName() As String
        Set(ByVal UName As String)
            strUsername = UName
        End Set
        Get
            Return strUsername
        End Get
    End Property


    Public Shared Property AreaID() As Integer
        Set(ByVal Areaid As Integer)
            intArea = Areaid
        End Set
        Get
            Return intArea
        End Get
    End Property


    Public Shared Property StaffID() As Integer
        Set(ByVal Staffid As Integer)
            intStaff = Staffid
        End Set
        Get
            Return intStaff
        End Get
    End Property


    Public Shared Property ClientID() As Integer
        Set(ByVal Clientid As Integer)
            intClient = Clientid
        End Set
        Get
            Return intClient
        End Get
    End Property


    Public Shared Function PopDGVColumn(ByVal dgv As DataGridView) As Integer
        '...Sole purpose is to populate ALL the DGV Column PersonnelNo's before saving...
        Dim x As Integer = dgv.Rows.Count - 1
        For r = 0 To x
            If IsDBNull(dgv.Rows(r).Cells("PersonnelNo")) Then
                dgv.Rows(r).Cells("PersonnelNo").Value = StaffID  'Global Variable
            End If
        Next r
        Return 0
    End Function
    Public Shared Function Add(ByVal x As Integer, ByVal y As Integer) As Integer
        Return x + y
    End Function
End Class
 
Latest test class...getting error

Error 1 Cannot refer to an instance member of a class from within a shared method or shared member initializer without an explicit instance of the class. ......\Visual Studio 2010\Projects\GlobalClassTest\GlobalClassTest\Globals.vb 18 20 GlobalClassTest ??

Any ideas?
A Shared member cannot access members of the current instance because there is no current instance. If your properties are Shared then their backing fields have to be Shared too.
 
Back
Top