In this section you’ll learn about an interesting (but quite optional) feature of class design: how to customize the usual operators. Some operators in Visual Basic act differently on various types of data. The addition operator ( + ) is the most typical example. When used with numbers, the addition operator adds them. When used with strings, however, it concatenates the strings. The same operator can perform even more complicated calculations with the more-elaborate data types.
When you add two variables of the TimeSpan type, the addition operator adds their durations and returns a new TimeSpan object. If you execute the following statements, the value 3882 will be printed in the Output window (the number of seconds in a time span of 1 hour, 4 minutes, and 42 seconds):
Dim TS1 As New TimeSpan(1, 0, 30)
Dim TS2 As New TimeSpan(0, 4, 12)
Debug.WriteLine((TS1 + TS2).TotalSeconds.ToString)
Code language: PHP (php)
The TimeSpan class is discussed in detail in Chapter 13, “Handling Strings, Characters, and Dates,” but for the purposes of the preceding example, all you need to know is that variable TS1 represents a time span of 1 hour and 30 seconds, while TS2 represents a time span of 4 minutes and 12 seconds. Their sum is a new time span of 1 hour, 4 minutes and 42 seconds. So far you saw how to overload methods and how the overloaded forms of a method can simplify development.
Sometimes it makes sense to alter the default function of an operator. Let’s say you designed a class for representing lengths in meters and centimeters, something like the following:
Dim MU As New MetricUnits
MU.Meters = 1
MU.Centimeters = 78
Code language: PHP (php)
The MetricUnits class allows you to specify lengths as an integer number of meters and centimeters (presumably, you don’t need any more accuracy). The most common operation you’ll perform with this class is to add and subtract lengths. However, you can’t directly add two objects of the MetricUnits type by using a statement such as this:
TotalLength = MU1 + MU2
Wouldn’t it be nice if you could add two custom objects by using the addition operator? For this to happen, you should be able to overload the addition operator, just as you can overload a method. Indeed, it’s possible to overload an operator for your custom classes and write statements like the preceding one. Let’s design a class to express lengths in metric and English units, and then overload the basic operators for this class.
To overload an operator, you must create an Operator procedure, which is basically a function with an odd name: the name of the operator you want to overload. The Operator procedure accepts as arguments two values of the custom type (the type for which you’re overloading the operator) and returns a value of the same type. Here’s the outline of an Operator procedure that overloads the addition operator:
Public Shared Operator + ( _
ByVal length1 As MetricUnits, _
ByVal length2 As MetricUnits) As MetricUnits
End Operator
Code language: PHP (php)
The procedure’s body contains the statements that add the two arguments as units of length, not as numeric values. Overloading operators is a straightforward process that can help you create elegant classes that can be manipulated with the common operators.
The LengthUnits Class Example
To demonstrate the overloading of common operators, I am listing you the LengthUnits Class example, which is a simple class for representing distances in English and metric units. Listing 6.27 shows the definition of the MetricUnits class, which represents lengths in meters and centimeters.
Listing 6.27: TheMetricUnits Class
Public Class MetricUnits
Private _Meters As Integer
Private _Centimeters As Integer
Public Sub New()
End Sub
Public Sub New(ByVal meters As Integer, ByVal centimeters As Integer)
Me.Meters = meters
Me.Centimeters = centimeters
End Sub
Public Property Meters() As Integer
Get
Return _Meters
End Get
Set(ByVal Value As Integer)
_Meters = Value
End Set
End Property
Public Property Centimeters() As Integer
Get
Return _Centimeters
End Get
Set(ByVal Value As Integer)
If Value > 100 Then
_Meters = _Meters + Convert.ToInt32(Math.Floor(Value / 100))
_Centimeters = (Value Mod 100)
Else
_Centimeters = Value
End If
End Set
End Property
Public Overloads Function Tostring() As String
Dim str As String = Math.Abs(_Meters).ToString & "." & Math.Abs(_Centimeters).ToString
If _Meters < 0 Or (_Meters = 0 And _Centimeters < 0) Then
str = "-" & str
End If0
Return str
End Function
End Class
Code language: JavaScript (javascript)
The class uses the private variables Meters and Centimeters to store the two values that determine the length of the current instance of the class. These variables are exposed as the Meters and Centimeters properties. Notice the two forms of the constructor and the custom ToString method. Because the calling application may supply a value that exceeds 100 for the Centimeters property, the code that implements the Centimeters property checks for this condition and increases the Meters property, if needed. It allows the calling application to set the Centimeters property to 252, but internally it increases the Meters local variable by 2 and sets the Centimenters local variable to 52. The ToString method returns the value of the current instance of the class as a string such as 1.98, but it inserts a minus sign in front of it if it’s negative. If you open the sample project, you’ll find the implementation of the EnglishUnits class, which represents lengths in feet and inches. The code is quite similar.
There’s nothing out of the ordinary so far; it’s actually a trivial class. We can turn it into a highly usable class by overloading the basic operators for the MetricUnits class: namely the addition and subtraction operators. Add the Operator procedures shown in Listing 6.28 to the class’s code.
Listing 6.28: Overloading Operators for the MetricUnits Class
Public Shared Operator +(ByVal length1 As MetricUnits, _
ByVal length2 As MetricUnits) As MetricUnits
Dim result As New metricUnits
result.Meters = 0
result.Centimeters = length1.Meters * 100 + _
length1.Centimeters + length2.Meters * 100 + length2.Centimeters
Return result
End Operator
Public Shared Operator -(ByVal length1 As MetricUnits, _
ByVal length2 As MetricUnits) As MetricUnits
Dim result As New MetricUnits
result.Meters = 0
result.Centimeters = length1.Meters * 100 + _
length1.Centimeters - length2.Meters * 100 - length2.Centimeters
Return result
End Operator
Code language: PHP (php)
These two procedures turned an ordinary class into an elegant custom data type. You can now create MetricUnits variables in your code and manipulate them with the addition and subtraction operators as if they were simple numeric data types. The following code segment exercises the MetricUnits class:
Public Class frmUnitsTest
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
TextBox1.Clear()
Dim MU1 As New MetricUnits
MU1.Centimeters = 194
TextBox1.AppendText("194 centimeters is " & MU1.Tostring & " meters" & vbCrLf)
Dim MU2 As New MetricUnits
MU2.Meters = 2
MU2.Centimeters = 189
TextBox1.AppendText("1 meter and 189 centimeters is " & MU2.Tostring & " meters " & vbCrLf)
TextBox1.AppendText("194 + 289 centimeters is " & (MU1 + MU2).Tostring & " meters" & vbCrLf)
TextBox1.AppendText("194 - 289 centimeters is " & (MU1 - MU2).Tostring & " meters" & vbCrLf)
If MU1 = MU2 Then
TextBox1.AppendText("MU1 is equal to MU2" & vbCrLf)
Else
TextBox1.AppendText("MU1 and MU2 are not equal" & vbCrLf)
End If
MU2 = -MU1
If MU1 = -MU2 Then
TextBox1.AppendText("MU1 is equal to MU2" & vbCrLf)
Else
TextBox1.AppendText("MU1 and MU2 are not equal" & vbCrLf)
End If
TextBox1.AppendText("The negative of " & MU1.Tostring & " is " & MU2.Tostring & vbCrLf)
MU1.Meters = 4
MU1.Centimeters = 63
Dim EU1 As EnglishUnits = CType(MU1, EnglishUnits)
TextBox1.AppendText("4.62 meters are " & EU1.Tostring & vbCrLf)
MU1 = CType(EU1, MetricUnits)
TextBox1.AppendText(EU1.Tostring & " are " & MU1.Tostring & " meters" & vbCrLf)
If MU1 <> EU1 Then
TextBox1.AppendText("MU1 is not equal to EU1" & vbCrLf)
Else
TextBox1.AppendText("EU1 is equal to ME1" & vbCrLf)
End If
Try
If MU1 <> 32.55 Then
TextBox1.AppendText("MU1 is not equal to EU1" & vbCrLf)
Else
TextBox1.AppendText("EU1 is equal to ME1" & vbCrLf)
End If
Catch ex As Exception
TextBox1.AppendText(ex.Message & vbCrLf)
End Try
End Sub
End Class
Code language: JavaScript (javascript)
If you execute the preceding statements, the highlighted values will appear in the Output window. (The LengthUnits sample project uses a TextBox control to display its output.) Figure 6.9 shows the test project for theMetricUnits and EnglishUnits classes. The last few statements convert values between metric and English units, and you’ll see the implementation of these operations momentarily.
Figure 6.9 – Exercising the members of the MetricUnits class
Implementing Unary Operators
In addition to being the subtraction operator, the minus symbol is also a unary operator (it negates the following value). If you attempt to negate a MetricUnits variable, an error will be generated because the subtraction operator expects two values — one on either side of it. In addition to the subtraction operator (which is a binary operator because it operates on two values), we must define the negation operator (which is a unary operator because it operates on a single value). The unary minus operator negates the following value, so a new definition of the subtraction Operator procedure is needed. This definition will overload the existing one, as follows:
Public Overloads Shared Operator -( _
ByVal length1 As MetricUnits) As MetricUnits
Dim result As New MetricUnits
result.Meters = -length1.Meters
result.Centimeters = -length1.Centimeters
Return result
End Operator
Code language: PHP (php)
To negate a length unit stored in a variable of the MetricUnits type, use statements such as the following:
MU2 = -MU1
Debug.Write(MU2.Tostring)
Debug.Write((-MU1).Tostring)
Both statements will print the following in the Output window:
-1 meters, -94 centimeters
There are several unary operators, which you can overload in your custom classes as needed. There’s the unary + operator (not a common operator), and the Not, IsTrue, and IsFalse operators, which are logical operators. The last unary operator is the CType operator, which is exposed as a method of the custom class and is explained next.
Handling Variants
To make your custom data type play well with the other data types, you must also provide a CType() function that can convert a value of the MetricUnits type to any other type. It doesn’t make much sense to convert MetricUnits to dates or any of the built-in objects, but let’s say you have another class: the EnglishUnits class. This class is similar to the MetricUnits class, but it exposes the Inches and Feet properties in place of the Meters and Centimeters properties. The CType() function of the MetricUnits class, which will convert MetricUnits to EnglishUnits, is shown next:
Public Overloads Shared Widening Operator _
CType(ByVal EU As EnglishUnits) As MetricUnits
Dim MU As New MetricUnits
MU.Centimeters = Convert.ToInt32( _
(EU.Feet * 12 + EU.Inches) * 2.54)
Return MU
End Operator
Code language: PHP (php)
Do you remember the implicit narrowing and widening conversions we discussed in Chapter 2? An attempt to assign an integer value to a decimal variable will produce a warning, but the statement will be executed because it’s a widening conversion (no loss of accuracy will occur). The opposite is not true. If the Strict option is on, the compiler won’t allow narrowing conversions because not all Decimal values can be mapped to Integers. To help the compiler enforce strict types, you can use the appropriate keyword to specify whether the CType() function performs a widening or a narrowing conversion. The CType() procedure is shared and overloads the default implementation, which explains all the keywords prefixing its declaration. The following statements exercise the CType method of the MetricUnits class:
Debug.Write(MU1.Tostring)
1 meters, 94 centimeters
Debug.WriteLine(CType(MU1, EnglishUnits).Tostring)
6 feet, 4 inches
Code language: CSS (css)
The output of the two statements is highlighted. Both classes expose integer properties, so the Widening or Narrowing keyword isn’t really important. In other situations, you must carefully specify the type of the conversion to help the compiler generate the appropriate warnings (or exceptions, if needed).
The CType operatorwe added to the MetricUnits class can only convert values of theMetricUnit type to values of the EnglishUnit type. If it makes sense to convert MetricUnits variables to other types, you must provide more overloaded forms of the CType() procedure. For example, you can convert them to numeric values (the numeric value could be the length in centimeters or a double value that represents the same length in meters). The compiler sees the return type(s) of the various overloaded forms of the CType operator, knows whether the requested conversion is possible, and generates the appropriate exception.
In short, operator overloading isn’t complicated, but it adds a touch of elegance to a custom class and enables variables of this type to mix well with the other data types. If you like math, you could implement classes to represent matrices, or complex numbers, and overload the usual operators for addition, multiplication, and so on.