
Pro CSharp And The .NET 2.0 Platform (2005) [eng]
.pdf
294 C H A P T E R 9 ■ A D VA N C E D C # T Y P E C O N S T R U C T I O N T E C H N I Q U E S
Table 9-1. Valid Overloadable Operators
C# Operator |
Overloadability |
+, –, !, ~, ++, – –, true, false +, –, *, /, %, &, |, ^, <<, >> ==, !=, <, >, <=, >=
This set of unary operators can be overloaded.
These binary operators can be overloaded.
The comparison operators can be overloaded. C# will demand that “like” operators (i.e., < and >, <= and >=, = = and !=) are overloaded together.
[] |
The [] operator cannot be overloaded. As you saw earlier |
|
in this chapter, however, the indexer construct provides |
|
the same functionality. |
() |
The () operator cannot be overloaded. As you will see |
|
later in this chapter, however, custom conversion methods |
|
provide the same functionality. |
+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= |
Shorthand assignment operators cannot be overloaded; |
|
however, you receive them as a freebie when you overload |
|
the related binary operator. |
|
|
Overloading Binary Operators
To illustrate the process of overloading binary operators, assume the following simple Point structure:
// Just a simple everyday C# struct. public struct Point
{
private int x, y;
public Point(int xPos, int yPos)
{
x = xPos; y = yPos;
}
public override string ToString()
{
return string.Format("[{0}, {1}]", this.x, this.y);
}
}
Now, logically speaking, it makes sense to add Points together. On a related note, it may be helpful to subtract one Point from another. For example, you would like to be able to author the following code:
// Adding and subtracting two points. static void Main(string[] args)
{
Console.WriteLine("***** Fun with Overloaded Operators *****\n");
// Make two points.
Point ptOne = new Point(100, 100); Point ptTwo = new Point(40, 40); Console.WriteLine("ptOne = {0}", ptOne); Console.WriteLine("ptTwo = {0}", ptTwo);
// Add the points to make a bigger point?
Console.WriteLine("ptOne + ptTwo: {0} ", ptOne + ptTwo);

C H A P T E R 9 ■ A D VA N C E D C # T Y P E C O N S T R U C T I O N T E C H N I Q U E S |
295 |
// Subtract the points to make a smaller point?
Console.WriteLine("ptOne - ptTwo: {0} ", ptOne - ptTwo); Console.ReadLine();
}
To allow a custom type to respond uniquely to intrinsic operators, C# provides the operator keyword, which you can only use in conjunction with static methods. When you are overloading a binary operator (such as + and -), you will pass in two arguments that are the same type as the defining class (a Point in this example), as illustrated in the following code:
// A more intelligent Point type. public struct Point
{
...
// overloaded operator +
public static Point operator + (Point p1, Point p2) { return new Point(p1.x + p2.x, p1.y + p2.y); }
// overloaded operator -
public static Point operator - (Point p1, Point p2) { return new Point(p1.x - p2.x, p1.y - p2.y); }
}
The logic behind operator + is simply to return a brand new Point based on the summation of the fields of the incoming Point parameters. Thus, when you write pt1 + pt2, under the hood you can envision the following hidden call to the static operator + method:
// p3 = Point.operator+ (p1, p2) p3 = p1 + p2;
Likewise, p1 – p2 maps to the following:
// p3 = Point.operator- (p1, p2) p3 = p1 - p2;
And What of the += and –+ Operators?
If you are coming to C# from a C++ background, you may lament the loss of overloading the shorthand assignment operators (+=, –=, and so forth). Fear not. In terms of C#, the shorthand assignment operators are automatically simulated if a type overloads the related binary operator. Thus, given that the Point structure has already overloaded the + and - operators, you are able to write the following:
// Overloading binary operators results in a freebie shorthand operator. static void Main(string[] args)
{
...
// Freebie +=
Point ptThree = new Point(90, 5); Console.WriteLine("ptThree = {0}", ptThree);
Console.WriteLine("ptThree += ptTwo: {0}", ptThree += ptTwo);
// Freebie -=
Point ptFour = new Point(0, 500); Console.WriteLine("ptFour = {0}", ptFour);
Console.WriteLine("ptFour -= ptThree: {0}", ptFour -= ptThree);
}

296 C H A P T E R 9 ■ A D VA N C E D C # T Y P E C O N S T R U C T I O N T E C H N I Q U E S
Overloading Unary Operators
C# also allows you to overload various unary operators, such as ++ and --. When you overload
a unary operator, you will also define a static method via the operator keyword; however in this case you will simply pass in a single parameter that is the same type as the defining class/structure. For example, if you were to update the Point with the following overloaded operators:
public struct Point
{
...
// Add 1 to the incoming Point.
public static Point operator ++(Point p1) { return new Point(p1.x+1, p1.y+1); }
// Subtract 1 from the incoming Point. public static Point operator --(Point p1) { return new Point(p1.x-1, p1.y-1); }
}
you could increment and decrement Point’s X and Y values as so:
static void Main(string[] args)
{
...
// Applying the ++ and -- unary operators to a Point.
Console.WriteLine("++ptFive = {0}", ++ptFive); Console.WriteLine("--ptFive = {0}", --ptFive);
}
Overloading Equality Operators
As you may recall from Chapter 3, System.Object.Equals() can be overridden to perform value-based (rather than referenced-based) comparisons between types. If you choose to override Equals() (and the often related System.Object.GetHashCode() method), it is trivial to overload the equality operators (== and !=). To illustrate, here is the updated Point type:
// This incarnation of Point also overloads the == and != operators. public struct Point
{
...
public override bool Equals(object o)
{
if(o is Point)
{
if( ((Point)o).x == this.x && ((Point)o).y == this.y) return true;
}
return false;
}
public override int GetHashCode()
{return this.ToString().GetHashCode(); }
// Now let's overload the == and != operators. public static bool operator ==(Point p1, Point p2)
{return p1.Equals(p2); }

C H A P T E R 9 ■ A D VA N C E D C # T Y P E C O N S T R U C T I O N T E C H N I Q U E S |
297 |
public static bool operator !=(Point p1, Point p2)
{return !p1.Equals(p2); }
}
Notice how the implementation of operator == and operator != simply makes a call to the overridden Equals() method to get the bulk of the work done. Given this, you can now exercise your Point class as follows:
// Make use of the overloaded equality operators. static void Main(string[] args)
{
...
Console.WriteLine("ptOne == ptTwo : {0}", ptOne == ptTwo); Console.WriteLine("ptOne != ptTwo : {0}", ptOne != ptTwo);
}
As you can see, it is quite intuitive to compare two objects using the well-known == and != operators rather than making a call to Object.Equals(). If you do overload the equality operators for a given class, keep in mind that C# demands that if you override the == operator, you must also override the != operator (if you forget, the compiler will let you know).
Overloading Comparison Operators
In Chapter 7, you learned how to implement the IComparable interface in order to compare the relative relationship between two like objects. Additionally, you may also overload the comparison operators (<, >, <=, and >=) for the same class. Like the equality operators, C# demands that if you overload <, you must also overload >. The same holds true for the <= and >= operators. If the Point type overloaded these comparison operators, the object user could now compare Points as follows:
// Using the overloaded < and > operators. static void Main(string[] args)
{
...
Console.WriteLine("ptOne < ptTwo : {0}", ptOne < ptTwo); Console.WriteLine("ptOne > ptTwo : {0}", ptOne > ptTwo);
}
Assuming you have implemented the IComparable interface, overloading the comparison operators is trivial. Here is the updated class definition:
// Point is also comparable using the comparison operators. public struct Point : IComparable
{
...
public int CompareTo(object obj)
{
if (obj is Point)
{
Point p = (Point)obj;
if (this.x > p.x && this.y > p.y) return 1;
if (this.x < p.x && this.y < p.y) return -1;
else
return 0;
}

298 C H A P T E R 9 ■ A D VA N C E D C # T Y P E C O N S T R U C T I O N T E C H N I Q U E S
else
throw new ArgumentException();
}
public static bool operator <(Point p1, Point p2) { return (p1.CompareTo(p2) < 0); }
public static bool operator >(Point p1, Point p2) { return (p1.CompareTo(p2) > 0); }
public static bool operator <=(Point p1, Point p2) { return (p1.CompareTo(p2) <= 0); }
public static bool operator >=(Point p1, Point p2) { return (p1.CompareTo(p2) >= 0); }
}
The Internal Representation of Overloaded Operators
Like any C# programming element, overloaded operators are represented using specific CIL syntax. To begin examining what takes place behind the scenes, open the OverloadedOps.exe assembly using ildasm.exe. As you can see from Figure 9-1, the overloaded operators are internally expressed via hidden methods (e.g., op_Addition(), op_Subtraction(), op_Equality(), and so on).
Figure 9-1. In terms of CIL, overloaded operators map to hidden methods.
Now, if you were to examine the specific CIL instructions for the op_Addition method, you would find that the specialname method decoration has also been inserted by csc.exe:

C H A P T E R 9 ■ A D VA N C E D C # T Y P E C O N S T R U C T I O N T E C H N I Q U E S |
299 |
.method public hidebysig specialname static valuetype OverloadedOps.Point
op_Addition(valuetype OverloadedsOps.Point p1, valuetype OverloadedOps.Point p2) cil managed
{
...
}
The truth of the matter is that any operator that you may overload equates to a specially named method in terms of CIL. Table 9-2 documents the C# operator-to-CIL mapping for the most common C# operators.
Table 9-2. C# Operator-to-CIL Special Name Road Map
Intrinsic C# Operator |
CIL Representation |
|
–– |
op_Decrement() |
|
++ |
op_Increment() |
|
+ |
op_Addition() |
|
– |
op_Subtraction() |
|
* |
op_Multiply() |
|
/ |
op_Division() |
|
== |
op_Equality() |
|
> |
op_GreaterThan() |
|
< |
op_LessThan() |
|
!= |
op_Inequality() |
|
>= |
op_GreaterThanOrEqual() |
|
<= |
op_LessThanOrEqual() |
|
–= |
op_SubtractionAssignment() |
|
+= |
op_AdditionAssignment() |
|
|
|
|
Interacting with Overloaded Operators from Overloaded Operator–Challenged Languages
Understanding how overloaded operators are represented in CIL code is not simply interesting from an academic point of view. To understand the practical reason for this knowledge, recall that the capability to overload operators is not supported by every .NET-aware language. Given this, what would happen if you wanted to add two Point types together in an overloaded operator–challenged language?
One approach is to provide “normal” public members that perform the same task as the overloaded operators. For example, you could update the Point type with Add() and Subtract() methods, which leverage the work performed by the custom + and - operators:
//Exposing overloaded operator semantics using simple
//member functions.
public struct Point
{
...
// Operator + via Add()
public static Point Add (Point p1, Point p2) { return p1 + p2; }

300 C H A P T E R 9 ■ A D VA N C E D C # T Y P E C O N S T R U C T I O N T E C H N I Q U E S
// Operator - via Subtract()
public static Point Subtract (Point p1, Point p2) { return p1 - p2; }
}
With this, the Point type is able to expose the same functionality using whichever technique a given language demands. C# users can apply the + and - operators and/or call Add()/Subtract():
// Use operator + or Add().
Console.WriteLine("ptOne + ptTwo: {0} ", ptOne + ptTwo); Console.WriteLine("Point.Add(ptOne, ptTwo): {0} ", Point.Add(ptOne, ptTwo));
// Use operator - or Subtract().
Console.WriteLine("ptOne - ptTwo: {0} ", ptOne - ptTwo); Console.WriteLine("Point.Subtract(ptOne, ptTwo): {0} ",
Point.Subtract(ptOne, ptTwo));
Languages that cannot use overloaded operators can simply make due with the public static methods. As an alternative to providing duplicate functionality on the same type, understand that it is also possible to directly call the specially named methods from languages that lack overloaded operators.
Consider the initial release of the VB .NET programming language. If you were to build a VB .NET console application that references the Point type, you could add or subtract Point types using the CIL “special names,” for example:
' Assume this VB .NET application has access to the Point type.
Module OverLoadedOpClient Sub Main()
Dim p1 As Point p1.x = 200 p1.y = 9
Dim p2 As Point p2.x = 9
p2.y = 983
'Not as clean as calling AddPoints(),
'but it gets the job done.
Dim bigPoint = Point.op_Addition(p1, p2)
Console.WriteLine("Big point is {0}", bigPoint)
End Sub
End Module
As you can see, overloaded operator–challenged .NET programming languages are able to directly invoke the internal CIL methods as if they were “normal” methods. While it is not pretty, it works.
■Note Do be aware that the current version of VB .NET (Visual Basic .NET 2005) supports operator overloading. However, for the (many) managed languages that do not support operator overloading, knowledge of CIL “special names” can prove helpful.

C H A P T E R 9 ■ A D VA N C E D C # T Y P E C O N S T R U C T I O N T E C H N I Q U E S |
301 |
Final Thoughts Regarding Operator Overloading
As you have seen, C# provides the capability to build types that can respond uniquely to various intrinsic, well-known operators. Now, before you go and retrofit all your classes to support such behavior, you must be sure that the operator(s) you are about to overload make some sort of logical sense in the world at large.
For example, let’s say you overloaded the multiplication operator for the Engine class. What exactly would it mean to multiply two Engine objects? Not much. Overloading operators is generally only useful when you’re building utility types. Strings, points, rectangles, fractions, and hexagons make good candidates for operator overloading. People, managers, cars, headphones, and baseball hats do not. As a rule of thumb, if an overloaded operator makes it harder for the user to understand a type’s functionality, don’t do it. Use this feature wisely.
■Source Code The OverloadedOps project is located under the Chapter 9 subdirectory.
Understanding Custom Type Conversions
Let’s now examine a topic closely related to operator overloading: custom type conversions. To set the stage for the discussion to follow, let’s quickly review the notion of explicit and implicit conversions between numerical data and related class types.
Recall: Numerical Conversions
In terms of the intrinsic numerical types (sbyte, int, float, etc.), an explicit conversion is required when you attempt to store a larger value in a smaller container, as this may result in a loss of data. Basically, this is your way to tell the compiler, “Leave me alone, I know what I am trying to do.” Conversely, an implicit conversion happens automatically when you attempt to place a smaller type in a destination type that will not result in a loss of data:
static void Main() |
|
{ |
|
int a = 123; |
|
long b = a; |
// Implicit conversion from int to long |
int c = (int) b; |
// Explicit conversion from long to int |
} |
|
Recall: Conversions Among Related Class Types
As shown in Chapter 4, class types may be related by classical inheritance (the “is-a” relationship). In this case, the C# conversion process allows you to cast up and down the class hierarchy. For example, a derived class can always be implicitly cast into a given base type. However, if you wish to store a base class type in a derived variable, you must perform an explicit cast:
// Two related class types. class Base{}
class Derived : Base{}
class Program
{
static void Main()
{

302C H A P T E R 9 ■ A D VA N C E D C # T Y P E C O N S T R U C T I O N T E C H N I Q U E S
//Implicit cast between derived to base.
Base myBaseType; myBaseType = new Derived();
//Must explicitly cast to store base reference
//in derived type.
Derived myDerivedType = (Derived)myBaseType;
}
}
This explicit cast works due to the fact that the Base and Derived classes are related by classical inheritance. However, what if you have two class types in different hierarchies that require conversions? Given that they are not related by classical inheritance, explicit casting offers no help.
On a related note, consider value types. Assume you have two .NET structures named Square and Rectangle. Given that structures cannot leverage classic inheritance, you have no natural way to cast between these seemingly related types (assuming it made sense to do so).
While you could build helper methods in the structures (such as Rectangle.ToSquare()), C# allows you to build custom conversion routines that allow your types to respond to the () operator. Therefore, if you configured the Square type correctly, you would be able to use the following syntax to explicitly convert between these structure types:
// Convert a Rectangle to a Square.
Rectangle rect; rect.Width = 3; rect.Height = 10;
Square sq = (Square)rect;
Creating Custom Conversion Routines
C# provides two keywords, explicit and implicit, that you can use to control how your types respond during an attempted conversion. Assume you have the following structure definitions:
public struct Rectangle
{
//Public for ease of use;
//however, feel free to encapsulate with properties. public int Width, Height;
public void Draw()
{ Console.WriteLine("Drawing a rect.");}
public override string ToString()
{
return string.Format("[Width = {0}; Height = {1}]", Width, Height);
}
}
public struct Square
{
public int Length;
public void Draw()
{ Console.WriteLine("Drawing a square.");}

C H A P T E R 9 ■ A D VA N C E D C # T Y P E C O N S T R U C T I O N T E C H N I Q U E S |
303 |
public override string ToString()
{ return string.Format("[Length = {0}]", Length); }
//Rectangles can be explicitly converted
//into Squares.
public static explicit operator Square(Rectangle r)
{
Square s;
s.Length = r.Width; return s;
}
}
Notice that this iteration of the Rectangle type defines an explicit conversion operator. Like the process of overloading an operator, conversion routines make use of the C# operator keyword (in conjunction with the explicit or implicit keyword) and must be defined as static. The incoming parameter is the entity you are converting from, while the return value is the entity you are converting to:
public static explicit operator Square(Rectangle r) {...}
In any case, the assumption is that a square (being a geometric pattern in which all sides are of equal length) can be obtained from the width of a rectangle. Thus, you are free to convert a Rectangle into a Square as so:
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Custom Conversions *****\n");
//Create a 5 * 10 Rectangle.
Rectangle rect; rect.Width = 10; rect.Height = 5;
Console.WriteLine("rect = {0}", rect);
//Convert Rectangle to a 10 * 10 Square.
Square sq = (Square)rect; Console.WriteLine("sq = {0}", sq); Console.ReadLine();
}
While it may not be all that helpful to convert a Rectangle into a Square within the same scope, assume you have a function that has been prototyped to take Square types.
// This method requires a Square type. private static void DrawSquare(Square sq)
{
sq.Draw();
}
Using your explicit conversion operation, you can safely pass in Square types for processing:
static void Main(string[] args)
{
...
// Convert Rectangle to Square to invoke method.
DrawSquare((Square)rect);
}