|
C# Topics: Operator Overloading |
|
Fundamentals of Overloading an Operator |
|
|
In elementary school, we learned how to perform
arithmetic operations such as adding 2 to 5 to get 7. We also learned
somehow how to add letters to create a word, how to add words to create a
sentence, and how to add symbols to create abbreviations. In programming, we
learn how to create classes that are made of more than one value.
|
When necessary, we may want to add the values of
objects of those classes to get new objects or new values. Unfortunately,
those classes are not equipped to perform arithmetic operations or
comparisons on their objects. Fortunately, if you judge it necessary, you
can write code that makes it possible.
We know many of the operators available in the C#
language. We also reviewed some of the ways they can be used. We saw that
there are rules that must be followed when using any of those operators.
Operator overloading consists of customizing the
behavior of a C# operator to be able to apply it on the values of a class.
This means that you must create a special behavior in your class for a
particular operator you want to use.
A class whose operators you want to overload primarily
starts like any class. You can add any members you judge necessary to it.
Make sure you provide appropriate and realistic constructors so the class
can be initialized appropriately. The other important detail you should take
care of is how the value of the class will be accessed outside; that is, by
clients of the class. Probably the easiest way to do this consists of
overriding the ToString() method in it.
Here is an example of a class:
public class Integer
{
public int Number;
public Integer(int n)
{
Number = n;
}
public override string ToString()
{
return Number.ToString();
}
}
Application:
Introducing Operator Overloading
|
|
- Start Microsoft Visual Studio
- To start a new application, on the main menu, click File -> New
Project...
- In the middle list, click Class Library
- Change the Name to Mathematics and click OK
- In the Solution Explorer, right-click Class1.cs and click Rename
- Type Arithmetic.cs and press Enter
- To save the project, on the Standard toolbar, click the Save All
button
- Make a note of the location and click Save
- Change the file as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Mathematics
{
public class Arithmetic
{
public static long GreatestCommonDivisor(long a, long b)
{
long remainder;
while (b != 0)
{
remainder = a % b;
a = b;
b = remainder;
}
return a;
}
}
}
- To create the library, in the Solution Explorer, right-click
Mathematics and click Build
- To create a new project, on the main menu, click File -> New
Project...
- In the left list, click Windows
- In the right list, click Empty Project
- Change the name to Algebra1
- Click OK
- To create a new file, on the main menu, click Project -> Add New
Item...
- In the left list, click Code
- In the right list, click Code File
- Change the Name to Algebra and press Add
- In the empty document, type the following:
using System;
public class Exercise
{
public static int Main()
{
return 0;
}
}
- In the Solution Explorer, under Algebra1, right-click References and
click Add Reference...
- Click the Browse tab
- Locate the folder where the Mathematics library was created
- Select Mathematics.dll
- Click OK
- To create a new class, in the Class View, right-click Algebra1 ->
Add -> Class...
- Set the Name to Rational and click Add
- Change the file as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Algebra1
{
public class Rational
{
private long num;
private long den;
public Rational(long a, long b)
{
num = a;
den = b;
}
}
}
- In the document, right-click num -> Refactor -> Encapsulate Field...
- In the Encapsulate Field dialog box, change the name to
Numerator
- Click OK
- In the Preview Reference Changes dialog box, check the code and
click Apply
- In the document, right-click fn -> den -> Encapsulate Field...
- In the Encapsulate Field dialog box, change the name to
Denominator
- Click OK
- Inside the class but outside of any method, type public override
- In the list that appears, double-click ToString
- Complete the file as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mathematics;
namespace Algebra1
{
public class Rational
{
private long num;
private long den;
public Rational(long a, long b)
{
num = a;
den = b;
}
public long Numerator
{
get
{
return num / Arithmetic.GreatestCommonDivisor(num, den);
}
set { num = value; }
}
public long Denominator
{
get
{
return den / Arithmetic.GreatestCommonDivisor(num, den);
}
set { den = value; }
}
public override string ToString()
{
long numer = num / Arithmetic.GreatestCommonDivisor(num, den);
long denom = den / Arithmetic.GreatestCommonDivisor(num, den);
if (denom == 1)
return numer.ToString();
else
return string.Format("{0}/{1}", numer, denom);
}
}
}
To overload an operator, you must create a static method
using the following formula:
public static ReturnType operator What(Argument(s))
{
}
Start with the access level. This is public. After the
public access level, use the static keyword.
Because the method is statically implemented, you should
use the name of its class as the return type. After all, the operator will
act on an object of its class type. For this reason, the method should
return a value that represents its class.
After the return type, you must use the operator
keyword. This is followed by the operator you want to customize. There are
rules you must, and suggestions you should, follow:
- Not all operators need to be customized for all classes. For
example, while it would be feasible to add two books to get a new book
that includes all the pages on both books and their combined authors, it
may not be realistic to multiply two books (to get what?). Therefore,
you should select the right operator based on your needs
- While some operators are always available to be overloaded, some
operators have restrictive rules. This means that you cannot just use
any operator anyhow
- Not all operators are overloadable
Because you are creating a type of method, after the
operator, add the parentheses. In the parentheses, you will add one or more
arguments, depending on the operator.
Since this is a method, it must have a body delimited by
curly brackets.
After defining the method, you can call it either inside
or outside of its class.
A unary operator is one that acts on one value. The C#
language provides many unary operators but not all can be overloaded. The
unary operarors available for overloading are +, -, !, ~, ++, and --.
To overload a unary operator, pass one argument to the
parentheses of the method. In the body of the method, perform the desired
operation. Here is an example of overloading a unary operaor:
using System;
public class Integer
{
public int Number;
public Integer(int n)
{
Number = n;
}
public static Integer operator ~(Integer value)
{
int number = value.Number;
int nbr = ~number;
Integer natural = new Integer(nbr);
return natural;
}
public override string ToString()
{
return Number.ToString();
}
}
public class Exercise
{
static int Main()
{
Integer nat = new Integer(1405);
Console.WriteLine("The bitwise negation of {0} is {1}",
nat, ~nat);
return 0;
}
}
This would produce:
The bitwise negation of 1405 is -1406
Press any key to continue . . .
In the same way, you can overload the other operators.
Application:
Overloading Unary Operators
|
|
- Change the Rational class as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mathematics;
namespace Algebra1
{
public class Rational
{
private long num;
private long den;
public Rational(long a, long b)
{
num = a;
den = b;
}
public long Numerator
{
get
{
return num / Arithmetic.GreatestCommonDivisor(num, den);
}
set { num = value; }
}
public long Denominator
{
get
{
return den / Arithmetic.GreatestCommonDivisor(num, den);
}
set { den = value; }
}
public static Rational operator ++(Rational number)
{
long result = number.Numerator + number.Denominator;
return new Rational(result, number.Denominator);
}
public static Rational operator --(Rational number)
{
long result = number.Numerator - number.Denominator;
return new Rational(result, number.Denominator);
}
public override string ToString()
{
long numer = num / Arithmetic.GreatestCommonDivisor(num, den);
long denom = den / Arithmetic.GreatestCommonDivisor(num, den);
if (denom == 1)
return numer.ToString();
else
return string.Format("{0}/{1}", numer, denom);
}
}
}
- Access the Algebra.cs file and change it as follows:
using System;
using Algebra1;
public class Exercise
{
public static int Main()
{
long x, y;
Rational fraction = null;
Console.WriteLine("This program allows you to get a rational");
Console.Write("Enter the numerator: ");
x = long.Parse(Console.ReadLine());
Console.Write("Enter the denominator: ");
y = long.Parse(Console.ReadLine());
Console.WriteLine();
fraction = new Rational(x, y);
Console.WriteLine("Fraction: {0}/{1} is equivalent to {2}\n",
fraction.Numerator, fraction.Denominator, fraction);
fraction = new Rational(x, y);
Rational increment = ++fraction;
Console.WriteLine("Increment: {0}/{1}++ = {2}",
x, y, increment);
fraction = new Rational(x, y);
Rational decrement = --fraction;
Console.WriteLine("Decrement: {0}/{1}-- = {2}",
x, y, decrement);
System.Console.ReadKey();
return 0;
}
}
- To execute the application, on the main menu, click Debug -> Start
Debugging
- When requested, enter the numerator as 128 and
press Enter
- Enter the denominator as 54 and press Enter
This program allows you to get a rational
Enter the numerator: 128
Enter the denominator: 54
Fraction: 128/54 is equivalent to 64/27
Increment: 128/54++ = 91/27
Decrement: 128/54-- = 37/27
Binary Arithmetic Operators
|
|
A binary operator is one that acts on two values. The
values must be of the same type. C# provides a rich set of binary operators.
Those used in arithmetics are: +, -, *, /, and %.
To overloaded a binary arithmetic operator, pass two
arguments to the parentheses of the method. The first arguments must be of
the type of class in which you are working. The second argument can be
another type. Here is an example:
using System;
public class Integer
{
public int Number;
public Integer(int n)
{
Number = n;
}
public static Integer operator ~(Integer value)
{
int number = value.Number;
int nbr = ~number;
Integer natural = new Integer(nbr);
return natural;
}
public static Integer operator +(Integer value, int add)
{
int number = value.Number;
int nbr = number + add;
Integer natural = new Integer(nbr);
return natural;
}
public override string ToString()
{
return Number.ToString();
}
}
public class Exercise
{
static int Main()
{
Integer nat = new Integer(248);
Console.WriteLine("{0} + 35 = {1}", nat, nat + 35);
return 0;
}
}
This would produce:
248 + 35 = 283
Press any key to continue . . .
If you want to perform the operations on two similar
types of values, then the second argument must be of the same type as the
first, which is the class you are using.
Application:
Overloading Binary Operators
|
|
- Access the Rational.cs file and change it as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mathematics;
namespace Algebra1
{
public class Rational
{
private long num;
private long den;
public Rational(long a, long b)
{
num = a;
den = b;
}
public long Numerator
{
get
{
return num / Arithmetic.GreatestCommonDivisor(num, den);
}
set { num = value; }
}
public long Denominator
{
get
{
return den / Arithmetic.GreatestCommonDivisor(num, den);
}
set { den = value; }
}
public static Rational operator ++(Rational number)
{
long result = number.Numerator + number.Denominator;
return new Rational(result, number.Denominator);
}
public static Rational operator --(Rational number)
{
long result = number.Numerator - number.Denominator;
return new Rational(result, number.Denominator);
}
public static Rational operator +(Rational first, Rational second)
{
long numerator = (first.Numerator * second.Denominator) + (first.Denominator * second.Numerator);
long denominator = first.Denominator * second.Denominator;
return new Rational(numerator, denominator);
}
public static Rational operator -(Rational first, Rational second)
{
long numerator = (first.Numerator * second.Denominator) - (first.Denominator * second.Numerator);
long denominator = first.Denominator * second.Denominator;
return new Rational(numerator, denominator);
}
public static Rational operator *(Rational first, Rational second)
{
long numerator = first.Numerator * second.Numerator;
long denominator = first.Denominator * second.Denominator;
return new Rational(numerator, denominator);
}
public static Rational operator /(Rational first, Rational second)
{
long numerator = first.Numerator * second.Denominator;
long denominator = first.Denominator * second.Numerator;
return new Rational(numerator, denominator);
}
public override string ToString()
{
long numer = num / Arithmetic.GreatestCommonDivisor(num, den);
long denom = den / Arithmetic.GreatestCommonDivisor(num, den);
if (denom == 1)
return numer.ToString();
else
return string.Format("{0}/{1}", numer, denom);
}
}
}
- Access the Algebra.cs file and change it as follows:
using System;
using Algebra1;
public class Exercise
{
public static int Main()
{
long a, b, c, d;
Rational firstFraction = null;
Rational secondFraction = null;
Console.Write("This program allows you to perform ");
Console.WriteLine("an arithmetic operation on two fractions");
Console.WriteLine("First Fraction");
Console.Write("\tEnter the numerator: ");
a = long.Parse(Console.ReadLine());
Console.Write("\tEnter the denominator: ");
b = long.Parse(Console.ReadLine());
Console.WriteLine("Second Fraction");
Console.Write("\tEnter the numerator: ");
c = long.Parse(Console.ReadLine());
Console.Write("\tEnter the denominator: ");
d = long.Parse(Console.ReadLine());
Console.WriteLine();
firstFraction = new Rational(a, b);
Console.WriteLine("First Fraction: {0}/{1} = {2}\n",
a, b, firstFraction);
secondFraction = new Rational(c, d);
Console.WriteLine("Second Fraction: {0}/{1} = {2}\n",
c, d, secondFraction);
Console.WriteLine();
Rational addition = firstFraction + secondFraction;
Console.WriteLine("Addition: {0}/{1} + {2}/{3} = {4}",
a, b, c, d, addition);
Rational subtraction = firstFraction - secondFraction;
Console.WriteLine("Addition: {0}/{1} - {2}/{3} = {4}",
a, b, c, d, subtraction);
Rational multiplication = firstFraction * secondFraction;
Console.WriteLine("Addition: {0}/{1} * {2}/{3} = {4}",
a, b, c, d, multiplication);
Rational division = firstFraction / secondFraction;
Console.WriteLine("Addition: {0}/{1} / {2}/{3} = {4}",
a, b, c, d, division);
System.Console.ReadKey();
return 0;
}
}
- To execute the application, on the main menu, click Debug -> Start
Debugging
- When requested, enter the first numerator as 128
and press Enter
- Enter the first denominator as 54 and press Enter
- Enter the second numerator as 88 and press Enter
- Enter the second denominator as 36 and press Enter
This program allows you to perform an arithmetic operation on two fractions
First Fraction
Enter the numerator: 128
Enter the denominator: 54
Second Fraction
Enter the numerator: 88
Enter the denominator: 36
First Fraction: 128/54 = 64/27
Second Fraction: 88/36 = 22/9
Addition: 128/54 + 88/36 = 130/27
Addition: 128/54 + 88/36 = -2/27
Addition: 128/54 + 88/36 = 1408/243
Addition: 128/54 + 88/36 = 32/33
Besides the traditional arithmetic operations, C#
support the ability to add or shift bits in the computer memory. These
operators are the bitwise conjunction operator & and its assignment
operator &=, the bitwise disjunction operator | and its assignment operator
|=, the bitwise exclusion operator ^ and its assignment operator ^=, the
left-shift operator << and its assigment operator <<=, the right-shift
operator >> and its assignment operator >>=. When necessary, overload only
the primary operator (&, |, ^, <<, >>) and its equivalent assignment
operator will be automatically provided by the compiler.
To overloaded a binary operator, pass two arguments to
the parentheses of the method. For the two bitwise shift operators, << and
>>, the first argument must be the class in which you are working and the
second argument must by a constant integer (type int). Here
is an example that overloads the left bitwise shifter <<:
using System;
public class Integer
{
public int Number;
public Integer(int n)
{
Number = n;
}
public static Integer operator ~(Integer value)
{
int number = value.Number;
int nbr = ~number;
Integer natural = new Integer(nbr);
return natural;
}
public static Integer operator <<(Integer value, int shifts)
{
int number = value.Number;
int nbr = (number << shifts );
Integer natural = new Integer(nbr);
return natural;
}
public override string ToString()
{
return Number.ToString();
}
}
public class Exercise
{
static int Main()
{
Integer nat = new Integer(1248);
Console.WriteLine("Shifting the bits of {0} to the left by {1} bits is {2}",
nat, 4, nat << 4);
return 0;
}
}
This would produce:
Shifting the bits of 1248 to the left by 4 bits is 19968
Press any key to continue . . .
For the operators that join or disjoin two series of
bits:
- The first argument must be the class on which you are working. The
second argument can be an integer. Here is an example:
using System;
public class Integer
{
public int Number;
public Integer(int n)
{
Number = n;
}
public static Integer operator &(Integer operand1, int constant)
{
int result = operand1.Number & constant;
return new Integer(result);
}
}
- The first argument must be the class on which you are working. The
second argument can be a class of the same type as the first argument.
Here is an example:
using System;
public class Integer
{
public int Number;
public Integer(int n)
{
Number = n;
}
public static Integer operator ~(Integer value)
{
int number = value.Number;
int nbr = ~number;
Integer natural = new Integer(nbr);
return natural;
}
public static Integer operator +(Integer value, int add)
{
int number = value.Number;
int nbr = number + add;
Integer natural = new Integer(nbr);
return natural;
}
public static Integer operator <<(Integer value, int shifts)
{
int number = value.Number;
int nbr = (number << shifts );
Integer natural = new Integer(nbr);
return natural;
}
public static Integer operator >>(Integer value, int shifts)
{
int number = value.Number;
int nbr = (number >> shifts);
Integer natural = new Integer(nbr);
return natural;
}
public static Integer operator &(Integer operand1, int constant)
{
int result = operand1.Number & constant;
return new Integer(result);
}
public static Integer operator &(Integer operand1, Integer operand2)
{
int result = operand1.Number & operand2.Number;
return new Integer(result);
}
public override string ToString()
{
return Number.ToString();
}
}
public class Exercise
{
public static long GreatestCommonDivisor(long a, long b)
{
long remainder;
while (b != 0)
{
remainder = a % b;
a = b;
b = remainder;
}
return a;
}
static int Main()
{
int x, y;
Console.WriteLine("This program allows you to biwise join two values");
Console.Write("Enter Value 1: ");
x = int.Parse(Console.ReadLine());
Console.Write("Enter Value 2: ");
y = int.Parse(Console.ReadLine());
Integer i1 = new Integer(x);
Integer i2 = new Integer(y);
Console.WriteLine("\n{0} & 45 = {1}", i1, i1 & 45);
Console.WriteLine("\n{0} & {1} = {2}", i1, i2, i1 & i2);
return 0;
}
}
This would produce:
This program allows you to biwise join two values
Enter Value 1: 286
Enter Value 2: 1524
286 & 45 = 12
286 & 1524 = 276
Press any key to continue . . .
In the same way you can overload the other operators.
As its name implies, a comparison is used to find out
whether one of two values is higher than the other. The values must be of
the same type. Comparison operators go in pair. That is, if you want to
compare two values for equality, you must also be able to know when they are
different. For this reason, if you decide to overload a comparison operator,
you must also overload its opposite:
- If you overload ==, you must also overload != and vice versa
- If you overload <, you must also overload > and vice versa
- If you overload <=, you must also overload >= and vice versa
We saw that the Object class is
equipped with a method named Equals. That method makes it
possible to find out whether one object is equal to another. And since all
classes in a C# application derive from Object, if you
decide to overload a couple of comparison operators, you should also
override the Equals() method.
Application:
Overloading Binary Operators
|
|
- Access the Rational.cs file and change it as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mathematics;
namespace Algebra1
{
public class Rational
{
private long num;
private long den;
public Rational(long a, long b)
{
num = a;
den = b;
}
public long Numerator
{
get
{
return num / Arithmetic.GreatestCommonDivisor(num, den);
}
set { num = value; }
}
public long Denominator
{
get
{
return den / Arithmetic.GreatestCommonDivisor(num, den);
}
set { den = value; }
}
public static Rational operator ++(Rational number)
{
long result = number.Numerator + number.Denominator;
return new Rational(result, number.Denominator);
}
public static Rational operator --(Rational number)
{
long result = number.Numerator - number.Denominator;
return new Rational(result, number.Denominator);
}
public static Rational operator +(Rational first, Rational second)
{
long numerator = (first.Numerator * second.Denominator) +
(first.Denominator * second.Numerator);
long denominator = first.Denominator * second.Denominator;
return new Rational(numerator, denominator);
}
public static Rational operator -(Rational first, Rational second)
{
long numerator = (first.Numerator * second.Denominator) -
(first.Denominator * second.Numerator);
long denominator = first.Denominator * second.Denominator;
return new Rational(numerator, denominator);
}
public static Rational operator *(Rational first, Rational second)
{
long numerator = first.Numerator * second.Numerator;
long denominator = first.Denominator * second.Denominator;
return new Rational(numerator, denominator);
}
public static Rational operator /(Rational first, Rational second)
{
long numerator = first.Numerator * second.Denominator;
long denominator = first.Denominator * second.Numerator;
return new Rational(numerator, denominator);
}
public static bool operator ==(Rational first, Rational second)
{
if( (first.Numerator == second.Numerator) &&
(first.Denominator == second.Denominator) )
return true;
else
return false;
}
public static bool operator !=(Rational first, Rational second)
{
if( (first.Numerator != second.Numerator) ||
(first.Denominator != second.Denominator) )
return true;
else
return false;
}
public override string ToString()
{
long numer = num / Arithmetic.GreatestCommonDivisor(num, den);
long denom = den / Arithmetic.GreatestCommonDivisor(num, den);
if (denom == 1)
return numer.ToString();
else
return string.Format("{0}/{1}", numer, denom);
}
public override bool Equals(object obj)
{
Rational rat = (Rational)obj;
if( (this.num == rat.num) &&
(this.den == rat.den) )
return true;
return false;
}
}
}
- Access the Algebra.cs file and change it as follows:
using System;
using Algebra1;
public class Exercise
{
public static int Main()
{
long a, b, c, d;
Rational firstFraction = null;
Rational secondFraction = null;
Console.Write("This program allows you to perform ");
Console.WriteLine("an arithmetic operation on two fractions");
Console.WriteLine("First Fraction");
Console.Write("\tEnter the numerator: ");
a = long.Parse(Console.ReadLine());
Console.Write("\tEnter the denominator: ");
b = long.Parse(Console.ReadLine());
Console.WriteLine("Second Fraction");
Console.Write("\tEnter the numerator: ");
c = long.Parse(Console.ReadLine());
Console.Write("\tEnter the denominator: ");
d = long.Parse(Console.ReadLine());
Console.WriteLine();
firstFraction = new Rational(a, b);
Console.WriteLine("First Fraction: {0}/{1} = {2}\n",
a, b , firstFraction);
secondFraction = new Rational(c, d);
Console.WriteLine("Second Fraction: {0}/{1} = {2}\n",
c, d, secondFraction);
Console.WriteLine();
Console.WriteLine("{0}/{1} == {2}/{3} = {4}",
a, b, c, d, firstFraction == secondFraction);
Console.WriteLine("{0}/{1} != {2}/{3} = {4}",
a, b, c, d, firstFraction != secondFraction);
System.Console.ReadKey();
return 0;
}
}
- To execute the application, on the main menu, click Debug -> Start
Debugging
- When requested, enter the first numerator as 36 and
press Enter
- Enter the first denominator as 8 and press Enter
- Enter the second numerator as 54 and press Enter
- Enter the second denominator as 12 and press Enter
This program allows you to perform an arithmetic operation on two fractions
First Fraction
Enter the numerator: 36
Enter the denominator: 8
Second Fraction
Enter the numerator: 54
Enter the denominator: 12
First Fraction: 36/8 = 9/2
Second Fraction: 54/12 = 9/2
36/8 == 54/12 = True
36/8 != 54/12 = False
- To execute the application again, on the main menu, click Debug ->
Start Debugging
- When requested, enter the first numerator as 36 and
press Enter
- Enter the first denominator as 14 and press Enter
- Enter the second numerator as 45 and press Enter
- Enter the second denominator as 20 and press Enter
This program allows you to perform an arithmetic operation on two fractions
First Fraction
Enter the numerator: 26
Enter the denominator: 14
Second Fraction
Enter the numerator: 45
Enter the denominator: 20
First Fraction: 26/14 = 13/7
Second Fraction: 45/20 = 9/4
26/14 == 45/20 = False
26/14 != 45/20 = True
Some operators either cannot be overloaded or depend on
other operators being overloaded:
- The following operators cannot be overloaded: the conjunction &&,
the disjunction ||, the assignment =, the period ., the ternary ?:, the
C++ pointer access ->, the object creator new, the
object compatibility verifier is, the size of
sizeof, and the type of typeof
- true and false can be overloaded but there is hardly any reason to
do it. So, unless you have a good justified reason, don't overload them
- [] cannot be overloaded but the alternative is to create an indexer
as we will see in Lessons 39 and 40
Application:
Ending the Lesson
|
|
- Close your programming environment
- When asked whether you want to save, click No
|
|