Team LiB   Previous Section   Next Section

12.1 Using the operator Keyword

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 to create a static method, operator+. In this example, the overloaded addition operator (the operator+ method) takes two Fraction objects (the fractions you want to add) as parameters and returns 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. You can 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 "left-hand side" and reminds me that the first parameter represents the left-hand side of the operation. Similarly, rhs stands for "right-hand 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 f1 = new Fraction(3,4);
        Console.WriteLine("f1: {0}", f1.ToString());

        Fraction f2 = new Fraction(2,4);
        Console.WriteLine("f2: {0}", f2.ToString());
        
        Fraction f3 = f1 + f2;
        Console.WriteLine("f1 + f2 = f3: {0}", f3.ToString());

    }
    static void Main()
    {
        Tester t = new Tester();
        t.Run();
    }
}
Output:
f1: 3/4
f2: 2/4
f1 + f2 = f3: 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:

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 f1 and f2 are passed in to the overloaded addition operator as lhs and rhs, respectively. The new Fraction is created on the heap and returned to the calling method, Run(), where it is assigned to f3.

Fraction f3 = f1 + f2;
Console.WriteLine("f1 + f2 = f3: {0}", f3.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 9.)

Supporting Other .NET Languages

Operator overloading is a feature of C#; it is not supported by every .NET language. It is important to ensure that your class supports other ways to achieve the same results so that your C# program can work with objects created in languages that do not support operator overloading.

Thus, if you overload the addition operator (+), you might also want to provide an Add() method that does the same work. This unattractive solution is required to ensure compatibility with other .NET languages that do not support operator overloading (e.g., Visual Basic .NET). For this reason, operator overloading ought to be seen as a syntactic shortcut and not as the only path for your objects to accomplish a given task.

    Team LiB   Previous Section   Next Section