C#
will
convert int
to
long
implicitly, and allow 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 want 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.
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.)
Example 6-1. Defining conversions and operators for the fraction class operators
using System; public class Fraction { 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)
{
System.Console.WriteLine("In implicit conversion to Fraction");
return new Fraction(theInt);
}
public static explicit operator int(Fraction theFraction)
{
System.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; } private int numerator; private int denominator; } 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
taking a numerator and denominator, the other taking 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,1); }
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
.
You delegate this responsibility to the constructor which 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 1
. 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.
You 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, you implement only
addition, and even here you simplify greatly. You check to see if the
denominators are the same; if so, you add the 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, you 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. (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; }
You create a new string object by calling the ToString( )
method on numerator. Since
numerator
is an object, this causes the compiler
to implicitly box the integer (creating an object) and calls
ToString( )
on that object, returning a string
representation of the numerator. You 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,
and you test whether it is equal to f2
. If so, you
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
18.220.245.140