In this section we carefully examine the VB.NET class , which is fundamental to programming in VB.NET. For illustration we introduce two classes, Customer and Hotel , which will be elaborated in a case study that is used throughout the book. We will introduce the case study itself in Chapter 5. Classes as Structured Data VB.NET defines primitive data types that are built into the language. Data types, such as Integer and Decimal , can be used to represent simple data. VB.NET provides the class mechanism to represent more complex forms of data. Through a class, you can build up structured data out of simpler elements, which are called data members , or fields . (See TestCustomer\Step0 .) ' Customer.vb - Step 0 (same as Step 1) Public Class Customer Public CustomerId As Integer Public FirstName As String Public LastName As String Public EmailAddress As String Public Sub New(_ ByVal first As String, _ ByVal last As String, _ ByVal email As String) FirstName = first LastName = last EmailAddress = email End Sub End Class Customer is now a new data type. A Customer class object has a CustomerId , a FirstName , a LastName , and an EmailAddress . Classes and Objects A class represents a "kind of," or type of, data. It is somewhat analogous to the built-in types like Integer and Decimal . A class can be thought of as a template from which individual instances can be created. An instance of a class is called an object. The fields, such as CustomerId and FirstName in our example, are sometimes also called instance variables . References There is a fundamental distinction between the primitive data types, such as Integer , and the extended data types that can be created using classes. When you declare a variable of a primitive data, you are allocating memory and creating the actual instance. This is because the variable itself contains its own data value. Dim x as Integer '4 bytes of memory have been allocated When you declare a variable of a class type (an object reference ), you are only obtaining memory for a reference to an object of the class type. No memory is allocated for the object itself, which may be quite large. This behavior is identical to what happens in Java. 'cust is a reference to a Customer object Dim cust As Customer ' The object itself does not yet exist Constructors Through a constructor, you can initialize individual objects in any way you wish. Besides initializing instance data, you can perform other appropriate initializations (e.g., open a file). A constructor is like a special method that is automatically called when an object is created via the New keyword. A constructor -
has no return type (i.e., a subroutine) -
has the name New -
should usually have public access -
may take parameters, which are passed when invoking New You use the New keyword to instantiate object instances, and you pass desired values as parameters. Default Constructor If you do not define a constructor in your class, VB.NET will implicitly create one for you. It is called the default constructor and takes no arguments. The default constructor will assign instance data, using any assignments in the class definition. Fields without an initializer are assigned default values (0 for numerical data types, empty string for String , and so on.) The default constructor is called when an object instance is created with New and no parameters. If you provide code for any constructor in your class, you must explicitly define a default constructor with no arguments, if you want one. Instantiating and Using an Object You instantiate an object by the New operator, which will cause a constructor to be invoked. cust = New Customer(_ "Rocket", _ "Squirrel", _ "rocky@frosbitefalls.com") ' Customer object now exists and cust is a reference to it Once an object exists, you work with it, including accessing its fields and methods . Our simple Customer class at this point has no methods, only four fields. You access fields and methods using a dot. cust.CustomerId = 1 ' all fields have now been assigned TestCustomer\Step0 provides a simple test program to exercise the Customer class. Note that an unassigned field of a class receives a default value, such as 0, when an object is instantiated . Assigning Object References TestCustomer\Step1 provides a more complete test program to exercise the Customer class. Two object instances are created, an assignment is made of one object reference to another, and a field is assigned a value. ' TestCustomer.vb - Step1 Imports System Module TestCustomer Public Sub Main() Dim cust1, cust2 As Customer cust1 = New Customer(_ "Rocket", _ "Squirrel", _ "rocky@frosbitefalls.com") cust1.CustomerId = 1 cust2 = New Customer(_ "Bullwinkle", _ "Moose", _ "moose@wossamotta.edu") cust2.CustomerId = 2 ShowCustomer("cust1", cust1) ShowCustomer("cust2", cust2) cust1 = cust2 ' cust1, cust2 refer to same object cust1.EmailAddress = "bob@podunk.edu" ShowCustomer("cust1", cust1) ShowCustomer("cust2", cust2) End Sub Private Sub ShowCustomer(_ ByVal label As String, ByVal cust As Customer) Console.WriteLine("---- {0} ----", label) Console.WriteLine(_ "CustomerId = {0}", cust.CustomerId) Console.WriteLine(_ "FirstName = {0}", cust.FirstName) Console.WriteLine(_ "LastName = {0}", cust.LastName) Console.WriteLine(_ "EmailAddress = {0}", cust.EmailAddress) End Sub End Module Figure 3-9 shows the object references cust1 and cust2 and the data they refer to after the objects have been instantiated and the CustomerId field has been assigned. Figure 3-9. Two object references and the data they refer to. When you assign an object variable, you are assigning only the reference; there is no copying of data . [3] Figure 3-10 shows both object references and their data after the assignment: [3] C and C++ programmers will recognize assignment of references as similar to assignment of pointers. Figure 3-10. Two references refer to the same data. cust1 = cust2 ' cust1, cust2 refer to same object Now consider what happens when you assign a new value to a field of one object, cust1.EmailAddress = "bob@podunk.edu" You will now see the same data through both object references, since they refer to the same object. Here is the output from running TestCustomer\Step1 . ---- cust1 ---- CustomerId = 1 FirstName = Rocket LastName = Squirrel EmailAddress = rocky@frosbitefalls.com ---- cust2 ---- CustomerId = 2 FirstName = Bullwinkle LastName = Moose EmailAddress = moose@wossamotta.edu ---- cust1 ---- CustomerId = 2 FirstName = Bullwinkle LastName = Moose EmailAddress = bob@podunk.edu ---- cust2 ---- CustomerId = 2 FirstName = Bullwinkle LastName = Moose EmailAddress = bob@podunk.edu Garbage Collection Through the assignment of a reference, an object may become orphaned. Objects may also be orphaned when they pass out of scope (i.e., when a local reference variable is lost as the method it is declared in returns). Such an orphan object (or "garbage") takes up memory in the computer, which can now never be referenced. In Figure 3-2 the customer with CustomerId of 1 is now garbage. The CLR automatically reclaims the memory of unreferenced objects. This process is known as garbage collection . Garbage collection takes up some execution time, but it is a great convenience for programmers, helping to avoid a common program error known as a memory leak . [4] Garbage collection is discussed in more detail in Chapter 10. [4] Memory leaks have always plagued C and C++ programmers, but languages such as VB.NET, previous versions of Visual Basic, and Java have been spared this nightmare. Methods Typically, a class will specify behavior as well as data. A class encapsulates data and behavior in a single entity. A method specifies the behavior and consists of either a Function or a Sub defined within the class, with the following characteristics: -
An optional access specifier , typically Public or Private -
A return type for a Function , (a Sub is used if no data is returned) -
A method name, which can be any legal VB.NET identifier -
A parameter list, enclosed by parentheses, which specifies data that is passed to the method (can be empty if no data is passed) -
A method body, enclosed by Function and End Function or Sub and End Sub , which contains the code that the method will execute Here is an example of a method in the Hotel class taken from the Hotel.vb source file in the TestHotel\Step1 example. Public Sub RaisePrice (ByVal amount As Decimal) rate += amount End Sub In this example there is no data returned, so it is implemented with a Sub . The method name is RaisePrice , the parameter list consists of a single parameter of type Decimal , and the body contains one line of code that increments the member variable rate by the value that is passed in as a parameter. Public and Private Fields and methods of a VB.NET class can be specified as Public or Private . Normally, you declare fields as Private . A private field can be accessed only from within the methods defined in the same class, not from outside the class. Public Class Hotel Private city As String Private name As String Private number As Integer = 50 Private rate As Decimal ... Note that in VB.NET you can initialize fields where they are declared, as shown in the case of the field named number . Methods may be declared as either Public or Private . Public methods can be called from outside of the class and are often used to perform calculations and to manipulate private field data. You may also provide public "accessor" methods to provide access to private fields. ... Public Function GetRate() As Decimal Return rate End Function Public Sub SetRate(ByVal val As Decimal) rate = val End Sub ... You may also have private methods, which can be thought of as helper functions for use within the class. Rather than duplicating code in several places, you may create a private method, which will be called wherever it is needed internally. An example is the ShowHotel method in TestHotel.vb . The Keyword Me Sometimes it is convenient within code for a method to be able to access the current object reference. VB.NET defines the keyword Me (similar to the keyword this in C++, Java, and C#), which is a special variable that always refers to the current object instance. With Me you can then refer to the instance variables, but usually, you can simply refer to them directly without using Me explicitly. The Hotel class has a constructor to initialize its instance data with values passed as parameters. We can make use of the same names for parameters and fields and remove ambiguity by using the Me variable explicitly. Here is the code for the Hotel constructor: Public Sub New(_ ByVal city As String, _ ByVal name As String, _ ByVal number As Integer, _ ByVal rate As Decimal) Me .city = city Me .name = name Me .number = number Me .rate = rate End Sub Sample Program The program TestHotel\Step1 illustrates all the features we have discussed so far. Here is the class definition: ' Hotel.vb - Step 1 Public Class Hotel Private city As String Private name As String Private number As Integer = 50 Private rate As Decimal Public Sub New(_ ByVal city As String, _ ByVal name As String, _ ByVal number As Integer, _ ByVal rate As Decimal) Me.city = city Me.name = name Me.number = number Me.rate = rate End Sub Public Sub New() End Sub Public Function GetCity() As String Return city End Function Public Function GetName() As String Return name End Function Public Function GetNumber() As Integer Return number End Function Public Sub SetNumber(ByVal val As Integer) number = val End Sub Public Function GetRate() As Decimal Return rate End Function Public Sub SetRate(ByVal val As Decimal) rate = val End Sub Public Sub RaisePrice(ByVal amount As Decimal) rate += amount End Sub End Class Here is the test program: ' TestHotel.vb - Step 1 Imports System Module TestHotel Sub Main() Dim generic As Hotel = New Hotel() ShowHotel(generic) Dim ritz As Hotel = New Hotel(_ "Atlanta", "Ritz", 100, 95D) ShowHotel(ritz) ritz.RaisePrice(50D) ritz.SetNumber(125) ShowHotel(ritz) End Sub Private Sub ShowHotel(ByVal hotel As Hotel) Console.WriteLine(_ "{0} {1}: number = {2}, rate = {3:C}", _ hotel.GetCity(), hotel.GetName(), _ hotel.GetNumber(), hotel.GetRate()) End Sub End Module Here is the output: : number = 50, rate = : number = 50, rate = $0.00 Atlanta Ritz: number = 100, rate = $95.00 Atlanta Ritz: number = 125, rate = $145.00 .00 Atlanta Ritz: number = 100, rate = .00 Atlanta Ritz: number = 125, rate = 5.00 Properties The encapsulation principle leads us to typically store data in private fields and to provide access to this data through public accessor methods that allow us to set and get values. For example, in the Hotel class we provide a method GetCity to access the private field city . You don't need any special syntax; you can simply provide methods and call these methods what you want, typically GetXXX and SetXXX . VB.NET provides a special property syntax that simplifies client code. You can access a private field as if it were a public member. Here is an example of using the Number property of the Hotel class. ritz.Number = 125 Console.WriteLine(_ "There are now {0} rooms", ritz.Number ) As you can see, the syntax using the property is a little more concise . Properties were popularized in Visual Basic and are now part of .NET and available in selected other .NET languages, such as C# and managed C++. The program TestHotel\Step2 illustrates implementing and using several properties, City , Name , Number and Rate . The first two properties are read-only (only Get defined), and the other properties are read/write (both Get and Set ). It is also possible to have a write-only property (only Set defined). Here is the code for the properties Name (read-only) and Number (read-write) in the second version of the Hotel class. Notice the syntax and the VB.NET keyword Value to indicate the new value of the field. The private fields are now spelled with an "m_" prefix (short for member variable) to distinguish them from the corresponding property names, preventing a compiler error. ' Hotel.vb - Step 2 Public Class Hotel Private m_City As String Private m_Name As String Private m_Number As Integer Private m_Rate As Decimal ... Public ReadOnly Property Name() As String Get Name = m_Name End Get End Property Public Property Number() As Integer Get Number = m_Number End Get Set(ByVal Value As Integer) m_Number = Value End Set End Property .. . Shared Fields and Methods In VB.NET, a field normally is defined on a per-instance basis, with a unique value for each object instance of the class. Sometimes it is useful to have a single value associated with the entire class as a whole. This type of field is called a Shared field, or a class variable as opposed to an instance variable. Like instance data members, shared data members can be either Public or Private . To access a public shared member, you use the dot notation, but in place of an object reference before the dot, you use the name of the class. Shared Methods A method may also be declared as Shared . A shared method can be called without instantiating the class. If you declare the Main method in a module, you do not declare it as shared, since all methods in a module are inherently shared; however, if you declare a Main method in a class, you must declare it as shared. This allows the runtime system to call Main without instantiating an object. You call a shared method by using the dot notation, with the class name in front of the dot. You can call a shared method without an instance, and a shared method can only access shared data members and not instance data members. Shared methods may be declared Public or Private . A private shared method, like other private methods, may be used as a helper function within a class, but not called from outside the class. Sample Program Our previous Customer class relied on the user of the class to assign a CustomerId for the customer. A better approach is to encapsulate the assignment of this field within the class itself, so that a unique ID will be automatically generated every time a new Customer object is created. It is easy to implement such a scheme by using a shared field nextCustId , which is used to assign a new ID. Every time the ID is assigned, nextCustId is incremented. TestCustomer\Step2 demonstrates this solution and also illustrates the use of a shared method. Here is the code defining the Customer class: ' Customer.vb - Step 2 Public Class Customer Public CustomerId As Integer Public FirstName As String Public LastName As String Public EmailAddress As String Private Shared nextCustId As Integer = 1 Public Sub New(_ ByVal first As String, _ ByVal last As String, _ ByVal email As String) CustomerId = nextCustId nextCustId += 1 FirstName = first LastName = last EmailAddress = email End Sub Public Shared Function GetNextId() As Integer Return nextCustId End Function End Class Here is the test program: ' TestCustomer.vb - Step 2 Imports System Module TestCustomer Sub Main() Console.WriteLine("next id = {0}", _ Customer.GetNextId()) Dim cust1, cust2 As Customer cust1 = new Customer("John", "Doe", _ "john@rocky.com") cust2 = new Customer("Mary", "Smith", _ "mary@moose.edu") ShowCustomer("cust1", cust1) ShowCustomer("cust2", cust2) End Sub Private Sub ShowCustomer(_ ByVal label As String, ByVal cust As Customer) ... Note that the shared method GetNextId is accessed through the class Customer and not through an object reference such as cust1 . Note the fact that Main is an implicitly shared method, since it is a member of a module, and is invoked by the runtime without an instance of an object being created. Here is the output from the program: next id = 1 ---- cust1 ---- CustomerId = 1 FirstName = John LastName = Doe EmailAddress = john@rocky.com ---- cust2 ---- CustomerId = 2 FirstName = Mary LastName = Smith EmailAddress = mary@moose.edu Shared Constructor Besides having shared fields and shared methods, a class may also have a shared constructor . A shared constructor is called only once, before any object instances have been created. A shared constructor is defined by prefixing the constructor with Shared . A shared constructor must take no parameters and has no access modifier (such as Public or Private ). In some languages, such as C++, where there can be global variables not attached to any class, you may initialize a library through the constructor for a global object. In VB.NET there are no such freestanding global objects, but you can achieve similar initialization through use of a shared constructor. As a somewhat whimsical example of a shared constructor, consider the SharedWorld program, which provides an alternative implementation of "Hello, World." This program displays the text Hello, World, proving that the shared constructor automatically executes first, displaying the text Hello, . Then later, the call to the World method displays the word World. ' SharedWorld.vb Public Class Hello Shared Sub New() System.Console.Write("Hello, ") End Sub Public Shared Sub World() System.Console.WriteLine("World") End Sub End Class Module Module1 Sub Main() Hello.World() End Sub End Module Constant and Read Only Fields If you want to make sure that a variable or field always has the same value, you can assign the value via an initializer and use the Const modifier. After its one-time initialization at runtime, the value cannot be changed. You can also define a field or property to be ReadOnly , with the same effect. The program ConstantHotel illustrates the use of both Const and Read-Only . In both cases, you will get a compiler error if you try to modify the value after it has been initialized . ' ConstantHotel.vb Public Class Hotel Public Const rate As Decimal = 100D Public ReadOnly name As String Public Sub New(ByVal name As String) Me.name = name End Sub End Class Here is the test program: ' TestHotel.vb Imports System Module TestHotel Sub Main() Dim Hotel As Hotel = New Hotel("Ritz") Console.WriteLine("rate = {0:C}", Hotel.rate) ' Hotel.rate = 150D // illegal Console.WriteLine("hotel name = {0}", Hotel.name) ' hotel.name = "Sheraton" // illegal End Sub End Module Here is the output: rate = 0.00 hotel name = Ritz |