In Lesson 25 you learned how to overload a class's methods. C# also lets you overload operators such as +
and *
to give them new meanings when working with the structures and classes that you create. For example, you could overload the +
operator so the program would know how to add a Student
object and a Course
object. Sometimes that allows you to use a more natural syntax when you're working with objects.
In this lesson, you learn how to overload operators so you can use them to manipulate objects.
In C#, you can overload the unary, binary, and comparison operators listed in Table 26.1.
Type | Operators |
Unary | + , – , ! , ˜ , ++ , -- |
Binary | + , – , * , / , % , & , | , ^ , ≪ , ≫ |
Comparison | == , != , < , > , <= , >= |
The comparison operators come in pairs. For example, if you overload the <
operator, you must also overload the >
operator.
The compound assignment operators (+=
, -=
, *=
, /=
, %=
, &=
, |=
, ^=
, ≪=
, and ≫=
) are automatically overloaded when you overload the corresponding binary operator. For example, if you overload *
, C# automatically overloads *=
for you.
The syntax for overloading operators is easiest to understand by looking at examples. The following sections explain how to overload the different types of operators.
The following code shows how you can overload the unary -
operator for the ComplexNumber
class:
public static ComplexNumber operator -(ComplexNumber me)
{
return new ComplexNumber(-me.Real, -me.Imaginary);
}
The method begins with public static
followed by the operator's return type. In this case the operator returns a ComplexNumber
because the negation of a complex number is another complex number.
Next comes the keyword operator
and the operator's symbol, in this case -
.
The parameter list tells on which class the operator should be defined. Because this code is defining an operator for the ComplexNumber
class, that's the parameter's data type. I often name this parameter me
to help me remember that this is the object to which the operator is being applied.
Note that the overload must be declared inside the class used by the parameter. In this case, the parameter is a ComplexNumber
so this code must be in the ComplexNumber
class.
The code inside this method simply negates the ComplexNumber
's real and imaginary parts and returns a new ComplexNumber
.
The following code shows how a program might use this operator:
ComplexNumber a = new ComplexNumber(1, 2); // 1 + 2i
ComplexNumber minusA = -a; // -1 - 2i
Overloading binary operators is similar to overloading unary operators except the operator takes a second parameter. The first parameter is still the object to which the operator is being applied.
For example, the following code overloads the binary -
operator to subtract two ComplexNumber
s:
public static ComplexNumber operator -(ComplexNumber me, ComplexNumber other)
{
return new ComplexNumber(me.Real - other.Real,
me.Imaginary - other.Imaginary);
}
The first parameter gives the object on the left of the –
sign and the second parameter gives the object on the right. To help keep them straight, I often name the parameters me
and other
.
Note that the overload must be declared inside a class or structure used by one of the parameters. In this case, both parameters are ComplexNumber
s so this code must be in the ComplexNumber
class.
Although this example subtracts two ComplexNumber
s, in general the parameters do not need to have the same data types. The following code defines the binary –
operator for subtracting a double
from a ComplexNumber
:
public static ComplexNumber operator -(ComplexNumber me, double x)
{
return new ComplexNumber(me.Real - x, me.Imaginary);
}
Note that this is not the same as subtracting a ComplexNumber
from a double
. If you want to handle that situation as well, you need the following separate overload:
public static ComplexNumber operator -(double me, ComplexNumber other)
{
return new ComplexNumber(me - other.Real, other.Imaginary);
}
With these overloads, a program could execute the following code:
ComplexNumber a = new ComplexNumber(2, 3);
ComplexNumber b = new ComplexNumber(4, 5);
ComplexNumber c = a - b; // ComplexNumber - ComplexNumber
ComplexNumber d = a - 10; // ComplexNumber - double
ComplexNumber e = 10 - a; // double - ComplexNumber
The comparison operators are simply binary operators that return a boolean result. The only oddity to these is that they come in pairs. For example, if you define ==
, then you must also define !=
. The pairs are ==
and !=
, <
and >
, and <=
and >=
.
The following code shows how you could overload the <
and >
operators for the ComplexNumber
class:
// Return the number's magnitude.
public double Magnitude
{
get { return Math.Sqrt(Real * Real + Imaginary * Imaginary); }
}
public static bool operator <(ComplexNumber me, ComplexNumber other)
{
return (me.Magnitude() < other.Magnitude());
}
public static bool operator >(ComplexNumber me, ComplexNumber other)
{
return (me.Magnitude() > other.Magnitude());
}
C# provides one more kind of operator you can overload: conversion operators. These let a C# program convert one data type to another, either implicitly or explicitly. For example, consider the following code:
int i = 10;
double d = i; // Implicitly convert i into a double.
int j = (int)d; // Explicitly convert d into an int.
The first statement declares and initializes the integer i
. The next statement sets the double
variable d
equal to the variable i
. Because any int
value can fit in a double
variable, this conversion is safe so C# allows you to make it implicitly.
The third statement sets integer variable j
equal to the value in the double
variable. Not all double
values can fit in an int
, so C# won't let you make that assignment implicitly. The cast operator (int)
explicitly tells C# to make the conversion anyway and you're willing to take the risk that the value may not fit.
You can overload conversion operators to allow your program to convert between types that you define. For example, consider the following code in the ComplexNumber
class:
// Convert double to ComplexNumber.
public static implicit operator ComplexNumber(double x)
{
return new ComplexNumber(x, 0);
}
// Convert ComplexNumber to double.
public static explicit operator double (ComplexNumber me)
{
return me.Magnitude;
}
The first method defines a conversion operator that converts a double
into a ComplexNumber
. You can easily convert any double
into a ComplexNumber
by simply setting its imaginary part to 0. This conversion never causes a loss of data so it can be made implicitly.
The second method defines a conversion operator that converts a ComplexNumber
into a double
by returning the number's magnitude. This does cause a loss of data (unless the number's imaginary part happens to be 0) so the conversion is declared explicit
. It allows your code to convert from a ComplexNumber
to a double
, but you need to explicitly use a cast to make it happen.
In this Try It, you extend the ComplexNumber
class you built in Exercise 25-6. That version of the class included methods such as AddTo
and SubtractFrom
to perform simple operations. Now you'll replace those cumbersome methods with overloaded +
, -
, *
, and unary -
operators.
In this lesson, you:
ComplexNumber
class's AddTo
, MultiplyBy
, and SubtractFrom
methods.ComplexNumber + ComplexNumber
ComplexNumber + double
double + ComplexNumber
ComplexNumber * ComplexNumber
ComplexNumber * double
double * ComplexNumber
-ComplexNumber
ComplexNumber - ComplexNumber
ComplexNumber - double
double - ComplexNumber
-
operator, the following two operations have the same result:
ComplexNumber - ComplexNumber
ComplexNumber + -ComplexNumber
ComplexNumber
class's AddTo
, MultiplyBy
, and SubtractFrom
methods.
ComplexNumber + ComplexNumber
ComplexNumber + double
double + ComplexNumber
ComplexNumber * ComplexNumber
ComplexNumber * double
double * ComplexNumber
-ComplexNumber
ComplexNumber - ComplexNumber
ComplexNumber - double
double - ComplexNumber
// ComplexNumber + ComplexNumber.
public static ComplexNumber operator +(ComplexNumber me, ComplexNumber other)
{
return new ComplexNumber(
me.Real + other.Real,
me.Imaginary + other.Imaginary);
}
// ComplexNumber + double.
public static ComplexNumber operator +(ComplexNumber me, double x)
{
return new ComplexNumber(me.Real + x, me.Imaginary);
}
// double + ComplexNumber.
public static ComplexNumber operator +(double x, ComplexNumber other)
{
return other + x;
}
// ComplexNumber * ComplexNumber.
public static ComplexNumber operator *(ComplexNumber me, ComplexNumber other)
{
return new ComplexNumber(
me.Real * other.Real - me.Imaginary * other.Imaginary,
me.Real * other.Imaginary + me.Imaginary * other.Real);
}
// ComplexNumber * double.
public static ComplexNumber operator *(ComplexNumber me, double x)
{
return new ComplexNumber(me.Real * x, me.Imaginary * x);
}
// double * ComplexNumber.
public static ComplexNumber operator *(double x, ComplexNumber other)
{
return other * x;
}
// Unary -.
public static ComplexNumber operator -(ComplexNumber me)
{
return new ComplexNumber(-me.Real, -me.Imaginary);
}
// ComplexNumber - ComplexNumber.
public static ComplexNumber operator -(ComplexNumber me, ComplexNumber other)
{
return me + -other;
}
// ComplexNumber - double.
public static ComplexNumber operator -(ComplexNumber me, double x)
{
return new ComplexNumber(me.Real - x, me.Imaginary);
}
// double - ComplexNumber.
public static ComplexNumber operator -(double x, ComplexNumber other)
{
return -other + x;
}
// Perform the calculations between two ComplexNumbers.
private void calculateButton_Click(object sender, EventArgs e)
{
ComplexNumber a = new ComplexNumber(
double.Parse(real1TextBox.Text),
double.Parse(imaginary1TextBox.Text));
ComplexNumber b = new ComplexNumber(
double.Parse(real2TextBox.Text),
double.Parse(imaginary2TextBox.Text));
ComplexNumber aPlusB = a + b;
aPlusBTextBox.Text = aPlusB.ToString();
ComplexNumber aMinusB = a - b;
aMinusBTextBox.Text = aMinusB.ToString();
ComplexNumber aTimesB = a * b;
aTimesBTextBox.Text = aTimesB.ToString();
}
// Perform the calculations with a real number.
private void calculateRealOnlyButton_Click(
object sender, EventArgs e)
{
double x = double.Parse(realOnlyTextBox.Text);
ComplexNumber b = new ComplexNumber(
double.Parse(real2TextBox.Text),
double.Parse(imaginary2TextBox.Text));
ComplexNumber xPlusB = x + b;
aPlusBTextBox.Text = xPlusB.ToString();
ComplexNumber xMinusB = x - b;
aMinusBTextBox.Text = xMinusB.ToString();
ComplexNumber xTimesB = x * b;
aTimesBTextBox.Text = xTimesB.ToString();
}
ComplexNumber
s and double
s requires a lot of similar code. For example, to perform addition with ComplexNumber
s, you need to overload the +
operator three times to handle ComplexNumber + ComplexNumber
, ComplexNumber + double
, and double + ComplexNumber
.
Fortunately, there's a better approach. Just provide an implicit conversion operator to convert a double
into a ComplexNumber
. Now if the program needs to perform the operation ComplexNumber + double
, it automatically converts the double
into a ComplexNumber
and can then perform the addition.
Copy the program you built in this lesson's Try It and remove the code that combines ComplexNumber
s with double
s. Then add an implicit conversion operator to convert double
s into ComplexNumber
s. Verify that the program still works.
ComplexNumber
class's /
operator to perform division using this equation:
Use this operator to define operators for ComplexNumber / double
and double / ComplexNumber
. (Hint: Don't perform all of the calculations for these. Convert the double
into a ComplexNumber
and then use the previous definition of /
.)
Change the main program to calculate A / B. Verify these calculations:
OrderItem
class that has the properties Description
, Quantity
, and PriceEach
. Also make an Order
class that has the properties CustomerName
and Items
, which is a List<OrderItem>
. Then overload the Order
class's +
operator so you can use it to add OrderItem
s to an Order
. Build a simple user interface to test the classes.
Hints:
Order
object and then add items to it.+
operator return the Order
to which it is adding an item.OrderItem
class's ToString
method so you can easily display items in a ListBox
.Equals
method tests reference equality. That means it considers two variables equal if they refer to the same instance of the class. For example, it would consider two Employee
variables different if they refer to separate instances of the class even if they have the same FirstName
and LastName
property values. Sometimes that makes sense, but other times it's inconvenient.
Make a program that defines an Employee
class with FirstName
and LastName
properties. Override the ToString
method to return the concatenated names.
Use the following code to override the class's Equals
method so it returns true
if two Employee
s have the same FirstName
and LastName
values:
// Return true if the object is an Employee with
// the same first and last names as this object.
public override bool Equals(object obj)
{
if (obj == null) return false;
if (!(obj is Employee)) return false;
Employee other = obj as Employee;
return (
(FirstName == other.FirstName) &&
(LastName == other.LastName));
}
The first line checks that the other object is not null
. (You already know that the current “this
” object isn't null
or else it couldn't be executing this code.)
The is
keyword returns true
if an object can be converted into a specific type, so the second line makes sure that obj
inherits from the Employee
type.
The method then converts obj
into an Employee
and compares its FirstName
and LastName
values to the current object's values.
If you override Equals
, you should also override GetHashCode
. This method converts an object into an int
that acts as a sort of shorthand representation for it. The hash code for two equal objects must be the same. (That's why you need to override GetHashCode
if you override Equals
.) Ideally, two different objects should also be unlikely to have the same hash value.
For this exercise, give the Employee
class the following GetHashCode
method:
// Return a hash code for the object.
public override int GetHashCode()
{
return FirstName.GetHashCode() ^ LastName.GetHashCode();
}
Now that you've defined the Employee
class, create a Department
class that has the properties Name
and Employees
, which is a List<Employee>
. Overload its +
operator to add an Employee
to the Employees
list.
Give the form a class-level Department
object and initialize it. Then build the user interface shown in Figure 26.1.
When the user clicks Add, create an Employee
object with the entered names, and use the Department
object's +
operator to add the Employee
to the Department
.
When the user clicks Remove, create an Employee
object with the entered names. Use the Contains
method of the Department
object's Employees
list to see if the Employee
is in the list. If the Employee
is present, use the list's Remove
method to remove it. (The Contains
and Remove
methods wouldn't work if you hadn't overridden the Equals
method. Comment out Equals
and GetHashCode
to see what happens.)
Employee
class you built for Exercise 26.4. Use the Equals
method to overload the ==
and !=
operators. Then use an interface similar to the one shown in Figure 26.2 to test the operators.
When the user clicks Compare, the program should create two Employee
objects, use ==
and !=
to compare them, and display the results, as shown in the figure.