[ Team LiB ] Previous Section Next Section

6.6 Conversion Operators

C# converts int to long implicitly, and allows you to convert long to int explicitly. The conversion from int to long is implicit because you know that any int will fit into the memory representation of a long. The reverse operation, from long to int, must be explicit (using a cast) because it is possible to lose information in the conversion:

int myInt = 5;
long myLong;
myLong = myInt;        // implicit
myInt = (int) myLong;  // explicit

You must have the same functionality for your fractions. Given an int, you can support an implicit conversion to a fraction because any whole value is equal to that value over 1 (e.g., 15==15/1).

Given a fraction, you might want to provide an explicit conversion back to an integer, understanding that some value might be lost. Thus, you might convert 9/4 to the integer value 2.

The keyword implicit is used when the conversion is guaranteed to succeed and no information will be lost; otherwise explicit is used.

C and C++ programmers take note: Make sure to use implicit whenever you don't use explicit!

Example 6-1 illustrates how you might implement implicit and explicit conversions, and some of the operators of the Fraction class. (Although I've used Console.WriteLine( ) to print messages illustrating which method we're entering, the better way to pursue this kind of trace is with the debugger. You can place a breakpoint on each of the test statements, and then step into the code, watching the invocation of the constructors as they occur.) When you compile this example, it will generate some warnings because GetHashCode( ) is not implemented (see Chapter 9).

Example 6-1. Defining conversions and operators for the fraction class operators
using System;

public class Fraction
{
    private int numerator;
    private int denominator;

    public Fraction(int numerator, int denominator)
    {
        Console.WriteLine("In Fraction Constructor(int, int)");
        this.numerator=numerator;
        this.denominator=denominator;
    }

    public Fraction(int wholeNumber)
    {
        Console.WriteLine("In Fraction Constructor(int)");
        numerator = wholeNumber;
        denominator = 1;
    }

    
    public static implicit operator Fraction(int theInt)
    {
        Console.WriteLine("In implicit conversion to Fraction");
        return new Fraction(theInt);
    }

    public static explicit operator int(Fraction theFraction)
    {
        Console.WriteLine("In explicit conversion to int");
        return theFraction.numerator / 
            theFraction.denominator;
    }


    public static bool operator==(Fraction lhs, Fraction rhs)
    {
        Console.WriteLine("In operator ==");
        if (lhs.denominator == rhs.denominator && 
            lhs.numerator == rhs.numerator)
        {
            return true;
        }
        // code here to handle unlike fractions
       return false;
    }

    public static bool operator !=(Fraction lhs, Fraction rhs)
    {
        Console.WriteLine("In operator !=");
        
        return !(lhs==rhs);
    }

    public override bool Equals(object o)
    {
        Console.WriteLine("In method Equals");
        if (! (o is Fraction) )
        {
            return false;
        }
        return this == (Fraction) o;
    }

    public static Fraction operator+(Fraction lhs, Fraction rhs)
    {
        Console.WriteLine("In operator+");
        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
        int firstProduct = lhs.numerator * rhs.denominator;
        int secondProduct = rhs.numerator * lhs.denominator;
        return new Fraction(
            firstProduct + secondProduct,       
            lhs.denominator * rhs.denominator
        );
    }

    public override string ToString( )
    {
        String s = numerator.ToString( ) + "/" +
            denominator.ToString( );
        return s;
    }
}


public class Tester
{
    static void Main( )
    {
        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( ));

        Fraction f4 = f3 + 5;
        Console.WriteLine("f3 + 5 = f4: {0}", f4.ToString( ));

        Fraction f5 = new Fraction(2,4);
        if (f5 == f2)
        {
            Console.WriteLine("F5: {0} == F2: {1}", 
                f5.ToString( ), 
                f2.ToString( ));
        }

    }
}

The Fraction class begins with two constructors. One takes a numerator and denominator, the other takes a whole number. The constructors are followed by the declaration of two conversion operators. The first conversion operator changes an integer into a Fraction:

public static implicit operator Fraction(int theInt)
{
   return new Fraction(theInt);
}

This conversion is marked implicit because any whole number (int) can be converted to a Fraction by setting the numerator to the int and the denominator to 1. Delegate this responsibility to the constructor that takes an int.

The second conversion operator is for the explicit conversion of Fractions into integers:

public static explicit operator int(Fraction theFraction)
{
   return theFraction.numerator / 
        theFraction.denominator;
}

Because this example uses integer division, it will truncate the value. Thus, if the fraction is 15/16, the resulting integer value will be 0. A more sophisticated conversion operator might accomplish rounding.

The conversion operators are followed by the equals operator (==) and the not equals operator (!=). Remember that if you implement one of these equals operators, you must implement the other.

You have defined value equality for a Fraction such that the numerators and denominators must match. For this exercise, 3/4 and 6/8 are not considered equal. Again, a more sophisticated implementation would reduce these fractions and notice the equality.

Include an override of the object class' Equals( ) method so that your Fraction objects can be treated polymorphically with any other object. Your implementation is to delegate the evaluation of equality to the equality operator.

A Fraction class would, no doubt, implement all the arithmetic operators (addition, subtraction, multiplication, division). To keep the illustration simple, implement only addition, and even here you simplify greatly. Check to see if the denominators are the same; if so, add the following numerators:

public static Fraction operator+(Fraction lhs, Fraction rhs)
{
    if (lhs.denominator == rhs.denominator)
    {
        return new Fraction(lhs.numerator+rhs.numerator,
            lhs.denominator);
    }

If the denominators are not the same, cross multiply:

int firstProduct = lhs.numerator * rhs.denominator;
int secondProduct = rhs.numerator * lhs.denominator;
return new Fraction(
    firstProduct + secondProduct,       
    lhs.denominator * rhs.denominator

This code is best understood with an example. If you were adding 1/2 and 3/4, you can multiply the first numerator (1) by the second denominator (4) and store the result (4) in firstProduct. You can also multiply the second numerator (3) by the first denominator (2) and store that result (6) in secondProduct. You add these products (6+4) to a sum of 10, which is the numerator for the answer. You then multiply the two denominators (2*4) to generate the new denominator (8). The resulting fraction (10/8) is the correct answer.[1]

[1] To recap: 1/2=4/8, 3/4=6/8, 4/8+6/8=10/8. The example does not reduce the fraction, to keep it simple.

Finally, to enable debugging of the new Fraction class, the code is written so that Fraction is able to return its value as a string in the format numerator/denominator:

public override string ToString( )
{
   String s = numerator.ToString( ) + "/" +
      denominator.ToString( );
   return s;
}

Create a new string object by calling the ToString( ) method on numerator. Since numerator is an int, and ints are value types, the call to the ToString( ) method causes the compiler to implicitly box the integer (creating an object) and calls ToString( ) on that object, returning a string representation of the numerator. Concatenate the string "/" and then concatenate the string that results from calling ToString( ) on the denominator.

With your Fraction class in hand, you're ready to test. Your first tests create simple fractions, 3/4 and 2/4:

Fraction f1 = new Fraction(3,4);
Console.WriteLine("f1: {0}", f1.ToString( ));

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

The output from this is what you would expect—the invocation of the constructors and the value printed in WriteLine( ):

In Fraction Constructor(int, int)
f1: 3/4
In Fraction Constructor(int, int)
f2: 2/4

The next line in Main( ) invokes the static operator+. The purpose of this operator is to add two fractions and return the sum in a new fraction:

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

Examining the output reveals how operator+ works:

In operator+
In Fraction Constructor(int, int)
f1 + f2 = f3: 5/4

The operator+ is invoked, and then the constructor for f3, taking the two int values representing the numerator and denominator of the resulting new fraction.

The next test in Main( ) adds an int to the Fraction f3 and assigns the resulting value to a new Fraction, f4:

Fraction f4 = f3 + 5;
Console.WriteLine("f3 + 5: {0}", f4.ToString( ));

The output shows the steps for the various conversions:

In implicit conversion to Fraction
In Fraction Constructor(int)
In operator+
In Fraction Constructor(int, int)
f3 + 5 =  f4: 25/4

Notice that the implicit conversion operator was invoked to convert 5 to a fraction. In the return statement from the implicit conversion operator, the Fraction constructor was called, creating the fraction 5/1. This new fraction was then passed along with Fraction f3 to operator+, and the sum was passed to the constructor for f4.

In our final test, a new fraction (f5) is created. Test whether it is equal to f2. If so, print their values:

Fraction f5 = new Fraction(2,4);
if (f5 == f2)
{
    Console.WriteLine("F5: {0} == F2: {1}", 
        f5.ToString( ), 
        f2.ToString( ));
}

The output shows the creation of f5, and then the invocation of the overloaded equals operator:

In Fraction Constructor(int, int)
In operator ==
F5: 2/4 == F2: 2/4      
    [ Team LiB ] Previous Section Next Section