
Pro CSharp 2008 And The .NET 3.5 Platform [eng]
.pdf
392 CHAPTER 12 ■ INDEXERS, OPERATORS, AND POINTERS
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); Console.ReadLine();
}
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 follows:
static void Main(string[] args)
{
...
//Applying the ++ and -- unary operators to a Point.
Point ptFive = new Point(1, 1); Console.WriteLine("++ptFive = {0}", ++ptFive); // [2, 2] Console.WriteLine("--ptFive = {0}", --ptFive); // [1, 1]
//Apply same operators as postincrement/decrement.
Point ptSix = new Point(20, 20);

CHAPTER 12 ■ INDEXERS, OPERATORS, AND POINTERS |
393 |
Console.WriteLine("ptSix++ = {0}", ptSix++); // [20, 20] Console.WriteLine("ptSix-- = {0}", ptSix--); // [21, 21] Console.ReadLine();
}
Notice in the previous code example we are applying our custom ++ and -- operators in two unique manners. In C++, it is possible to overload preand postincrement/decrement operators separately. This is not possible in C#; however, the return value of the increment/decrement is automatically handled “correctly” free of charge (i.e., for an overloaded ++ operator, pt++ has the value of the unmodified object as its value within an expression, while ++pt has the new value applied before use in the expression).
Overloading Equality Operators
As you may recall from Chapter 6, System.Object.Equals() can be overridden to perform valuebased (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)
{
return o.ToString() == this.ToString();
}
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); }
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); Console.ReadLine();
}
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).

394 CHAPTER 12 ■ INDEXERS, OPERATORS, AND POINTERS
Overloading Comparison Operators
In Chapter 9, 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); Console.ReadLine();
}
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;
}
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

CHAPTER 12 ■ INDEXERS, OPERATORS, AND POINTERS |
395 |
using ildasm.exe. As you can see from Figure 12-3, the overloaded operators are internally expressed via hidden methods (e.g., op_Addition(), op_Subtraction(), op_Equality(), and so on).
Figure 12-3. 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:
.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 12-2 documents the C# operator-to-CIL mapping for the most common C# operators.
Table 12-2. C# Operator-to-CIL Special Name Road Map
Intrinsic C# Operator |
CIL Representation |
-- |
op_Decrement() |
++ |
op_Increment() |
+ |
op_Addition() |
Continued

396 CHAPTER 12 ■ INDEXERS, OPERATORS, AND POINTERS
Table 12-2. Continued
Intrinsic C# Operator |
CIL Representation |
- |
op_Subtraction() |
* |
op_Multiply() |
/ |
op_Division() |
== |
op_Equality() |
> |
op_GreaterThan() |
< |
op_LessThan() |
!= |
op_Inequality() |
>= |
op_GreaterThanOrEqual() |
<= |
op_LessThanOrEqual() |
-= |
op_SubtractionAssignment() |
+= |
op_AdditionAssignment() |
|
|
■Note There is a practical reason to know the “special names” of an overloaded operator. Because many languages cannot use types with overloaded operators, programmers of said languages are able to call these internal names statically from the defining type (e.g., Point.op_Addition(myPoint, yourPoint)).
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 MiniVan class. What exactly would it mean to multiply two MiniVan objects? Not much. In fact, it would be very confusing for teammates to see the following use of MiniVan objects.
// Huh?! This is far from intuitive...
MiniVan newVan = myVan * yourVan;
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, database connections, and web pages 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.
Also be aware that even if you do not tend to overload operators for your custom classes, numerous types in the base class libraries have already done so. For example, the System.Drawing. dll assembly provides an “official” Point definition that overloads numerous operators. Notice the operator icon from the Visual Studio 2008 Object Browser (see Figure 12-4).

CHAPTER 12 ■ INDEXERS, OPERATORS, AND POINTERS |
397 |
Figure 12-4. Numerous types in the base class libraries have already-overloaded operators.
■Source Code The OverloadedOps project is located under the Chapter 12 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 6, 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 to a base type. However, if you wish to store a base class type in a derived variable, you must perform an explicit cast:

398 CHAPTER 12 ■ INDEXERS, OPERATORS, AND POINTERS
// Two related class types. class Base{}
class Derived : Base{}
class Program
{
static void Main()
{
//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 with no common parent (other than System.Object) that requires conversions? Given that they are not related by classical inheritance, explicit casting offers no help.
On a related note, consider value types (e.g., structures). Assume you have two .NET structures named Square and Rectangle. Given that structures cannot leverage classic inheritance (as they are always sealed), you have no natural way to cast between these seemingly related types.
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 () casting operator. Therefore, if you configured the structures correctly, you would be able to use the following syntax to explicitly convert between them as follows:
// Convert a Rectangle to a Square!
Rectangle rect; rect.Width = 3; rect.Height = 10;
Square sq = (Square)rect;
Creating Custom Conversion Routines
Begin by creating a new Console Application named CustomConversions. 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 Rectangle(int w, int h)
{
Width = w; Height = h;
}
public void Draw()
{
for (int i = 0; i < Height; i++)

CHAPTER 12 ■ INDEXERS, OPERATORS, AND POINTERS |
399 |
{
for (int j = 0; j < Width; j++)
{
Console.Write("*");
}
Console.WriteLine();
}
}
public override string ToString()
{
return string.Format("[Width = {0}; Height = {1}]", Width, Height);
}
}
public struct Square
{
public int Length; public Square(int l)
{
Length = l;
}
public void Draw()
{
for (int i = 0; i < Length; i++)
{
for (int j = 0; j < Length; j++)
{
Console.Write("*");
}
Console.WriteLine();
}
}
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.Height; return s;
}
}
Notice that this iteration of the Square 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 operator type is the entity you are converting to.

400 CHAPTER 12 ■ INDEXERS, OPERATORS, AND POINTERS
In this case, the assumption is that a square (being a geometric pattern in which all sides are of equal length) can be obtained from the height of a rectangle. Thus, you are free to convert a
Rectangle into a Square as follows:
static void Main(string[] args)
{
Console.WriteLine("***** Fun with Conversions *****\n");
// Make a Rectangle.
Rectangle r = new Rectangle(15, 4); Console.WriteLine(r.ToString()); r.Draw();
Console.WriteLine();
//Convert r into a Square,
//based on the height of the Rectangle.
Square s = (Square)r; Console.WriteLine(s.ToString()); s.Draw();
Console.ReadLine();
}
The output can be seen in Figure 12-5.
Figure 12-5. Converting a Rectangle structure to a Square structure
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 designed to take Square parameters.
// This method requires a Square type. static void DrawSquare(Square sq)
{
Console.WriteLine(sq.ToString());
sq.Draw();
}
Using your explicit conversion operation on the Square type, you can now pass in Rectangle types for processing using an explicit cast:
static void Main(string[] args)
{
...
// Convert Rectangle to Square to invoke method.

CHAPTER 12 ■ INDEXERS, OPERATORS, AND POINTERS |
401 |
Rectangle rect = new Rectangle(10, 5); DrawSquare((Square)rect); Console.ReadLine();
}
Additional Explicit Conversions for the Square Type
Now that you can explicitly convert Rectangles into Squares, let’s examine a few additional explicit conversions. Given that a square is symmetrical on each side, it might be helpful to provide an explicit conversion routine that allows the caller to cast from a System.Int32 type into a Square (which, of course, will have a side length equal to the incoming integer). Likewise, what if you were to update Square such that the caller can cast from a Square into a System.Int32? Here is the calling logic:
static void Main(string[] args)
{
...
//Converting a System.Int32 to a Square.
Square sq2 = (Square)90; Console.WriteLine("sq2 = {0}", sq2);
//Converting a Square to a System.Int32. int side = (int)sq2;
Console.WriteLine("Side length of sq2 = {0}", side); Console.ReadLine();
}
and here is the update to the Square type:
public struct Square
{
...
public static explicit operator Square(int sideLength)
{
Square newSq; newSq.Length = sideLength; return newSq;
}
public static explicit operator int (Square s) {return s.Length;}
}
To be honest, converting from a Square into a System.Int32 may not be the most intuitive (or useful) operation. However, this does point out a very important fact regarding custom conversion routines: the compiler does not care what you convert to or from, as long as you have written syntactically correct code. Thus, as with overloading operators, just because you can create an explicit cast operation for a given type does not mean you should. Typically, this technique will be most helpful when you’re creating .NET structure types, given that they are unable to participate in classical inheritance (where casting comes for free).
Defining Implicit Conversion Routines
Thus far, you have created various custom explicit conversion operations. However, what about the following implicit conversion?