As you have seen in earlier chapters, some classes allow you to call some of their members without first creating an instance of the class. The DateTime class, for example, exposes the IsLeapYear method, which accepts as an argument a numeric value and returns a True/False value that indicates whether the year is a leap year. You can call this method through the DateTime (or Date) class without having to create a variable of the DateTime type, as shown in the following statement:
If DateTime.IsLeapYear(1999) Then
{ process a leap year}
End If
Code language: VB.NET (vbnet)
A typical example of classes that can be used without explicit instances is the Math class. To calculate the logarithm of a number, you can use an expression such as this one:
Math.Log(3.333)
Code language: VB.NET (vbnet)
The properties and methods that don’t require you to create an instance of the class before you call them are called shared methods. Methods that must be applied to an instance of the class are called instance methods. By default, all methods are instance methods. To create a shared method, you must prefix the corresponding function declaration with the Shared keyword, just like a shared property.
Why do we need shared methods, and when should we create them? If a method doesn’t apply to a specific instance of a class, make it shared. In other words, if a method doesn’t act on the properties of the current instance of the class, it should be shared. Let’s consider the DateTime class. The DaysInMonth method returns the number of days in the month (of a specific year) that is passed to the method as an argument. You don’t really need to create an instance of a Date object to find out the number of days in a specific month of a specific year, so the DaysInMonth method is a shared method and can be called as follows:
DateTime.DaysInMonth(2004, 2)
Code language: VB.NET (vbnet)
Think of the DaysInMonth method this way: Do I need to create a new date to find out if a specific month has 30 or 31 days? If the answer is no, then the method is a candidate for a shared implementation.
The AddDays method, on the other hand, is an instance method. We have a date to which we want to add a number of days and construct a new date. In this case, it makes sense to apply the method to an instance of the class — the instance that represents the date to which we add the number of days.
If you spend a moment to reflect on shared and instance members, you’ll come to the conclusion that all members could have been implemented as shared members and accept the data they act upon as arguments. The idea behind classes, however, is to combine data with code. If you implement a class with shared members, you lose one of the major advantages of OOP. Building a class with shared members only is equivalent to a collection of functions, and the Math class of the Framework is just that.
The SharedMembers sample project is a simple class that demonstrates the differences between a shared and an instance method. Both methods do the same thing: They reverse the characters in a string. The IReverseString method is an instance method; it reverses the current instance of the class, which is a string. The SReverseString method is a shared method; it reverses its argument. Listing 6.19 shows the code that implements the SharedMembersClass component.
Listing 6.19: A Class with a Shared and an Instance Method
Public Class SharedMembersClass
Private strProperty As String
Sub New(ByVal str As String)
strProperty = str
End Sub
Public Function IReverseString() As String
Return (StrReverse(strProperty))
End Function
Public Shared Function SReverseString(ByVal str As String) As String
Return (StrReverse(str))
End Function
End Class
Code language: VB.NET (vbnet)
The instance method acts on the current instance of the class. This means that the class must be initialized to a string, and this is why the New constructor requires a string argument. To test the class, add a form to the project, make it the Startup object, and add two buttons to it. The code behind the two buttons is shown next:
Private Sub Button1_Click(...) Handles Button1.Click
Dim testString As String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
Dim obj As New SharedMembersClass(testString)
Debug.WriteLine(obj.IReverseString)
End Sub
Private Sub Button2_Click(...) Handles Button2.Click
Dim testString As String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
Debug.WriteLine(SharedMembersClass.SReverseString(testString))
End Sub
Code language: VB.NET (vbnet)
The code behind the first button creates a new instance of the SharedMembersClass and calls its IReverseString method. The second button calls the SReverseString method through the class’s name and passes the string to be reversed as an argument to the method.
A class can also expose shared properties. There are situations in which you want all instances of a class to see the same property value. Let’s say you want to keep track of the users currently accessing your class. You can declare a method that must be called to enable the class, and this method signals that another user has requested your class. This method could establish a connection to a database or open a file. We’ll call it the Connect method. Every time an application calls the Connect method, you can increase an internal variable by one. Likewise, every time an application calls the Disconnect method, the same internal variable is decreased by one. This internal variable can’t be private because it will be initialized to zero with each new instance of the class. You need a variable that is common to all instances of the class. Such a variable should be declared with the Shared keyword.
Let’s add a shared variable to our Minimal class. We’ll call it LoggedUsers, and it will be read-only. Its value is reported via the Users property, and only the Connect and Disconnect methods can change its value. Listing 6.20 is the code you must add to the Minimal class to implement a shared property.
Listing 6.20: Implementing a Shared Property
Shared LoggedUsers As Integer
ReadOnly Property Users() As Integer
Get
Users = LoggedUsers
End Get
End Property
Public Function Connect() As Integer
LoggedUsers = LoggedUsers + 1
{ your own code here}
End Function
Public Function Disconnect() As Integer
If LoggedUsers > 1 Then
LoggedUsers = LoggedUsers - 1
End If
{ your own code here}
End Function
Code language: VB.NET (vbnet)
To test the shared variable, add a new button to the form and enter the code in Listing 6.21 in its Click event handler. (The lines with the numbers are the values reported by the class; they’re not part of the listing.)
Listing 6.21: Testing the LoggedUsers Shared Property
Protected Sub Button5_Click(ByVal sender As Object, _
ByVal e As System.EventArgs)
Dim obj1 As New SharedMemberClass
obj1.Connect()
Debug.WriteLine(obj1.Users)
1
obj1.Connect()
Debug.WriteLine(obj1.Users)
2
Dim obj2 As New SharedMemberClass
obj2.Connect()
Debug.WriteLine(obj1.Users)
3
Debug.WriteLine(obj2.Users)
3
Obj2.Disconnect()
Debug.WriteLine(obj2.Users)
2
End Sub
Code language: VB.NET (vbnet)
If you run the application, you’ll see the values displayed under each Debug.WriteLine statement in the Output window. As you can see, both the obj1 and obj2 variables access the same value of the Users property. Shared variables are commonly used in classes that run on a server and service multiple applications. In effect, they’re the class’s global variables, which can be shared among all the instances of a class. You can use shared variables to keep track of the total number of rows accessed by all users of the class in a database, connection time, and other similar quantities.