One of the goals of C# is to allow you to create new classes that
have all the functionality of built-in types such as integer (int
) and Boolean (bool
). (See Chapter 3 for a discussion of these
intrinsic types.) For example, suppose you define a type (Fraction
) to represent fractional numbers. The
following constructors establish two Fraction objects, the first
representing 1/2 and the second representing 3/4:
Fraction firstFraction = new Fraction(1,2); // create 1/2 Fraction secondFraction = new Fraction(3,4); // create 3/4
The assumption here, of course, is that the first parameter will represent the numerator, and the second parameter will represent the denominator.
Ensuring that the Fraction
class
has all the functionality of the built-in types means that you must be
able to perform arithmetic on instances of your fractions (add two
fractions, multiply, and so on) and to convert fractions to and from
built-in types such as int
.
Hypothetically, you could implement methods for each of these
operations. For example, for your Fraction
type, you might create an Add( )
method and invoke it by writing a
statement such as:
// add 1/2 and 3/4 Fraction theSum = firstFraction.Add(secondFraction);
Although this will work, it is ugly and not how the built-in types are used. It would be much better to be able to write:
// add 1/2 and 3/4 using + operator Fraction theSum = firstFraction + secondFraction;
Statements that use operators (in this case, the plus sign) are intuitive and easy to use. Equally important, this use of operators is consistent with how built-in types are added, multiplied, and so forth.
In this chapter, you will learn techniques for adding standard
operators to be used with your user-defined types. When you create an
operator for a class, you say you have “overloaded” that operator, much as
you might overload a member method (discussed in Chapter 8). The C# syntax for
overloading an operator is to write the keyword operator
followed by the operator to overload.
The next section demonstrates how you might do this for the Fraction
class.
The chapter also discusses the special case of overloading the
equals operator, which is used to test whether two objects are equal.
Overriding this operator also requires you to override the class’s
Equals( )
method.
Later in the chapter, you will learn how to add conversion operators to your user-defined types so that they can be implicitly and explicitly converted to other types.
In C#, operators are static methods. The return value of an operator represents the result of an operation. The operator’s parameters are the operands.
Thus, to create an addition operator for a Fraction
class, you use the C# syntax of
combining the operator
keyword with
the plus sign (+) operator combined with the keyword static
. For example, the overloaded addition
operator (the operator+
method) takes
two Fraction
objects (the fractions
you want to add) as parameters and returns a reference to another
Fraction
object representing the sum
of the two parameters. Here is its signature:
public static Fraction operator+(Fraction lhs, Fraction rhs)
And here’s what you can do with it. Assume, for instance, you’ve defined two fractions representing the portion of a pie you’ve eaten for breakfast and lunch, respectively. (You love pie.)
Fraction pieIAteForBreakfast = new Fraction(1,2); // 1/2 of a pie Fraction pieIAteForLunch = new Fraction(1,3); // 1/3 of a pie
The overloaded operator+
allows
you to figure out how much pie you’ve eaten in total. (And there’s still
1/6 of a pie left over for dinner!) You write:
Fraction totalPigOut = pieIAteForBreakfast + pieIAteForLunch;
The compiler takes the first operand (pieIAteForBreakfast
) and passes it to operator+
as the parameter lhs
; it passes the second operand (pieIAteForLunch
) as rhs
. These two Fractions
are then added, and the result is
returned and assigned to the Fraction
object named totalPigOut
.
It is my convention to name the parameters to a binary operator
lhs
and rhs
. A binary operator is an operator that
takes two operands. The parameter name lhs
stands for “lefthand side” and reminds
me that the first parameter represents the lefthand side of the
operation. Similarly, rhs
stands
for “righthand side.”
To see how this works, you’ll create a Fraction
class, as described previously. The
complete listing is shown in Example 12-1, followed by a
detailed analysis.
Example 12-1. Implementing operator+ for Fraction
using System; public class Fraction { private int numerator; private int denominator; // create a fraction by passing in the numerator // and denominator public Fraction( int numerator, int denominator ) { this.numerator = numerator; this.denominator = denominator; } // overloaded operator + takes two fractions // and returns their sum public static Fraction operator+( Fraction lhs, Fraction rhs ) { // like fractions (shared denominator) can be added // by adding their numerators if ( lhs.denominator == rhs.denominator ) { return new Fraction( lhs.numerator + rhs.numerator, lhs.denominator ); } // simplistic solution for unlike fractions // 1/2 + 3/4 == (1*4) + (3*2) / (2*4) == 10/8 // this method does not reduce. int firstProduct = lhs.numerator * rhs.denominator; int secondProduct = rhs.numerator * lhs.denominator; return new Fraction( firstProduct + secondProduct, lhs.denominator * rhs.denominator ); } // return a string representation of the fraction public override string ToString( ) { String s = numerator.ToString( ) + "/" + denominator.ToString( ); return s; } } public class Tester { public void Run( ) { Fraction firstFraction = new Fraction( 3, 4 ); Console.WriteLine( "firstFraction: {0}", firstFraction.ToString( ) ); Fraction secondFraction = new Fraction( 2, 4 ); Console.WriteLine( "secondFraction: {0}", secondFraction.ToString( ) ); Fraction sumOfTwoFractions = firstFraction + secondFraction; Console.WriteLine( "firstFraction + secondFraction = sumOfTwoFractions: {0}", sumOfTwoFractions.ToString( ) ); } static void Main( ) { Tester t = new Tester( ); t.Run( ); } }
The output looks like this:
firstFraction: 3/4 secondFraction: 2/4 firstFraction + secondFraction = sumOfTwoFractions: 5/4
In Example 12-1,
you start by creating a Fraction
class. The private member data is the numerator
and denominator
, stored as integers:
public class Fraction { private int numerator; private int denominator;
The constructor just initializes these values. The overloaded
addition operator takes two Fraction
objects, returns a Fraction
, and is
marked static
:
public static Fraction operator+(Fraction lhs, Fraction rhs) {
If the denominators for the fractions are the same, you add the
numerators and return a new Fraction
object created by passing in the sum of the numerators as the new
numerator and the shared denominator as the new denominator:
if (lhs.denominator == rhs.denominator) { return new Fraction(lhs.numerator+rhs.numerator, lhs.denominator); }
The Fraction
objects firstFraction
and secondFraction
are passed in to the overloaded
addition operator as lhs
and rhs
, respectively. The new Fraction
is created on the heap, and a
reference is returned to the calling method, Run( )
, where it is assigned to sumOfTwoFractions
:
Fraction sumOfTwoFractions = firstFraction + secondFraction; Console.WriteLine( "firstFraction + secondFraction = sumOfTwoFractions: {0}", sumOfTwoFractions.ToString( ) );
Back in the implementation of the operator, if the denominators
are different, you cross-multiply before adding. This allows you to add
like Fractions
.
int firstProduct = lhs.numerator * rhs.denominator; int secondProduct = rhs.numerator * lhs.denominator; return new Fraction( firstProduct + secondProduct, lhs.denominator * rhs.denominator );
The two local variables, firstProduct
and secondProduct
, are temporary; they are
destroyed when the method returns. The new Fraction
created, however, is not temporary;
it is created on the heap, and a reference is returned as
previously.
A good Fraction
class would,
no doubt, implement all the arithmetic operators (addition,
subtraction, multiplication, division). To overload the multiplication
operator, you would write operator*
; to overload the division
operator, you would write operator/
.
The Fraction
class overrides
the ToString( )
method (inherited
from Object
) to allow you to display
the fractions by passing them to Console.WriteLine( )
. (For more information
about overloading methods, see Chapter 8.)