
C# ПІДРУЧНИКИ / c# / Apress - Accelerated C# 2005
.pdf
134C H A P T E R 6 ■ OV E R L OA D I N G O P E R ATO R S
another Complex instance is for you to decipher. In general, operator overloading syntax follows the previous pattern, with the + replaced with the operator du jour, and of course, some operators accept only one parameter.
■Note When comparing C# operators with C++ operators, note that C# operator declarations are more similar to the friend function technique of declaring C++ operators.
There are essentially three different groups of overloadable operators. Unary operators accept only one parameter. Familiar unary operators include the ++ and -- operators. Binary operators, as the name implies, accept two parameters and include familiar mathematical operators such as +, -, /, and *, as well as the familiar comparison operators. Finally, conversion operators define a userdefined conversion. They must have either the operand or the return value type declared the same as the containing class or struct type.
Even though operators are static and public, and thus are inherited by derived classes, operator methods must have at least one parameter in their declaration that matches the enclosing type, making it impossible for the derived type’s operator method to match the signature of the base class operator method exactly. For example, the following is not valid:
public class Apple
{
public static Apple operator+( Apple rhs, Apple lhs ) { // Method does nothing and exists only for example. return rhs;
}
}
public class GreenApple : Apple
{
// INVALID!! -- Won't compile.
public static Apple operator+( Apple rhs, Apple lhs ) { // Method does nothing and exists only for example. return rhs;
}
}
If you attempt to compile the previous code, you’ll get the following compiler error:
error CS0563: One of the parameters of a binary operator must be the containing type
Operators Shouldn’t Mutate Their Operands
You already know that operator methods are static. Therefore, it is highly recommended (read: required) that you do not mutate the operands passed into the operator methods. Instead, you should create a new instance of the return value type and return the result of the operation. Structs and classes that are immutable, such as System.String, are perfect candidates for implementing custom operators. This behavior is natural for operators such as bool operators, which usually return a type different than the types passed into the operator.

C H A P T E R 6 ■ OV E R L OA D I N G O P E R ATO R S |
135 |
■Note “Now wait just a minute!” some of you from the C++ community may be saying. “How in the world can you implement the postfix and prefix operators ++ and -- without mutating the operand?” The answer lies in the fact that the postfix and prefix operators as implemented in C# are somewhat different than those of C++. All C# operators are static, and that includes the postfix and prefix operators, whereas in C++ they are instance methods that modify the object instance through the this pointer. The beauty of the C# approach is that you don’t have to worry about implementing two different versions of the ++ operator in order to support both postfix and prefix incrementing, as you do in C++. The compiler handles the task involved with making temporary copies of the object to handle the difference in behavior between postfix and prefix. This is yet another reason why your operators must return new instances while never modifying the state of the operands themselves. If you don’t follow this practice, you’re setting yourself up for some major debugging heartbreak.
Does Parameter Order Matter?
Suppose you create a struct to represent simple complex numbers—say, struct Complex—and you need to add instances of Complex together. It would also be convenient to be able to add a plain old double to the Complex instance. Adding this functionality is no problem, since you can overload the operator+ method such that one parameter is a Complex and the other is a double. That declaration could look like the following:
static public Complex operator+( Complex lhs, double rhs )
With this operator declared and defined on the Complex struct, you can now write code such as the following:
Complex cpx1 = new Complex( 1.0, 2.0 );
Complex cpx2 = cpx1 + 20.0;
This saves you the time of having to create an extra Complex instance with just the real part set to 20.0 in order to add it to cpx1. However, suppose you want to be able to reverse the operands on the operator and do something like the following instead:
Complex cpx2 = 20.0 + cpx1;
If you want to support different orderings of operands of different types, you must provide different overloads of the operator. If you overload a binary operator that uses different parameter types, you can create a mirror overload—that is, another operator method that reverses the parameters. Doing so is a trivial task.
Overloading the Addition Operator
Let’s take a look at a cursory example of a Complex struct, which is by no means a complete implementation, but merely a demonstration of how to overload operators. Throughout this chapter, I’ll build upon this example and add more operators to it:
using System;
public struct Complex
{
public Complex( double real, double imaginary ) { this.real = real;
this.imaginary = imaginary;
}

136 C H A P T E R 6 ■ OV E R L OA D I N G O P E R ATO R S
static public Complex Add( Complex lhs, Complex rhs ) {
return new Complex( lhs.real + rhs.real, lhs.imaginary + rhs.imaginary );
}
static public Complex Add( Complex lhs, double rhs ) {
return new Complex( rhs + lhs.real, lhs.imaginary );
}
public override string ToString() { return String.Format( "({0}, {1})",
real, imaginary );
}
static public Complex operator+( Complex lhs, Complex rhs ) {
return Add( lhs, rhs );
}
static public Complex operator+( double lhs, Complex rhs ) {
return Add( rhs, lhs );
}
static public Complex operator+( Complex lhs, double rhs ) {
return Add( lhs, rhs );
}
private double real; private double imaginary;
}
public class EntryPoint
{
static void Main() {
Complex cpx1 = new Complex( 1.0, 3.0 ); Complex cpx2 = new Complex( 1.0, 2.0 );
Complex cpx3 = cpx1 + cpx2;
Complex cpx4 = 20.0 + cpx1;
Complex cpx5 = cpx1 + 25.0;
Console.WriteLine( "cpx1 == {0}", cpx1 ); Console.WriteLine( "cpx2 == {0}", cpx2 ); Console.WriteLine( "cpx3 == {0}", cpx3 ); Console.WriteLine( "cpx4 == {0}", cpx4 ); Console.WriteLine( "cpx5 == {0}", cpx5 );
}
}

C H A P T E R 6 ■ OV E R L OA D I N G O P E R ATO R S |
137 |
Notice that, as recommended, the overloaded operator methods call methods that perform the same operation. In fact, doing so makes supporting both orderings of operator+ that add a double to a Complex a snap.
■Tip If you’re absolutely sure that your type will only be used in a C# environment or in a language that supports overloaded operators, then you can forego this exercise and simply stick with the overloaded operators.
Operators That Can Be Overloaded
Let’s take a quick look at which operators you can overload. Unary operators, binary operators, and conversion operators compose the three general types of operators. It’s impossible to list all of the conversion operators here, since the set is limitless. Additionally, you can use the one ternary opera- tor—the familiar ?: operator—for conditional statements, but you cannot overload it directly. Later, in the “Boolean Operators” section, I describe what you can do to play nicely with the ternary operator. Table 6-1 lists all of the operators except the conversion operators.
Table 6-1. Unary and Binary Operators
Unary Operators |
Binary Operators |
+ |
+ |
- |
- |
! |
* |
~ |
/ |
++ |
% |
-- |
& |
true and false |
| |
|
^ |
|
<< |
|
>> |
|
== and != |
|
> and < |
|
>= and <= |
|
|
Comparison Operators
The binary comparison operators == and !=, < and >, and >= and <= are all required to be implemented as pairs. Of course, this makes perfect sense, because I doubt there would ever be a case where you would like to allow users to use operator== and not operator!=. Moreover, if your type allows ordering via implementation of the IComparable interface or its generic counterpart IComparable<T>, then it makes the most sense to implement all comparison operators. Implementing these operators is trivial if you follow the canonical guidelines given in Chapters 4 and 13 by overriding Equals() and
GetHashCode() and implementing IComparable (and optionally IComparable<T> and IEquatable<T>) appropriately. Given that, overloading the operators merely requires you to call those implementations. Let’s look at a modified form of the Complex number that follows this pattern to implement all of the comparison operators:

138C H A P T E R 6 ■ OV E R L OA D I N G O P E R ATO R S
using System;
public struct Complex : IComparable,
IEquatable<Complex>,
IComparable<Complex>
{
public Complex( double real, double img ) { this.real = real;
this.img = img;
}
// System.Object override
public override bool Equals( object other ) { bool result = false;
if( other is Complex ) {
result = Equals( (Complex) other );
}
return result;
}
// Typesafe version
public bool Equals( Complex that ) { return (this.real == that.real &&
this.img == that.img);
}
//Must override this if overriding Object.Equals() public override int GetHashCode() {
return (int) this.Magnitude;
}
//Typesafe version
public int CompareTo( Complex that ) { int result;
if( Equals( that ) ) { result = 0;
}else if( this.Magnitude > that.Magnitude ) { result = 1;
}else {
result = -1;
}
return result;
}
// IComparable implementation
int IComparable.CompareTo( object other ) { if( !(other is Complex) ) {
throw new ArgumentException( "Bad Comparison" );
}
return CompareTo( (Complex) other );
}
// System.Object override
public override string ToString() { return String.Format( "({0}, {1})",

C H A P T E R 6 ■ OV E R L OA D I N G O P E R ATO R S |
139 |
real, img );
}
public double Magnitude { get {
return Math.Sqrt( Math.Pow(this.real, 2) + Math.Pow(this.img, 2) );
}
}
// Overloaded operators
public static bool operator==( Complex lhs, Complex rhs ) { return lhs.Equals( rhs );
}
public static bool operator!=( Complex lhs, Complex rhs ) { return !lhs.Equals( rhs );
}
public static bool operator<( Complex lhs, Complex rhs ) { return lhs.CompareTo( rhs ) < 0;
}
public static bool operator>( Complex lhs, Complex rhs ) { return lhs.CompareTo( rhs ) > 0;
}
public static bool operator<=( Complex lhs, Complex rhs ) { return lhs.CompareTo( rhs ) <= 0;
}
public static bool operator>=( Complex lhs, Complex rhs ) { return lhs.CompareTo( rhs ) >= 0;
}
// Other methods omitted for clarity.
private double real; private double img;
}
public class EntryPoint
{
static void Main() {
Complex cpx1 = new Complex( 1.0, 3.0 ); Complex cpx2 = new Complex( 1.0, 2.0 );
Console.WriteLine( "cpx1 = {0}, cpx1.Magnitude = {1}", cpx1, cpx1.Magnitude );
Console.WriteLine( "cpx2 = {0}, cpx2.Magnitude = {1}\n", cpx2, cpx2.Magnitude );
Console.WriteLine( "cpx1 == cpx2 ? {0}", cpx1 == cpx2 ); Console.WriteLine( "cpx1 != cpx2 ? {0}", cpx1 != cpx2 ); Console.WriteLine( "cpx1 < cpx2 ? {0}", cpx1 < cpx2 ); Console.WriteLine( "cpx1 > cpx2 ? {0}", cpx1 > cpx2 ); Console.WriteLine( "cpx1 <= cpx2 ? {0}", cpx1 <= cpx2 );

140 C H A P T E R 6 ■ OV E R L OA D I N G O P E R ATO R S
Console.WriteLine( "cpx1 >= cpx2 ? {0}", cpx1 >= cpx2 );
}
}
Notice that the operator methods merely call the methods that implement Equals() and CompareTo(). Also, I’ve followed the guideline of providing type-safe versions of the two methods by implementing IComparable<Complex> and IEquatable<Complex>, since the Complex type is a value type and I want to avoid boxing if possible.1 Additionally, I implemented the IComparable.CompareTo method explicitly to give the compiler a bigger type-safety hammer to wield by making it harder for users to inadvertently call the wrong one. Anytime you can utilize the compiler’s type system to sniff out errors at compile time rather than run time, you should do so. Had I not implemented IComparable.CompareTo() explicitly, then the compiler would have happily compiled a statement where I attempt to compare an Apple instance to a Complex instance. Of course, you would expect an InvalidCastException at run time if you were to attempt something so silly, but again, always prefer compile-time errors over runtime errors.
Conversion Operators
Conversion operators are, as the name implies, operators that convert objects of one type into objects of another. Conversion operators can allow implicit conversion as well as explicit conversion. Implicit conversion is done with a simple assignment, whereas explicit conversion requires the familiar casting syntax with the target type of the conversion provided in parentheses immediately preceding the instance being assigned from.
There is an important restriction on implicit operators. The C# standard requires that implicit operators do not throw exceptions and that they’re always guaranteed to succeed with no loss of information. If you cannot meet that requirement, then your conversion must be an explicit one. For example, when converting from one type to another, there’s always the possibility for loss of information if the target type is not as expressive as the original type. Consider the conversion from long to short. Clearly, it’s possible that information could be lost if the value in the long is greater than the highest value representable by a short. Even though an exception is not thrown in this case if truncation occurs, in some cases it may make sense to throw an exception at run time. Such a conversion must be an explicit one and require the user to use the casting syntax. Now, suppose you were going the other way and converting a short into a long. Such a conversion will always succeed, so therefore it can be implicit.
Let’s see what kind of conversion operators you should provide for Complex. I can think of at least one definite case, and that’s the conversion from double to Complex. Definitely, such a conversion should be an implicit one. Another consideration is from Complex to double. Clearly, this conversion requires an explicit conversion. (Since casting a Complex to double makes no sense anyway and is only shown here for the sake of example, you can choose to return the magnitude rather than just the real portion of the complex number when casting to a double.) Let’s take a look at an example of implementing conversion operators:
using System;
public struct Complex
{
public Complex( double real, double imaginary ) { this.real = real;
1.I describe this guideline in more detail in Chapter 5 in the section, “Explicit Interface Implementation with Value Types.”

C H A P T E R 6 ■ OV E R L OA D I N G O P E R ATO R S |
141 |
this.imaginary = imaginary;
}
// System.Object override
public override string ToString() {
return String.Format( "({0}, {1})", real, imaginary );
}
public double Magnitude { get {
return Math.Sqrt( Math.Pow(this.real, 2) + Math.Pow(this.imaginary, 2) );
}
}
public static implicit operator Complex( double d ) { return new Complex( d, 0 );
}
public static explicit operator double( Complex c ) { return c.Magnitude;
}
// Other methods omitted for clarity.
private double real; private double imaginary;
} |
|
|
public class EntryPoint |
|
|
{ |
|
|
static void Main() |
{ |
|
Complex cpx1 = |
new Complex( |
1.0, 3.0 ); |
Complex cpx2 = |
2.0; |
// Use implicit operator. |
double d = (double) cpx1; |
// Use explicit operator. |
Console.WriteLine( "cpx1 = {0}", cpx1 ); Console.WriteLine( "cpx2 = {0}", cpx2 ); Console.WriteLine( "d = {0}", d );
}
}
The syntax in the Main method is natural using conversion operators. However, be careful when implementing conversion operators to make sure that you don’t open up users to any surprises or confusion with your implicit conversions. It’s difficult to introduce confusion with explicit operators when the users of your type must use the casting syntax to get it to work. After all, how can users be surprised when they must provide the type to convert to within parentheses? On the other hand, inadvertent use or misguided use of implicit conversion can be the source of much confusion. If you write a bunch of implicit conversion operators that make no semantic sense, I guarantee your users will find themselves in a confusing spot one day when the compiler decides to do a conversion for them when they least expect it. For example, the compiler could do an implicit conversion when trying to coerce an argument on a method call. Even if the conversion operators do make semantic sense, they can still provide plenty of surprises since the compiler will have the liberty of silently converting instances of one type to another when it feels it necessary.

142 C H A P T E R 6 ■ OV E R L OA D I N G O P E R ATO R S
C# requires you to explicitly write an implicit operator on the types that you define.2 Thus, you can’t accidentally create an implicit conversion operator without realizing you’re doing so (as you can in C++). However, in order to provide these conversions, you must bend the rules of method overloading ever so slightly for this one case. Consider the case where Complex provides another explicit conversion operator to convert to an instance of Fraction as well as to an instance of double. This would give Complex two methods with the following signatures:
public static explicit operator double( Complex d ) public static explicit operator Fraction( Complex f )
These two methods take the same type, Complex, and return another type. However, the overload rules clearly state that the return type doesn’t participate in the method signature. Going by those rules, these two methods should be ambiguous and result in a compiler error. In fact, they are not ambiguous, because a special rule exists to allow the return type of conversion operators to be considered in the signature. Incidentally, the implicit and explicit keywords don’t participate in the signature of conversion operator methods. Therefore, it’s impossible to have both implicit and explicit conversion operators with the same signature. Naturally, at least one of the types in the signature of a conversion operator must be the enclosing type. It is invalid for a type Complex to implement a conversion operator from type Apples to type Oranges.
Boolean Operators
It makes sense for some types to participate in Boolean tests, such as within the parentheses of an if block or with the ternary operator ?:. In order for this to work, you have two alternatives. Let’s consider the first alternative: You can implement two conversion operators known as operator true and operator false. You must implement these two operators in pairs and allow the Complex number to participate in Boolean test expressions. Consider the following modification to the Complex type, where you now want to use it in expressions where a value of (0, 0) means false and anything else means true:
using System;
public struct Complex
{
public Complex( double real, double imaginary ) { this.real = real;
this.imaginary = imaginary;
}
// System.Object override
public override string ToString() { return String.Format( "({0}, {1})",
real, imaginary );
}
public double Magnitude { get {
return Math.Sqrt( Math.Pow(this.real, 2) + Math.Pow(this.imaginary, 2) );
}
}
2.Yes, I realize the implications of my explicit, and possible confusing, use of the words implicit and explicit. I explicitly hope that I have not implicitly confused you.

C H A P T E R 6 ■ OV E R L OA D I N G O P E R ATO R S |
143 |
public static bool operator true( Complex c ) { return (c.real != 0) || (c.imaginary != 0);
}
public static bool operator false( Complex c ) { return (c.real == 0) && (c.imaginary == 0);
}
// Other methods omitted for clarity.
private double real; private double imaginary;
}
public class EntryPoint
{
static void Main() {
Complex cpx1 = new Complex( 1.0, 3.0 ); if( cpx1 ) {
Console.WriteLine( "cpx1 is true" ); } else {
Console.WriteLine( "cpx1 is false" );
}
Complex cpx2 = new Complex( 0, 0 );
Console.WriteLine( "cpx2 is {0}", cpx2 ? "true" : "false" );
}
}
You can see the two operators for applying the true and false tests to the Complex type. Notice that the declaration syntax is a bit quirky. The syntax looks almost the same as the conversion operators, except it includes the return type of bool. I’m not quite sure why this is necessary, since you can’t provide a type other than bool as the return type. If you do, the compiler will quickly tell you that the only valid return type from operator true or operator false is a bool. Nevertheless, you must supply the return type for these two operators. Also, notice that you cannot mark these operators explicit or implicit, because they’re not conversion operators. Once you define these two operators on the type, you can use instances of Complex in Boolean test expressions, as shown in the Main() method.
Alternatively, you can choose to implement a conversion to type bool to achieve the same result. Typically, you’ll want to implement this operator implicitly for ease of use. Consider the modified form of the previous example using the implicit bool conversion operator rather than operator true and operator false:
using System;
public struct Complex
{
public Complex( double real, double imaginary ) { this.real = real;
this.imaginary = imaginary;
}
// System.Object override
public override string ToString() { return String.Format( "({0}, {1})",
real, imaginary );
}