Previous section   Next section

5.10 Encapsulating Data with Properties

It is generally desirable to designate the member variables of a class as Private (using the Private keyword). This means that only member methods of that class can access their value. You make member variables private to support data hiding, which is part of the encapsulation of a class.

Typically, most methods will be public (designated by the Public keyword). The public members of your class constitute a contract between your class and the clients of your class. Any object that interacts with your class is a client. Your public methods promise that if the client calls them with the right parameters, they will perform the promised action. How your methods perform that object is not part of the public contract. That is up to your class.

You can also have some private methods, known as helper methods, whose job it is to do work for methods of your class, but which are not available to clients. The private member variables and private methods are not part of your public contract; they are hidden details of the implementation of your class.

Object-oriented programmers are told that member variables should be private. That is fine, but how do you provide access to this data to your clients? The answer for VB.NET programmers is properties.

Properties allow clients to access class state as if they were accessing member fields directly, while actually implementing that access through a class method.

This is ideal. The client wants direct access to the state of the object. The class designer, however, wants to hide the internal state of his class (perhaps in class fields), and provide indirect access through a method. The property provides both: the illusion of direct access for the client, the reality of indirect access for the class developer.

By decoupling the class state from the method that accesses that state, the designer is free to change the internal state of the object as needed. When the Time class is first created, the Hour value might be stored as a member variable. When the class is redesigned, the Hour value might be computed, or retrieved from a database. If the client had direct access to the original Hour member variable, the change to computing the value would break the client. By decoupling and forcing the client to go through a property, the Time class can change how it manages its internal state without breaking client code.

In short, properties provide the data hiding required by good object-oriented design. Example 5-10 creates a property called Hour, which is then discussed in the paragraphs that follow.

It is a convention in VB.NET to give your private member variables names with a prefix to distinguish them from the property name. For example, you might prefix every member variable with the letter m (for member), thus mMinute and mHour. You are then free to use the unprefixed version (Hour and Minute) for the property. By convention, properties are named with Pascal notation (first letters are uppercase).

Example 5-10. Properties
Option Strict On
Imports System

Public Class Time
   ' private member variables
   Private mYear As Integer
   Private mMonth As Integer
   Private mDayOfMonth As Integer
   Private mHour As Integer
   Private mMinute As Integer
   Private mSecond As Integer

   Property Hour( ) As Integer
      Get
         Return mHour
      End Get
      Set(ByVal Value As Integer)
         mHour = Value
      End Set
   End Property


   ' public accessor methods
   Public Sub DisplayCurrentTime( )
      Console.WriteLine( _
      "{0}/{1}/{2} {3}:{4}:{5}", _
      mMonth, mDayOfMonth, mYear, Hour, mMinute, mSecond)
   End Sub 'DisplayCurrentTime


   ' constructors
   Public Sub New(ByVal dt As DateTime)
      mYear = dt.Year
      mMonth = dt.Month
      mDayOfMonth = dt.Day
      mHour = dt.Hour
      mMinute = dt.Minute
      mSecond = dt.Second
   End Sub 'New


   Public Sub New( _
   ByVal mYear As Integer, _
   ByVal mMonth As Integer, _
   ByVal mDayOfMonth As Integer, _
   ByVal mHour As Integer, _
   ByVal mMinute As Integer, _
   ByVal mSecond As Integer)
      Me.mYear = mYear
      Me.mMonth = mMonth
      Me.mDayOfMonth = mDayOfMonth
      Me.Hour = mHour
      Me.mMinute = mMinute
      Me.mSecond = mSecond
   End Sub 'New

End Class 'Time


Module Module1

   Sub Main( )
      Dim currentTime As DateTime = DateTime.Now
      Dim time1 As New Time(currentTime)
      time1.DisplayCurrentTime( )

      'extract the hour to a local variable
      Dim theHour As Integer = time1.Hour

      'display the local variable
      Console.WriteLine("Retrieved the hour: {0}", _
       theHour)

      'add one to the local variable
      theHour += 1

      'write the time back to the object
      time1.Hour = theHour

      'display the result
      Console.WriteLine("Updated the hour: {0}", _
       time1.Hour)

   End Sub

End Module

Output:
5/1/2002 8:56:59
Retrieved the hour: 8
Updated the hour: 9

You create a property with this syntax:

Property Identifier( ) As Type
  Get
    statements
  End Get

  Set(ByVal Value As Type)
     statements
  End Set
End Property

If you create the property in Visual Studio .NET however, the editor will provide extensive help with the syntax. For example, once you type:

Property Minute As Integer

the IDE will reformat your property as follows:

Property Minute( ) As Integer
   Get

   End Get
   Set(ByVal Value As Integer)

   End Set
End Property

In Example 5-10, Hour is a property. Its declaration creates two accessors: Getand Set.

Property Hour( ) As Integer
   Get
      Return mHour
   End Get
   Set(ByVal Value As Integer)
      mHour = Value
   End Set
End Property

Each accessor has an accessor-body that does the work of retrieving and setting the property value. The property value might be stored in a database (in which case the accessor would do whatever work is needed to interact with the database), or it might just be stored in a private member variable (in this case, mHour):

Private mHour As Integer

5.10.1 The Get Accessor

The body of the Get accessor is similar to a class method that returns an object of the type of the property. In Example 5-10, the accessor for the Hour property is similar to a method that returns an integer. It returns the value of the private member variable mHour in which the value of the property has been stored:

Get
   Return mHour
End Get

In this example, the value of mHour is returned, but you could just as easily retrieve an Integer value from a database or compute it on the fly.

Whenever you reference the property (other than to assign to it), the Get accessor is invoked to read the value of the property. For example, in the following code the value of the Time object's Hour property is assigned to a local variable. What actually happens is that the Get accessor is called, which returns the value of the Hour member variable, and that value is assigned to the local variable named theHour:

Dim time1 As New Time(currentTime)
Dim theHour As Integer = time1.Hour

5.10.2 The Set Accessor

The Set accessor sets the value of a property. Set has an implicit parameter, Value, that represents the assigned value. That is, when you write:

Minute = 5

the compiler passes the value you are assigning (5) to the Set statement as the Value parameter. You can then set the member variable to that value using the keyword:

mMinute = Value

The advantage of this approach is that the client can interact with the properties directly, without sacrificing the data hiding and encapsulation sacrosanct in good object-oriented design.

5.10.3 ReadOnly and WriteOnly Properties

At times you may want to create a property that allows the client to retrieve a value but not to set it. You can mark your property ReadOnly, as in the following:

ReadOnly Property Hour( ) As Integer

Doing so allows you (and forces you) to leave out the Set accessor in your property. If you do add a Set accessor, the compiler will complain with the message:

Properties declared 'ReadOnly' cannot have a 'Set'

If you leave out the Set accessor and then try to assign to the property, the compiler will complain with the message:

Property 'Hour' is 'ReadOnly'

In short, marking the property ReadOnly enlists the compiler in enforcing that you can not use that property to set a value.

Similarly, you can mark a property WriteOnly:

WriteOnly Property Hour( ) As Integer

Doing so will cause the compiler to enforce that your property must have a Set and must not have a Get accessor. If you leave out the Get or Set without marking the property WriteOnly or ReadOnly, respectively, you will receive a compile error.

You are not permitted to combine ReadOnly with WriteOnly, but this is not much of a burden.


  Previous section   Next section
Top