Variable and Method Scope in Microsoft .NET

 

Paul D. Sheriff
PDSA, Inc.

December 2001

Summary: This article shows how to define the scope of variables, set the scope of function and sub procedures, and how to scope methods in a class in Microsoft .NET. (16 printed pages)

Objectives

  • Learn to define the scope of variables
  • Set the scope of Function and Sub procedures
  • Learn how to scope methods in a class

Assumptions

The following should be true for you to get the most out of this document:

  • You are familiar with code basics in Microsoft® Visual Basic®
  • You are familiar with methods, classes, procedures, and modules

Contents

Definition of Scope
Variable Scope in Modules
Static Variables
Variable Scope in Classes
Procedure Scope in Modules
Procedure Scope in Classes
Shared Modifier
What's Different From Visual Basic 6.0?
Summary

Definition of Scope

The scope of a variable, sometimes referred to as accessibility of a variable, refers to where the variable can be read from and/or written to, and the variable's lifetime, or how long it stays in memory. The scope of a procedure or method refers to where a procedure can be called from or under what context you are allowed to call a method.

There are many different ways you can declare variables and procedures. How you make these declarations controls the scope of variables and procedures. Before you see examples of how each of these different scoping rules can be used, let's take a quick look at the terms that you will need to know.

Table 1. Scoping terms

Term Used With… Visibility
Public Variables/Properties/Methods/Types Anywhere in or outside of a project
Private Variables/Properties/Methods/Types Only in the block where defined
Protected Variables/Properties/Methods Can be used in the class where defined. Can be used within any inherited class.
Friend Variables/Properties/Methods Can only be accessed by code in the same project/assembly.
ProtectedFriend Variables/Properties/Methods Combination of Protected and Friend

Scope of Variables

You can declare variables at four different locations in your programs. Where you choose to declare variables determines the scope.

The following table provides the general rules for scope. Be aware that the rules may vary slightly depending on how you declare the variable.

Table 2. General scoping rules

Location Description
Block If you declare a variable within a block construct such as an If statement, that variable's scope is only until the end of the block. The lifetime is until the procedure ends.
Procedure If you declare a variable within a procedure, but outside of any If statement, the scope is until the End Sub or End Function. The lifetime of the variable is until the procedures ends.
Module/Class You can declare a variable outside of any procedure, but it must be within a Class…End Class or Module…End Module statement. The scope is any procedure within this module. The lifetime for a variable defined within a class is until the object is cleaned up by the garbage collector. The lifetime for a variable defined within a module is until the program ends.
Project You can declare a Public variable within a Module…End Module statement, and that variable's scope will be any procedure or method within the project. The lifetime of the variable will be until the program ends.

Scope of Properties/Methods

Procedures can be declared in three locations, within classes, within structures and within modules. You may also assign five different modifiers to these procedures to give them different scope.

Table 3. Procedure modifiers

Location Description
Public A Public procedure, when declared within a module, can be called from anywhere within the project. A Public procedure, when declared within a class or structure, can be invoked from an object declared as the type of that class or structure.
Private A Private procedure can only be called within the class, structure or the module in which it is declared. This means that only other procedures within the same module may call them.
Protected A Protected procedure may only be declared within a class. Procedures declared within the same class can call it, as can other methods in inherited classes.
Friend A Friend procedure may be accessed from any other code residing in the same project/assembly.
ProtectedFriend A Protected Friend procedure may be accessed from the class it is defined in, a derived class, or other procedures in the same project/assembly.

Variable Scope in Modules

You will first use the scope of variables as you declare them within a module.

Block-Level Scope

Although you will probably not use this type of scope very often, you are allowed to declare a variable with the Dim statement inside of block level constructs such as an If…End If statement or a Try…End Try statement. This variable may then only be used inside of this statement block.

Private Sub BlockTest()     Dim boolPerform As Boolean
        
    boolPerform = True
    If boolPerform Then
        Dim intValue As Integer = 10
            
        MsgBox("intValue = " & _
         intValue)
    End If
    ' The following will not work, outside of scope.
    'MsgBox(intValue)
End Sub

In the Click event procedure above, you declare the variable intValue inside the If statement. The value is initialized to 10 and then displayed in a message box. After the End If statement, if you tried to reference this variable again, you would receive a compile-time error.

Procedure-Level Scope

The most common place to declare variables is at the top of a procedure. These variables are then available to you throughout the procedure. It is a good practice to put variables at the top of your procedure, as you will always know where to look for variable declarations in all procedures. If you do not put them all at the top, you never know where another variable might be declared, and it might take you longer to track down a bug related to a variable.

Private Sub ScopeTest()    Dim intValue As Integer
        
    intValue = 10
        
    MsgBox("intValue = " & _
     intValue)
    ' After the End Sub, intValue is released
End Sub

In the above Click event procedure, the variable intValue is declared at the top. Its value is available anywhere within the procedure, including within any block constructs.

Module-Level Scope

You might want to use a variable in more than one routine within a program. It can become quite cumbersome to always have to pass variables from one routine to another via a parameter. Or if you need to keep track of a value between invocations of a specific procedure, you need to have a variable that can stay around even after a procedure ends. To accomplish these tasks, you need to use a module-level variable.

Consider the following module created from the Microsoft Visual Studio® menu by clicking Project, and then clicking Add Module.

Module modVar1
    Private mintLoop As Integer
    
    Public Sub LoopIncrement()
        mintLoop += 1
    End Sub
    
    Public Sub LoopDisplay()
        MsgBox( _
         "mintLoop = " & mintLoop)
    End Sub    
End Module

In the above module you have created a Private variable called mintLoop. Use "m" as the prefix to indicate that this variable is scoped at the module level. This Private variable can now be accessed by any procedure in this module. Both the LoopIncrement and LoopDisplay procedures can access and use this variable.

Now consider the following Click event procedure that is declared in a Form class in another file.

Private Sub ModuleTest()    ' Can't see the following variable
    ' mintLoop = 10
        
    ' Can call a routine inside the module 
    ' to change the value
    LoopIncrement()

    LoopDisplay()
End Sub

In this Click event procedure, you can see that the mintLoop variable cannot be used from this routine. If you attempt to, you will receive a compile-time error that this variable is not declared. You can call the LoopIncrement and LoopDisplay procedures to change the value and report the value back to this routine.

Static Variables

Static variables are supported by Microsoft Visual Basic .NET.

Project-Level Scope

If you wish to have a "global" variable, one that can be seen by any procedure anywhere in your program, you need to declare this type of variable as a Public scope in a module somewhere in your program.

Module modVar2
    Public gintValue As Integer
    
    Public Sub GlobalModuleValue()
        MsgBox( _
         "gintValue = " & gintValue)
    End Sub
End Module

In the module shown above, the gintValue variable is declared as Public. This makes this variable available anywhere in your project, or from other projects referencing your project. You can see that the GlobalModuleValue procedure uses that variable. In the code below, you can see that a Click event procedure will change this variable, and read it back as well.

Private Sub PublicModuleTest()    ' Declared in module, you can change 
      it here
    gintValue = 10
        
    ' You can read it here
    MsgBox("gintValue = " & gintValue)
        
    ' You can call any procedure anywhere, 
    ' and that procedure can see it or change it
    GlobalModuleValue()
End Sub

Of course, you'll want to limit the use of global variables. Globals are very hard to keep track of, and you never know when another routine is using that value without careful tracking. This can cause a lot of wasted time and effort tracking down a bug that is related to the wrong data in the wrong variable at the wrong time.

Shadowing

Following some sort of naming standards for your variables can help immensely. Notice in the previous examples, that variables declared at the module level used an "m" as the prefix, and global variables used a "g" as a prefix. This naming standard can help you quickly identify what scope a particular variable has. Consider the following example:

Module modShadow2
    Public MyLoop As Integer = 200
    
End Module

In the module modShadow2, a variable named MyLoop is created as an integer. Now look at the module shown below, modShadow1:

Module modShadow1
    ' This variable shadows MyLoop in modVar3
    Private MyLoop As Integer
    
    Public Sub ShadowTest()
        ' Use the Private variable
        MyLoop = 300
        
        Msgbox( _
         "MyLoop = " & MyLoop)
        MsgBox( _
         "modShadow2.MyLoop = " & modShadow2.MyLoop)
        
        ' Change the Public variable
        modShadow2.MyLoop = 500
        MsgBox( _
         "modShadow2.MyLoop = " & modShadow2.MyLoop)
    End Sub
End Module

In the modShadow1 module, you declare another variable called MyLoop. This variable is declared as a Private variable so it has module-level scope. Because this variable has a lower scope than the Public variable declared as MyLoop, any time you reference MyLoop in this module, this Private variable is the one you reference. In fact, there is only one mechanism you can use to get at the Public variable named MyLoop, and that is by prefixing the module name in which it was declared to the variable. You can see a sample of this in the above code.

Variable Scope in Classes

Let's look at the different ways you can use scope when declaring variables within a class.

Public Variables in Classes

When you build your own classes, you need to create properties. There are two ways you can create properties for a class. You can create a Public variable outside of any procedure in the class, or you can create a Private variable and create a Property…End Property statement to expose that Private variable. To create a Public variable, you will write code like this:

Public Class PublicTest
    Public PublicName As String
    
End Class

In the above code, you created a variable called PublicName. Notice that this Public variable is declared outside of any procedure in this class. You can now reference that variable through an object that is created as the type PublicTest as shown in the code below.

Private Sub PublicClassTest()    ' PublicName is NOT accessible without 
      an object
    'MsgBox("PublicName = " & PublicName)
        
    ' Now declare an object
    Dim PName As New PublicTest()
    ' You can see the value in PublicName
    MsgBox("PName.PublicName = " & _
     PName.PublicName)
    ' You can assign values to Product Name
    PName.PublicName = "John"
        
    MsgBox("PName.PublicName = " & _
     PName.PublicName)
End Sub

The above code shows that, unlike a Public variable created in a module, a variable created in a class cannot be used by only including its name. Instead, you have to create an instance of the class containing the variable, and then reference the variable through the class instance.

There are a variety of reasons why creating a Public variable in a class is not such a good idea. It does not allow you to restrict the access outside the class to read-only, and it also does not give you the flexibility to put in any error handling or range-checking code. In some properties, you may wish to check values prior to assigning them to a Private variable of the class. When you make a variable Public like this, you do not have that capability.

Private Variables in Classes

You can declare variables as Private as well as Public outside of any procedures in a class. When you do this, these variables can only be read and written to in procedures within the class. This is just like the rules for a Private variable within a module.

Public Class PrivateTest
    ' Can not be seen outside of this class
    Private mstrName As String
    
    Public Sub NameInit()
        ' Can change mstrName here
        mstrName = "Jane Doe"
    End Sub
    
    Public Sub NameChange()
        ' Can change mstrName here
        mstrName = "Janie Doe"
    End Sub
End Class

In the above class, you can see that the mstrName variable is declared outside of any procedure. It can be read and written to within the class, but is not visible outside of the class at all.

Protected Variables in Classes

Another type of scope is called Protected. This is a new type of scope in Visual Basic .NET. Protected is similar to Private in that it is visible within the class in which it is declared, as shown below.

Public Class ProtectedTest
    Protected mcurCost As Decimal = 100
    
    Public Function GetCost() As Decimal
        Return mcurCost
    End Function
End Class

A Protected variable can also be seen from within any class that inherits from the same base class.

Below is an example of another class that inherits from the ProtectedTest class. You can see in the GetCost2 method that it accesses the mcurCost variable just like it was a part of this new class. But in fact, it is being inherited from the base class.

Public Class ProtectedInheritance
    Inherits ProtectedTest
    
    Public Function GetCost2() As Decimal
        ' Can still access the variable, 
        ' just like it is local
        Return mcurCost
    End Function
End Class

Friend

A variable declared with the Friend access specifier can only be seen by other classes within the same program. This means that if a class has a Friend variable and that class resides in a DLL, then only other classes within the same DLL can see that Friend variable. Any other program that uses that DLL cannot see that variable.

Protected Friend

This is a union of the capabilities of the Protected specifier and the Friend specifier.

Procedure Scope in Modules

You will next learn how to apply these same keywords, Public, Private, Protected, and Shared, to procedures and methods that you write in your application. There are only two scopes that you can apply to procedures within modules, Public and Private.

Public Scope

When you declare a Public procedure (Sub or Function) as Public, that procedure can be called from anywhere in your entire project. It cannot be called from any other project in your solution. Below is an example of a module that would declare within a file in your project.

Module modProc1
    Public Sub PublicProc()
        MsgBox( _
         "Hello from modProc1.PublicProc()")
    End Sub
End Module
The procedure PublicProc can be called by using the following syntax:
Private Sub btnPublicMod_Click(ByVal sender As Object, _
 ByVal e As System.EventArgs) Handles btnPublicMod.Click
    PublicProc()
    
End Sub

Note   You can use the same name for a Public procedure within two different modules. When you call that procedure, you must prefix the name with the module name or you will receive a compiler error.

Private Scope

When you declare a procedure as Private within a module, only other procedures within that module may call that procedure. Consider the following example:

Module modProc2
    Public Sub PublicProc2()
        ' Can call the Private proc from here
        PrivateProc()
        
        MsgBox( _
         "Hello from modProc2.PublicProc()")
    End Sub
    
    Private Sub PrivateProc()
        ' Can only be called from within this module
        MsgBox( _
         "Hello from PrivateProc()")
    End Sub
End Module

In the module, modProc2, there are two procedures. One is Public and the other is Private. PublicProc2 can be called from anywhere in your project. The PrivateProc procedure can only be called from PublicProc2 because it is declared as Private. If you add other procedures to this module, you would be able to call PrivateProc from those procedures as well.

Privately scoped procedures are typically used as "helper" procedures. These are routines that only do some specific task related to one of the Public procedures within the same module. Although these are not used too often in modules, you will most likely use them quite a bit within classes.

Procedure Scope in Classes

Now you will learn use scope in methods that you write in your classes.

Public Scope

A procedure within a class that is declared as Public can be called from any object of that type of class. Take, for example, the listing of code below.

Public Class PublicMethodTest
    Public Function NameGet() As String
        Return "PublicMethodTest.NameGet()"
    End Function
    
    Private Function PrivateProc() As String
        Return "PublicMethodTest.PrivateProc()"
    End Function
End Class

In the above code, you see a class named PublicMethodTest. Within this class is a Public function called NameGet(). The NameGet() method can be called from any object that is declared of the type PublicMethodTest.

Private Sub PublicScopeTest()    Dim oPublic As New PublicMethodTest()
        
    MsgBox("oPublic.NameGet() = " & _
     oPublic.NameGet())
End Sub

In the above code, you can see that you are allowed to call the NameGet method by prefixing it with the oPublic object variable. Because this variable is created as a type of PublicMethodTest, all Public procedures are available to it.

Private Scope

A Private procedure is one that cannot be seen outside of a class module. In the PublicMethodTest class, you saw a Private procedure named PrivateProc. This procedure can only be called from a procedure within the same class. If you tried to call it from the oPublic variable you declared in the previous code listing, you would get a compiler error.

Protected Scope

A procedure that is declared as Protected is similar to a Private procedure in that it can be called from other methods within the same class. However, it may also be called from any other classes that inherit the class in which the Protected procedure is declared. Consider the following code.

Public Class ProtectedMethodTest
    Protected Function NameGet() As String
        Return "ProtectedMethodTest.NameGet()"
    End Function
End Class

In the above code, you declared a function called NameGet. This method can only be seen within this class, or any inherited class. You can now create an inherited class by typing in code like the following.

Public Class PMInheritedTest
    Inherits ProtectedMethodTest
    
    Public Function NameReturn() As String
        Return NameGet()
    End Function
End Class

In this PMInheritedTest class, you use the Inherits keyword to retrieve all of the functionality of the ProtectedMethodTest class, including any Protected functions. If you then create a Public function called NameReturn, this method can call the NameGet function.

Shared Modifier

The Shared keyword is a modifier that may be applied to either a variable in a class or a method inside of a class. If you declare a variable with this modifier, that variable will only be created once no matter how many instances of the class are created. If you access this variable from any instance, or change this variable from any instance, then the value will be the same across all instances. A method declared as Shared can be called without creating an instance of that class. Think of the MessagBox.Show method. You do not have to create a MessageBox object, you just use the Show method.

Shared Variables in Classes

A shared variable is created just once for any objects that are declared of a certain class type. One common use of a Shared variable is when you wish to keep track of how many objects of a certain class type are currently declared. To do this, you use the keyword Shared in front of any variable that you wish to expose as a property of a class.

Public Class SharedMemberTest
    Public Shared NumInstances As Integer
    
    Public Sub New()
        NumInstances += 1
    End Sub
    
    Public Sub Dispose()
        NumInstances -= 1
    End Sub
End Class

In the SharedMemberTest class, you create a Public variable called NumInstances and identify it is as a Shared variable. This means that only one memory location is allocated for NumInstances. There is no individual memory location for each object instance.

In the constructor, you increment this variable value by one. Each time a new object is created, the variable increases in value. As an example, you could now write some code like the following to create a couple of different variables of the type SharedMemberTest.

Private Sub SharedTest()
    Dim oShared1 As SharedMemberTest _
     = New SharedMemberTest()
        
    MessageBox.Show("oShared1.NumInstances = " & _
     oShared1.NumInstances.ToString())
        
    Dim oShared2 As SharedMemberTest _
     = New SharedMemberTest()
        
    MessageBox.Show("oShared2.NumInstances = " & _
     oShared2.NumInstances.ToString())
    MessageBox.Show("oShared1.NumInstances = " & _
     oShared1.NumInstances.ToString())
        
    ' Call Dispose to decrement the # of instances
    oShared1.Dispose()
    oShared2.Dispose()
End Sub

Notice that you create the first variable, oShared1, and when you display the NumInstances property, it displays a 1. After creating the oShared2 object, the NumInstances property is incremented, so it now contains a 2. You can prove this by once again displaying the NumInstances property from the oShared2 and the oShared1 variables to see that a 2 is displayed.

It is good coding practice to call a Dispose method on the object prior to the object going out of scope. This allows you to decrement the NumInstances property for each object that will be destroyed. If you do not do this, the NumInstances property may continue to report a 3, as the garbage collector may take a while before it cleans up all instances of these objects.

Shared Methods

When you use the Shared keyword as part of a Public method in a class, that procedure can be called just by prefixing the name of the method with the class name. There is no need to create an object of the class. Consider the following class declaration.

Public Class MyBox
    Public Shared Sub Show(ByVal strValue As String)
        MsgBox(strValue)
    End Sub
End Class

In the MyBox class, you create a Public Shared Sub named Show. This method can now be called, as shown in the following code:

Private Sub SharedMethodTest()
    MyBox.Show("Hi There")
End Sub

As you can see in the above code, you used the Show method in the MyBox class without creating an object variable first. This type of method can come in very handy. This class/method can be used anywhere throughout your project and any projects that you include this class within.

There are a couple of items you must be aware of when using the Shared keyword in conjunction with methods in a class. 1). You may not reference any variable or method using the Me, MyClass or the MyBase keywords. This makes sense since these assume an instance has been created, when in fact, none has. 2). No shared method may use the modifiers Overridable, NotOverridable, or MustOverride keywords.

What's Different From Visual Basic 6.0?

There are quite a few changes to how you can change the scope of variables, functions, and methods in Visual Basic .NET compared to Visual Basic 6.0.

Here are some new ways of doing things that had no equivalent in previous versions of Visual Basic.

  • You may now declare variables within a block of code.
  • You now have a Protected scope of properties and methods.
  • You may declare a variable within a class as Shared.

One thing has changed from previous versions of Visual Basic:

  • You now declare a method as Shared instead of setting a property on a class as Global Multi-Use.

Visual Basic .NET has also removed one way of doing things:

  • Static variables can no longer be declared. You must use module-level variables instead.

Summary

In this document, you learned the different rules of scope as they apply to variables and procedures. There are many different rules that you need to keep in mind as you develop your applications. A general guideline for scoping variables is that you want to keep the scope of variables as local as possible. Avoid global variables as much as you can, as this will lead to fewer errors in your programs. As far as procedures go, you simply need to decide which scope to use based on your particular needs.

About the Author

Paul D. Sheriff is the owner of PDSA, Inc., a custom software development and consulting company in Southern California. Paul is the MSDN Regional Director for Southern California, is the author of a book on Visual Basic 6 called Paul Sheriff Teaches Visual Basic, and has produced over 72 videos on Visual Basic, SQL Server, .NET and Web Development for Keystone Learning Systems. Paul has co-authored a book entitled ASP.NET Jumpstart. Visit the PDSA, Inc. Web site (www.pdsa.com) for more information.

About Informant Communications Group

Informant Communications Group, Inc. (www.informant.com) is a diversified media company focused on the information technology sector. Specializing in software development publications, conferences, catalog publishing and Web sites, ICG was founded in 1990. With offices in the United States and the United Kingdom, ICG has served as a respected media and marketing content integrator, satisfying the burgeoning appetite of IT professionals for quality technical information.

Copyright © 2001 Informant Communications Group and Microsoft Corporation

Technical Editing: PDSA, Inc.