Advanced Basics

Calling All Operators

Ken Getz

Contents

Creating Your Own Overloaded Operators
Some Overloading Caveats

This month I found an interesting question in my mailbag. It went something like this: "I am writing some code working with points and other drawing objects in Visual Basic® 2003, and I just want to add an offset to a point, effectively moving the point. I tried adding a Size to a Point, and the thing just won't compile. I tried the exact same thing in C#, and it works fine. What gives?"

To set the context, it's worth creating some code in Visual Studio® .NET 2003 to see what the behavior is, before attempting a solution (if you try this in Visual Studio 2005, things will go slightly differently). Open Visual Basic .NET 2003, create a new Windows® application, put a button on the form, double-click the button to enter the code designer, and add the following code to the button's Click event handler:

Dim pt As New Point(0, 0) pt += New Size(10, 10)

It doesn't compile, does it? For fun, try the same exercise, writing the code in C# instead:

Point pt = new Point(0, 0); pt += new Size(10, 10);

This compiles fine, right? I could go back and forth about why it works in C# and why it doesn't work in Visual Basic .NET, but the whole thing boils down to a simple design issue: namely that Visual Basic 2002 and Visual Basic 2003 don't support overloaded operators. These languages can neither create nor consume operators that have been overloaded—that is, operators that look like standard operators, but that have nonstandard behaviors defined by one of the operator's operands.

If you think about it for a second, you'll agree that any operator, even the lowly + and – operators working on integers, is merely a typographical convenience for methods. For example, when adding two integers, rather than writing the code this way

Dim x As Integer = a + b

you could imagine that under the covers, the compiler is generating code like this:

Dim x As Integer = Int32.Add(a, b)

As long as the operator is defined by the compiler, Visual Basic 2002 and Visual Basic 2003 are fine with it. For example, did you consider the + operator for strings? Although most developers don't write code like this, it's perfectly acceptable and works fine:

Dim str1 As String = "Hello, " Dim str2 As String = "there" Dim str3 As String = str1 + str2

Under the covers, the "+" operator for strings has been overloaded to call the String.Concat method, converting that final line of code into something like this:

Dim str3 As String = String.Concat(str1, str2)

Most people don't ever think about things like this, I'm guessing, because it all happens transparently by the compiler, and it makes using the class(es) involved more intuitive.

Now back to the original question. Because C# has supported operator overloading from its inception, and because buried somewhere in the Point structure's code there's a procedure that overloads the + operator and accepts a Point and a Size as parameters, C# developers can use the + operator to add Point and Size structures together, resulting in a new Point at a new location. So, are developers using Visual Basic simply out in the cold if they want to add a Point and a Size together? Not quite.

When you use the + operator on the Point structure in C# code, the C# compiler in turn generates a call to a Shared method on the struct that performs the actual work. In this case, the method is named op_Addition, and you call it explicitly from code in Visual Basic .NET like any other method, as you might expect. To test it, go back to your Visual Basic .NET project that didn't compile and modify your code so that it looks like this:

Dim pt As New Point(0, 0) pt = Point.op_Addition(pt, New Size(10, 10))

Anyone would agree that this version is slightly more ungainly than the original, but at least this code works. How are you supposed to find this procedure? Check out the documentation for the Point structure. The documentation doesn't really help on its own, but if you click on the Addition Operator link on the Members page, you'll find the information that makes it look like you can use the addition, equality, and other operators. Although you can't use the actual operator symbols in the same way that you can in some other languages, you can call these operator functions explicitly. Given this extra information, when using Visual Basic, developers can call the method although they can't use the operator symbol directly.

If you spend a little more time with the documentation, you'll find that there are methods corresponding to other operators and conversions as well, as you can see in Figure 1. Why are the two conversions listed in Figure 1, and treated as operators? Let's step back and investigate how conversions work. Conversions are treated in .NET much like operators, so if you were to type the following code, whether or not you know it, you're using a widening and a narrowing conversion operator, both of which are built into the definition of the Integer class:

Dim x As Integer = 12 Dim y As Long = x Dim z As Short = x

Figure 1 Defining Methods that Overload Operators

Operator/Conversion Method
Addition (+) op_Addition(Point, Size)
Subtraction (-) op_Subtraction(Point, Size)
Equality (=) op_Equality(Point, Point)
Inequality (<>) op_Inequality(Point, Point)
Point to Size conversion op_Explicit(Point)
Point to PointF conversion op_Implicit(Point)

If you have turned on Option Strict (and I do hope that you have), the final line won't compile. The Visual Basic .NET compiler won't convert an Integer to a Short because you might lose information in doing the conversion (going from a four-byte value to a two-byte value). The conversion from Integer to Long is a widening conversion—there's no chance data will be lost. On the other hand, the conversion from Integer to Short is a narrowing conversion—it's possible that data would be lost in such a conversion. With Option Strict on, Visual Basic doesn't allow implicit narrowing conversions like this one, thus the code won't compile.

It's interesting that this sample code only uses one operator. The assignment operator, "=", handles both the widening and the narrowing conversions. In C#, the assignment operator can be used for both Point conversion operations listed in Figure 1. The code isn't quite the same in both cases, however. Performing the widening conversion from a Point to a PointF converts from one type to the other implicitly, but performing the narrowing conversion from Point to a Size requires an explicit cast, as you see in Figure 2.

Figure 2 Narrowing Conversions from Point to Size

C#

// Widening operations happen implicitly: Point pt1 = new Point(0, 0); PointF ptf = pt1; // Narrowing conversions require explicit casting: Point pt2 = new Point(0, 0); Size sz = (Size) pt2;

Visual Basic .NET

Dim pt1 As New Point(0, 0) Dim ptf As PointF = Point.op_Implicit(pt1) Dim pt2 As New Point(0, 0) Dim sz As Size = Point.op_Explicit(pt2)

But none of this works in Visual Basic 2002 or Visual Basic 2003 without calling the exposed methods. If I was talking only about those versions, I'd be finished now.

With Visual Basic 2005 looming, things will change in the operator overloading horizon. Visual Basic 2005 adds support for overloaded operators, making it possible to both consume and create overloaded operators. Although it's unlikely that you'll do this on a daily basis, it's nice to know that you can.

If you try my first code snippet using Visual Basic 2005, you'll notice that even with Option Strict on, the compiler doesn't complain. Try the following code, which is similar to the previous C# snippet, and you'll see that this works, as well:

' Just as in C#, a widening conversion can be implicit: Dim pt1 As New Point(0, 0) Dim ptf As PointF = pt1 ' Just as in C#, a narrowing conversion must be explicit: Dim pt2 As New Point(0, 0) Dim sz As Size = CType(pt2, Size)

Creating Your Own Overloaded Operators

The canonical operator overloading example, in just about every text I've seen, is the complex number demonstration. You can find a clear description of operator overloading, using that example, by reading the excellent article by Matthew Gertz entitled "Operator Overloading in Visual Basic 2005". For this column, however, I'll work with an array of Integers in a class I'll call Vector. This is a simple example that should be relatively easy to figure out, and to be honest, it's highly unlikely that you're going to need complex numbers in many applications you write. You have the power to overload any of the operators listed in Figure 3.

Figure 3 Overloadable

+ + (unary)
- - (unary)
* \
/ ^
& Like
Mode And
Or Xor
Not <<
>> = (comparison)
<> <
<= >
>= CType
IsTrue IsFalse

You should note the following important facts about operator overloading, in general:

  • Only the operators listed in Figure 3 can be overloaded. You can't overload member access (.), method invocation, or any of AndAlso, OrElse, New, TypeOf...Is, IsNot, AddressOf, or GetType. Using the = sign as an assignment operator isn't overloadable either.
  • Although you generally can overload any individual operator, some work in pairs, specifically, the logical operators. If you overload =, you must overload <>. If you overload <, you must overload >, and so on.
  • Operator overload procedures must be in the same class as one of the operator's operands, declared using both the Public and Shared keywords.
  • Overloads of CType must indicate whether they're widening or narrowing conversions. The widening CType overload defines the assignment operator's overload. (That is, you must use CType when performing a narrowing conversion, but can use an assignment directly when performing a widening conversion. This technique allows you to effectively overload the assignment operator.)

In order to follow along with this demonstration, create a new Windows application in Visual Basic 2005 and add a new class to the project named Vector. In the class, add the code in Figure 4. This class contains a simple array of integers, and you must pass the size of the array you'd like in the class's constructor.

Figure 4 Vector Class Containing an Array of Integers

Public Class Vector ' Create a one-dimensional array of ' integers that can be operated on. Private arrayValue() As Integer Public Sub New(ByVal Size As Integer) ReDim arrayValue(Size - 1) End Sub Public ReadOnly Property Length() As Integer Get Return arrayValue.Length End Get End Property Public ReadOnly Property Array() As Integer() ' Return the array of integers. Get Return arrayValue End Get End Property Default Public Property Item(ByVal Index As Integer) As Integer Get Return arrayValue(Index) End Get Set(ByVal Value As Integer) arrayValue(Index) = Value End Set End Property Public Overrides Function ToString() As String Dim strValue(arrayValue.Length - 1) As String For i As Integer = 0 To arrayValue.Length - 1 strValue(i) = arrayValue(i).ToString Next Return String.Join(", ", strValue) End Function End Class

Place a button on Form1 within your project, and add the code in Figure 5 to the button's Click event handler. The code creates two instances of the new class.

Figure 5 Creating Two Vectors

Dim v1 As New Vector(5) ' {0, 1, 4, 9, 16} For i As Integer = 0 To v1.Length – 1 v1(i) = i * i Next MessageBox.Show(v1.ToString()) Dim v2 As New Vector(5) ' {0, 1, 2, 3, 4} For i As Integer = 0 To v2.Length - 1 v2(i) = i Next MessageBox.Show(v2.ToString())

Given the Vector class as it is, you might want to insert functionality that allows you to add one vector to the standard + operator (obviously, you could extend this concept to other operations, as well). You might also want to be able to compare two vectors and determine if their contents are identical, using the = operator. Finally, you might want to be able to convert from a single-dimensioned array of Integer values into a Vector, or from a Vector back to an array, using the CType operator. Operator overloading makes all of these possible.

To add support for adding two Vector instances, you could add code like that shown in Figure 6 to the Vector class.

Figure 6 Support Vector Addition

Public Shared Operator +( _ ByVal v1 As Vector, ByVal v2 As Vector) As Vector ' Make the output vector the same length as the shorter ' of the two input vectors. Differences in length ' could be handled in different ways. ' This solution is simple. Dim len As Integer = Math.Min(v1.Length, v2.Length) Dim v3 As New Vector(len) For i As Integer = 0 To len - 1 v3(i) = v1(i) + v2(i) Next Return v3 End Operator

Given all these overloaded versions of the + operator, you could use code like the following to test your Vector class:

Dim v3 As Vector = v1 + v2 ' 0, 2, 6, 12, 20 MessageBox.Show(v3.ToString())

If you want to overload the equality (=) operator so that you can compare two Vector instances, you can add code to your Vector class like that shown in the top half of Figure 7. Note that if you overload =, you must also overload <>.

Figure 7 Overload Various Operators

Comparison Operators

Public Shared Operator =( _ ByVal v1 As Vector, ByVal v2 As Vector) As Boolean ' Compare the size of the two arrays. ' If the size matches, compare each element. Dim retval As Boolean = False If v1.Length = v2.Length Then Dim i As Integer For i = 0 To v1.Length - 1 If v1(i) <> v2(i) Then Exit For End If Next ' Get all the way through both arrays? If i = v1.Length Then retval = True End If End If Return retval End Operator Public Shared Operator <>( _ ByVal v1 As Vector, ByVal v2 As Vector) As Boolean Return Not (v1 = v2) End Operator

Conversion Operators

Public Shared Narrowing Operator CType( _ ByVal intValues As Integer()) As Vector ' Convert an array of integers into a vector. ' I've indicated that this is a narrowing conversion, ' even though it's not, just to show that you must use ' CType when performing a narrowing conversion. Dim v As New Vector(intValues.GetUpperBound(0)) For i As Integer = 0 To v.Length - 1 v(i) = intValues.GetValue(i) Next Return v End Operator Public Shared Widening Operator CType( _ ByVal v As Vector) As Integer() ' Convert a vector into an array of integers. This is a ' widening conversion, because it can never cause ' any loss of data. Because this is a widening conversion, ' you can use the assignment operator to perform ' the conversion. Dim intValues(v.Length - 1) As Integer For i As Integer = 0 To v.Length - 1 intValues(i) = v(i) Next Return intValues End Operator

In order to allow for conversion to and from an array of integers, add the overloads shown in the bottom half of Figure 7 to your Vector class. To test the conversion operators, add the following code to the button's Click event handler in your form's class:

Dim intValues1() As Integer = {1, 2, 3, 4, 5, 6, 7, 8} ' Note that narrowing conversion must be explicit. Dim v5 As Vector = CType(intValues1, Vector) MessageBox.Show(v5.ToString()) Dim intValues2() As Integer ' Note that widening conversion can be explicit. ' After this, intValues2 will contain the same ' data as v5. intValues2 = v5

You've now created a Vector class that has overloaded its +, =, and CType operators. I could have taken my own advice from my previous column (see Advanced Basics: Being Generic Ain't So Bad) and used a generic array instead of the Integer array, but I didn't want to confuse the issue at hand here.

Some Overloading Caveats

Lest you think that operator overloading is a trick you're going to use every day, I'd like to make something clear: you'll very seldom overload operators. When you do, they had better make your class more intuitive and easier to use, as opposed to just making you look smart. If it's not immediately clear what is going on when you execute an operation using your overloaded operator, drop it. For example, imagine an order entry system in which you're tracking Customer, Order, and OrderDetail information. Because you often want to display a total of all order amounts for a customer, you need a way to sum the Total field in the Order class across all the customer's orders. You could overload the + operator allowing you to sum the orders:

Dim total As Long = 0 For Each ord As Order In Customer1.Orders ' This is really the same as ' total = total + ord total += ord Next

For this example to work, you could overload the + operator to accept a Long and an Order as operands, and return a Long as the result. But is this code really any clearer than writing it out the long way, as shown here:

Dim total As Long = 0 For Each ord As Order In Customer1.Orders total += ord.Total Next

I really don't think you've gained any efficiency or clarity, and if anything, the meaning of the code has been obfuscated. In any case, be judicious in your use of operator overloading—it isn't required in most situations.

Don't think that just because some developers are programming in C#, they can't take advantage of your overloading—these features work across languages, and any overloaded operators you create in Visual Basic 2005 should work fine in C#, as well. (The same holds true in reverse, because Visual Basic 2005 can consume overloaded operators.)

Think twice before jumping into operator overloading, but don't be afraid to use this elegant technique when you need it. Until Visual Basic 2005 hits the streets, of course, you'll have to be content with calling the "op_Addition" methods of the world, but once Microsoft unleashes Visual Basic 2005, it's smooth sailing.

Ken Getz is a senior consultant with MCW Technologies. He is coauthor of ASP .NET Developers Jumpstart (Addison-Wesley, 2002), Access Developer's Handbook (Sybex, 2001), and VBA Developer's Handbook, 2nd Edition (Sybex, 2001). Reach him at keng@mcwtech.com.