COMP10068 - Advanced Programming in .NET
Week 5
Operator OverloadingThere are various C# built-in types, such as
integer (int) and Boolean (bool), and there are also various operators that allow these data types to be manipulatedarithmetic: + - * / %comparisons: == != < <= > >=logical: && || ! & | ^
Using most of these operators with basic types is simple and intuitive
Using these operators with created classes will cause errors
Designing the Fraction ClassCreate a new type called Fraction to store
fractional numbers:Fraction first = new Fraction(1, 2); //create ½Fraction second = new Fraction(3, 4); //create ¾
Constructors take the first parameter as the numerator, and the second parameter as the denominator
To give the Fraction class all the functionality of the built-in types, there will be the need to perform arithmetic on instances of Fraction Add two Fractions, multiple them, etc Conversions to or from built-in types should also be
available
Each of these operations could be implemented using methods, but it’s not intuitively obvious:
Fraction sum = first.Add(second);
This is hard to readIt’s not how a user would immediately expect
addition to workIt doesn’t look like addition
It would be much better to be able to write:Fraction sum = first + second;
This is intuitive and easy to useIt is consistent with how built-in operators are added
Using the operator KeywordC# allows methods and constructors to be
overloadedIt also enables most operators to be
overloaded as well to make them sensitive to the context in which they are usedThe syntax is to write the keyword operator
followed by the operator to overloadOperators are public and static methodsThe return value of an operator represents the
result of an operationThe operator’s parameters are the operands
Define an addition operator for a Fraction class the same as any other method but with a difference:Instead of a method name, use operator+Addition is a binary operation, requiring two
operands, so the operator+ method will require two parameters of type Fraction
Addition has a result, requiring a return value of type Fractionpublic static Fraction operator +(Fraction lhs, Fraction rhs)
The parameters of lhs and rhs stand for left-hand side and right-hand side
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; }
//return a string representation of the fraction public override string ToString() { string s = numerator.ToString() + "/" + denominator.ToString(); return s; }
//overloaded + takes two fractions and returns their sum public static Fraction operator +(Fraction lhs, Fraction rhs) { //like fractions (shared denominators) 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 is to cross-multiply: // 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); }}
public class Tester{ public void Run() { Fraction f1 = new Fraction(3, 4); Console.WriteLine("f1: {0}", f1);
Fraction f2 = new Fraction(2, 4); Console.WriteLine("f2: {0}", f2);
Fraction f3 = f1 + f2; //test the overloaded addition operator Console.WriteLine("f1 + f2 = f3: {0}", f3);}
public static void Main() { Tester t = new Tester(); t.Run(); }}
Output would be:f1: 3/4f2: 2/4f1 + f2 = f3: 5/4
Creating Useful OperatorsOperator overloading can make code more
intuitive and allow them to be used similar to built-in typesHowever, they way they are used should make
senseOperators that don’t naturally follow their
traditional meanings will be confusingUse operator overloading when it makes an
application clearer than accomplishing the sameoperations with explicit method calls
Overload operators to perform the same function or similar functions on class objects as the operators perform on objects of simple types
At least one argument of an overloaded operator method must be a reference to an object of the class in which the operator is overloadedThis prevents programmers from changing
how operators work on simple types
The Equals OperatorIt’s very common to overload the == operator to
determine whether two objects are equalWhat “equal” means is up to the programmer,
although the criteria should be reasonableTwo Employee objects might be equal if they have
the same name or perhaps if the same employee IDOverloading the == operator works the same way
as overloading any other operatorpublic static bool operator ==(Fraction lhs, Fraction rhs)
The == operator always returns a Boolean value (true or false)
C# insists that if the equals operator is overloaded, then the not-equals (!=) operator must also be overloadedIt’s good programming practice to have the
inequality operator delegate its work to the equality operator
The != operator will return the opposite of the value of the == operator
Similarly, the less than (<) and greater than (>) operators must be paired as must the less than or equal to (<=) and greater than or equal to (>=) operators
The Object class offers a virtual method called Equals()If the equals operator is overloaded, it is recommended
that the Equals() method also be overriddenOverriding the Equals() method allows the class to be
compatible with other .NET languages that do not overload operators but still support method overloading
That way, if the == operator can’t be used, the Equals() method is still availablepublic virtual bool Equals(object o)
Ensure that one Fraction object is being compared with another Fraction object, otherwise they cannot be equal
//add these methods to the existing Fraction class
//test whether two Fractions are equal public static bool operator ==(Fraction lhs, Fraction rhs) { if(lhs.denominator == rhs.denominator && lhs.numerator == rhs.numerator) return true;
//code here to handle unlike Fractions return false; }
//delegates to operator == public static bool operator !=(Fraction lhs, Fraction rhs) { return !(lhs == rhs); //just the opposite of == }
//tests for same types, then delegates public override bool Equals(object o) { if(!(o is Fraction)) //objects have to be the same type to be equal return false;
return this == (Fraction)o; //cast the object as a Fraction and delegate to == }
//can now add to the Run() method
Fraction f4 = new Fraction(5, 4);
if(f4 == f3) Console.WriteLine("f4: {0} == f3: {1}", f4, f3);
if(f4 != f2) Console.WriteLine("f4: {0} != f2: {1}", f4, f2);
if(f4.Equals(f3))
Console.WriteLine("{0}.Equals({1})", f4, f3);
Generates this output:f4: 5/4 == f3: 5/4f4: 5/4 != f2: 2/45/4.Equals(5/4)
Conversion OperatorsC# will convert an int to a long implicitly, but will
only allow a long to be converted to an int explicitlyThe conversion from int to long is implicit because
any int will fit into a longConversion from long to int must be explicit because
it is possible to lose information in the conversionIt would be useful for Fraction objects to be
converted to intrinsic types (such as int) and backGiven an int, implicit conversion to a Fraction can
be supported because any whole value is equal to that value over 1 (15 == 15/1)
Given a Fraction, an explicit conversion back to an integer might be provided with the understanding that some information might be lostThe Fraction 9/4 might be converted to the integer
value 2 (truncating to the nearest whole number)To implement the conversion operator, the keyword operator is still used, but instead of a symbol to override, the type to be converted to is used instead
public static implicit operator Fraction(int theInt)
Use the implicit operator when the conversion is guaranteed to succeed and no information will be lost, otherwise, use the explicit operator instead
//add these methods to the existing Fraction class
//overload the constructor to create a fraction from a whole number
public Fraction(int wholeNumber)
{
numerator = wholeNumber; //any whole number over 1 is a Fraction
denominator = 1;
}
//converts ints to Fractions implicitly
public static implicit operator Fraction(int theInt)
{
return new Fraction(theInt); //invoke constructor for a new Fraction
}
//convert Fractions to ints explicitly
public static explicit operator int(Fraction theFraction)
{
return theFraction.numerator / theFraction.denominator; //return an int
}
//can now add to the Run() method
Fraction f5 = f4 + 5; //implicit conversion of 5 to a Fraction Console.WriteLine("f4 + 5 = f5: {0}", f5);
int truncated = (int)f5; //explicit conversion of f5 to an int
Console.WriteLine("When you truncate f5 you get {0}", truncated);
Generates this output:f4 + 5 = f5: 25/4When you truncate f5 you get 6
The cast from an int to a Fraction is perfectly safe, so it can be implicit The implementation of this is to create a new Fraction object and to return it
The implicit cast operator causes the constructor to be invoked
Top Related