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):
Code language: PHP (php)
Dim TS1 As New TimeSpan(1, 0, 30) Dim TS2 As New TimeSpan(0, 4, 12) Debug.WriteLine((TS1 + TS2).TotalSeconds.ToString)
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:
Code language: PHP (php)
Dim MU As New MetricUnits MU.Meters = 1 MU.Centimeters = 78
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:
Code language: PHP (php)
Public Shared Operator + ( _ ByVal length1 As MetricUnits, _ ByVal length2 As MetricUnits) As MetricUnits End Operator
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
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
Code language: PHP (php)
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
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
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:
Code language: PHP (php)
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
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.
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:
Code language: PHP (php)
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
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:
Code language: CSS (css)
Debug.Write(MU1.Tostring) 1 meters, 94 centimeters Debug.WriteLine(CType(MU1, EnglishUnits).Tostring) 6 feet, 4 inches
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.