Methods that Return Values
If we have a method that does generate some
value that should be returned, we need to use the Function
keyword:
Public Function Age() As Integer
Return DateDiff(DateInterval.Year, mdtBirthDate, Now())
End
Function
Notice that we need to indicate the data type of the return
value when we declare a Function.
In this example, we are
returning the calculated age as a result of the method. We can return any value
of the appropriate data type by using the Return
keyword.
We can also return the value without using the Return
keyword, by setting the value of the function name itself:
Public Function Age() As Integer
Age = DateDiff(DateInterval.Year, mdtBirthDate, Now())
End Function
This is functionally equivalent to the previous code. Either
way, we can use this method with code similar to the following:
Dim myPerson As New Person()
Dim intAge As Integer
intAge = myPerson.Age()
The Age
method returns an Integer
data value that we can use in our program as required in this case we're just
storing it into a variable.
Indicating Method Scope
Adding the appropriate keyword in front
of the method declaration indicates the scope:
This indicates that Walk
is a Public
method and is thus available to code outside our class and even outside our
current project. Any application that references our assembly can make use of
this method. By being Public, this method becomes part of our object's
interface.
On the other hand, we might choose to restrict the method
somewhat:
By declaring the method with the Friend
keyword, we are indicating that it should be part of our object's interface
only for code inside our project any other applications or projects that make
use of our assembly will not be able to call the Walk
method.
Private Function Age() As Integer
The Private
keyword indicates that a
method is only available to the code within our particular class. Private
methods are very useful to help us organize complex code within each class.
Sometimes our methods will contain very lengthy and complex code. In order to
make this code more understandable, we may choose to break it up into several
smaller routines, having our main method call these routines in the proper
order. Additionally, we may use these routines from several places within our
class and so, by making them separate methods, we enable reuse of the code.
These sub-routines should never be called by code outside our object and so
we make them Private.
Method Parameters
We will often want to pass information into a method as we call it. This
information is provided via parameters to the method. For instance, in our Person class, perhaps we want our Walk method to track the distance the person walks over
time. In such a case, the Walk
method would need to know how far the person is to walk each time the method is
called. Add the following code to our Person class:
Public Class Person
Private mstrName As String
Private mdtBirthDate As Date
Private mintTotalDistance As Integer
Public Sub Walk(ByVal Distance As Integer)
mintTotalDistance += Distance
End
Sub
Public Function Age() As Integer
Return DateDiff(DateInterval.Year, mdtBirthDate, Now())
End
Function
End Class
With this implementation, a Person
object will sum up all of the distances that are walked over time. Each time
the Walk
method is called, the calling code must pass an Integer
value indicating the distance to be walked. Our code to call this method would
be similar to the following:
Dim myPerson As New Person()
myPerson.Walk(12)
The parameter is accepted using the ByVal
keyword. This indicates that the parameter value is a copy of the
original value. This is the default way VB.NET accepts
all parameters. Typically, this is desirable because it means that we can work
with the parameter inside our code including changing its value with no
risk of accidentally changing the original value back in the calling code.
If we do want to be able to change the value in the calling
code, we can change the declaration to pass the parameter by reference by using
the ByRef
qualifier:
Public Sub Walk(ByRef Distance As Integer)
In this case, we'll get a reference (or pointer) back to the
original value rather than receiving a copy. This means that any change we make
to the Distance
parameter will be reflected back in the calling code very similar to the way
object references work, as we discussed earlier in Chapter 4.
Using this technique can be dangerous, since it
is not explicitly clear to the caller of our method that the value will change.
Such unintended side effects can be hard to debug and should be avoided.
Properties
The .NET environment provides for a specialized type of method
called a property.
A property is a method specifically designed for setting and retrieving data
values. For instance, we declared a variable in our Person
class to contain a name, so our Person
class may include code to
allow that name to be set and retrieved. This could be done using regular
methods:
Public Sub SetName(ByVal Name As String)
mstrName = Name
End
Sub
Public Function GetName() As String
Return mstrName
End
Function
Using methods like these, we would write code to interact
with our object such as:
Dim myPerson As New Person()
myPerson.SetName("Jones")
MsgBox(myPerson.GetName())
While this is perfectly acceptable, it is not as nice as it
could be through the use of a property. A Property
style method consolidates
the setting and retrieving of a value into a single structure, and also makes
the code within our class smoother overall. We can rewrite these two methods
into a single property. Add the following code to the Person
class:
Public Property Name() As String
Get
Return mstrName
End Get
Set(ByVal Value As String)
mstrName = Value
End Set
End
Property
By using a property method instead, we can make our client
code much more readable:
Dim myPerson As New Person()
myPerson.Name = "Jones"
MsgBox(myPerson.Name)
The Property
method is declared
with both a scope and a data type:
Public Property Name() As String
In this example, we've declared the property as Public
in scope, but it can be declared using the same scope options as any other
method Public,
Friend,
Private,
or Protected.
As with other methods, a Public
property is accessible to any
code outside our class, while Friend
is available outside our
class, but only to code within our VB project. Protected
properties are available through inheritance, as we'll discuss in Chapter 6,
and Private
properties are only available to code within our class.
The return data type of
this property is String. A property can return virtually any data type as
appropriate for the nature of the value. In this regard, a property is very
similar to a method declared using the Function
keyword.
Though a Property
method is a single
structure, it is divided into two parts: a getter and a setter. The getter is
contained within a Get...End Get
block and is responsible for returning the value of the property on demand:
Get
Return mstrName
End Get
Though the code in this example is very simple, it could be
more complex perhaps calculating the value to be returned or applying other
business logic to change the value as it is returned.
Likewise, the code to change the value is contained within a
Set...End Set
block:
Set(ByVal Value As String)
mstrName = Value
End Set
The Set
statement accepts a single parameter value that
stores the new value. Our code in the block can then use this value to set the
property's value as appropriate. The data type of this parameter must match the
data type of the property itself. By having the parameter declared in this
manner, we can change the variable name used for the parameter value if needed.
By default, the parameter is named Value.
However, if we dislike the name Value,
we can change the parameter
name to something else, for example:
Set(ByVal NewName As String)
mstrName = NewName
End Set
In many cases, we may apply business rules or other logic
within this routine to ensure that the new value is appropriate before we
actually update the data within our object.
Parameterized Properties
The Name
property we created is an example of a
single-value property. We can also create property arrays or parameterized
properties. These properties reflect a range, or array, of values. As an
example, a person will often have several phone numbers. We might implement a PhoneNumber
property as a parameterized property storing not only phone numbers, but also
a description of each number. To retrieve a specific phone number we'd write
code such as:
Dim myPerson As New Person()
Dim strHomePhone As String
strHomePhone =
myPerson.Phone("home")
Or, to add or change a specific phone number, we'd write:
myPerson.Phone("work") =
"555-9876"
Not only are we retrieving and updating a phone number
property, but also we're updating some specific phone number. This implies a
couple of things. First off, we're no longer able to use a simple variable to
hold the phone number, since we are now storing a list of numbers and their
associated names. Secondly, we've effectively added a parameter to our property
we're actually passing the name of the phone number as a parameter on each
property call.
To store the list of phone numbers we can use the Hashtable
class. The Hashtable
is very similar to the standard VB Collection
object, but it is more
powerful allowing us to test for the existence of an existing element. Add
the following declaration to the Person
class:
Public Class Person
Private
mstrName As String
Private mdtBirthDate As Date
Private mintTotalDistance As Integer
Private colPhones As New Hashtable()
We can implement the Phone
property by adding the
following code to our Person class:
Public Property Phone(ByVal Location As String) As String
Get
Return CStr(colPhones.Item(Location))
End Get
Set(ByVal Value As String)
If colPhones.ContainsKey(Location) Then
colPhones.Item(Location) = Value
Else
colPhones.Add(Location, Value)
End If
End Set
End
Property
The declaration of the Property
method itself is a bit
different from what we've seen:
Public Property Phone(ByVal Location As String) As String
In particular, we've added a parameter, Location,
to the property itself. This parameter will act as the index into our list of
phone numbers and must be provided both when setting or retrieving phone number
values.
Since the Location
parameter is declared at the
Property
level, it is available to all code within the property including both the Get
and Set
blocks.
Within our Get
block, we use the Location
parameter to select the appropriate phone number to return from the Hashtable:
Get
Return colPhones.Item(Location)
End Get
With this code, if there is no value stored matching the Location,
we'll get a trappable runtime error.
Similarly, in the Set
block, we use the Location to update or add the appropriate element in the
Hashtable.
In this case, we're using the ContainsKey
method of Hashtable
to determine whether the phone number already exists in the list. If it does,
we'll simply update the value in the list otherwise, we'll add a new element
to the list for the value:
Set(ByVal Value As String)
If colPhones.ContainsKey(Location) Then
colPhones.Item(Location) = Value
Else
colPhones.Add(Location, Value)
End If
End Set
In this way, we're able to add or update a specific phone
number entry based on the parameter passed by the calling code.
Read-Only Properties
There are times when we may want a property to be read-only
so that it can't be
changed. In our Person class, for instance, we may have a read-write
property for BirthDate,
but just a read-only property for Age.
In such a case, the BirthDate
property is a normal property, as follows:
Public Property BirthDate() As Date
Get
Return mdtBirthDate
End Get
Set(ByVal Value As Date)
mdtBirthDate = Value
End Set
End
Property
The Age
value, on the other hand, is a
derived value based on BirthDate.
This is not a value that
should ever be directly altered and, thus, is a perfect candidate for read-only
status.
We already have an Age
method implemented as a Function.
Remove that code from the Person
class, as we'll be replacing
it with a Property
routine instead.
The difference between a Function
routine and a ReadOnlyProperty
is quite subtle. Both return
a value to the calling code and, either way, our object is running a subroutine
defined by our class module to return the value.
The difference is less a programmatic one than a design
choice. We could create all our objects without any Property
routines at all, just using methods for all interactions with the object.
However, Property
routines are obviously attributes of the object, while a Function
might be an attribute or a method. By carefully implementing all attributes as ReadOnly
Property
routines, and any interrogative methods as Function
routines, we will create
more readable and understandable code.
To make a property read-only, we use the ReadOnly
keyword and only implement the Get
block:
Public ReadOnly Property Age() As Integer
Get
Return CInt(DateDiff(DateInterval.Year, mdtBirthDate, Now()))
End Get
End
Property
Since the property is read-only, we'll get a syntax error if
we attempt to implement a Set
block.
Write-Only Properties
As with read-only properties, there are times when a property
should be write-only where the value
can be changed, but not retrieved.
Many people have allergies, so perhaps our Person
object should have some understanding of the ambient allergens in the area.
This is not a property that should be read from the Person
object since allergens come from the environment rather than from the person,
but it is data that the Person
object needs in order to
function properly. Add the following variable declaration to our class:
Public Class Person
Private mstrName As String
Private mdtBirthDate As Date
Private mintTotalDistance As Integer
Private colPhones As New Hashtable()
Private mintAllergens As Integer
We can implement an AmbientAllergens
property as follows:
Public WriteOnly Property AmbientAllergens() As Integer
Set(ByVal Value As Integer)
mintAllergens = Value
End Set
End
Property
To create a write-only property, we use the WriteOnly
keyword and only implement a Set
block in our code. Since the
property is write-only, we'll get a syntax error if we attempt to implement a
Get block.
Objects can implement a default property if desired. A default
property can be
used to simplify the use of our object at times, by making it appear as if our
object has a native value. A good example of this behavior is the Collection
object, which has a default property called Item
that returns the value of a
specific item, allowing us to write code similar to:
Dim colData As New Collection()
Return colData(Index)
Default properties must
be parameterized properties. A property without a parameter cannot be
marked as the default. This is a change from previous versions of VB, where
any property could be marked as the default.
Our Person
class has a parameterized
property the Phone
property we built earlier. We can make this the default property by using the Default
keyword:
Default Public Property Phone(ByVal Location As String) As String
Get
Return
colPhones.Item(Location)
End Get
Set(ByVal Value As
String)
If
colPhones.ContainsKey(Location) Then
colPhones.Item(Location)
= Value
Else
colPhones.Add(Location,
Value)
End If
End Set
End Property
Prior to this change, we would need code such as the
following to use the Phone property:
Dim myPerson As New Person()
MyPerson.Phone("home") =
"555-1234"
But now, with the property marked as Default,
we can simplify our code:
myPerson("home") =
"555-1234"
By picking appropriate default properties, we can
potentially make the use of our objects more intuitive.