Overloading Operators - Classes - C# 24-Hour Trainer (2015)

C# 24-Hour Trainer (2015)

Section IV

Classes

Lesson 26

Overloading Operators

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.

WARNING

Before you jump into operator overloading, be warned that just because you can overload an operator doesn't mean you should. You should only overload operators in intuitive ways.

For example, it makes sense to overload the + operator so you can add two ComplexNumber objects. It might also make sense to overload + so you can add an item to a purchase order.

It probably doesn't make sense to define + between two Employee objects to return a list of projects that included both employees. You could do that, but you probably shouldn't because it would be confusing.

Overloadable Operators

In C#, you can overload the unary, binary, and comparison operators listed in Table 26.1.

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.

Unary 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

Binary Operators

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 ComplexNumbers:

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 ComplexNumbers so this code must be in the ComplexNumber class.

Although this example subtracts two ComplexNumbers, 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

NOTE

The shift operatorsandare a little different from the other binary operators because the second parameter must always be an integer.

Comparison Operators

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());

}

WARNING 26.3

The Object class provides Equals and GetHashCode methods that are tied closely to an object's notion of equality, because Equals should return true if two objects are equal and GetHashCode should return the same value for two objects that are considered equal. To avoid confusion, you should not overload == and != unless you also override Equals and GetHashCode. In fact, Visual Studio flags an error if you overload == or != but not these two methods.

Conversion Operators

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.

Try It

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.

Lesson Requirements

In this lesson, you:

· Copy the complex number program you built for Exercise 25-6 (or download the version that's available on the book's website). Remove the ComplexNumber class's AddTo, MultiplyBy, and SubtractFrom methods.

· Give the class new overloaded operators to handle these cases:

· ComplexNumber + ComplexNumber

· ComplexNumber + double

· double + ComplexNumber

· ComplexNumber * ComplexNumber

· ComplexNumber * double

· double * ComplexNumber

· -ComplexNumber

· ComplexNumber - ComplexNumber

· ComplexNumber - double

· double - ComplexNumber

· Revise the main form's code to use the new operators.

NOTE

You can download the code and resources for this lesson from the website at www.wrox.com/go/csharp24hourtrainer2e.

Hints

· You can use operators to define other operators. For example, if you define the unary - operator, the following two operations have the same result:

·ComplexNumber - ComplexNumber

ComplexNumber + -ComplexNumber

Step-by-Step

· Copy the complex number program you built for Exercise 25-6 (or download the version that's available on the book's website). Remove the ComplexNumber class's AddTo, MultiplyBy, and SubtractFrom methods.

1. This is reasonably straightforward.

· Give the class new overloaded operators to handle these cases:

· ComplexNumber + ComplexNumber

· ComplexNumber + double

· double + ComplexNumber

· ComplexNumber * ComplexNumber

· ComplexNumber * double

· double * ComplexNumber

· -ComplexNumber

· ComplexNumber - ComplexNumber

· ComplexNumber - double

· double - ComplexNumber

11.You can use code similar to the following:

12. // ComplexNumber + ComplexNumber.

13. public static ComplexNumber operator +(ComplexNumber me, ComplexNumber other)

14. {

15. return new ComplexNumber(

16. me.Real + other.Real,

17. me.Imaginary + other.Imaginary);

18. }

19. // ComplexNumber + double.

20. public static ComplexNumber operator +(ComplexNumber me, double x)

21. {

22. return new ComplexNumber(me.Real + x, me.Imaginary);

23. }

24. // double + ComplexNumber.

25. public static ComplexNumber operator +(double x, ComplexNumber other)

26. {

27. return other + x;

28. }

29. // ComplexNumber * ComplexNumber.

30. public static ComplexNumber operator *(ComplexNumber me, ComplexNumber other)

31. {

32. return new ComplexNumber(

33. me.Real * other.Real - me.Imaginary * other.Imaginary,

34. me.Real * other.Imaginary + me.Imaginary * other.Real);

35. }

36. // ComplexNumber * double.

37. public static ComplexNumber operator *(ComplexNumber me, double x)

38. {

39. return new ComplexNumber(me.Real * x, me.Imaginary * x);

40. }

41. // double * ComplexNumber.

42. public static ComplexNumber operator *(double x, ComplexNumber other)

43. {

44. return other * x;

45. }

46. // Unary -.

47. public static ComplexNumber operator -(ComplexNumber me)

48. {

49. return new ComplexNumber(-me.Real, -me.Imaginary);

50. }

51. // ComplexNumber - ComplexNumber.

52. public static ComplexNumber operator -(ComplexNumber me, ComplexNumber other)

53. {

54. return me + -other;

55. }

56. // ComplexNumber - double.

57. public static ComplexNumber operator -(ComplexNumber me, double x)

58. {

59. return new ComplexNumber(me.Real - x, me.Imaginary);

60. }

61. // double - ComplexNumber.

62. public static ComplexNumber operator -(double x, ComplexNumber other)

63. {

64. return -other + x;

}

· Revise the main form's code to use the new operators.

0. You can use code similar to the following:

1. // Perform the calculations between two ComplexNumbers.

2. private void calculateButton_Click(object sender, EventArgs e)

3. {

4. ComplexNumber a = new ComplexNumber(

5. double.Parse(real1TextBox.Text),

6. double.Parse(imaginary1TextBox.Text));

7. ComplexNumber b = new ComplexNumber(

8. double.Parse(real2TextBox.Text),

9. double.Parse(imaginary2TextBox.Text));

10. ComplexNumber aPlusB = a + b;

11. aPlusBTextBox.Text = aPlusB.ToString();

12. ComplexNumber aMinusB = a - b;

13. aMinusBTextBox.Text = aMinusB.ToString();

14. ComplexNumber aTimesB = a * b;

15. aTimesBTextBox.Text = aTimesB.ToString();

16. }

17. // Perform the calculations with a real number.

18. private void calculateRealOnlyButton_Click(

19. object sender, EventArgs e)

20. {

21. double x = double.Parse(realOnlyTextBox.Text);

22. ComplexNumber b = new ComplexNumber(

23. double.Parse(real2TextBox.Text),

24. double.Parse(imaginary2TextBox.Text));

25. ComplexNumber xPlusB = x + b;

26. aPlusBTextBox.Text = xPlusB.ToString();

27. ComplexNumber xMinusB = x - b;

28. aMinusBTextBox.Text = xMinusB.ToString();

29. ComplexNumber xTimesB = x * b;

30. aTimesBTextBox.Text = xTimesB.ToString();

}

Exercises

1. Providing methods that combine ComplexNumbers and doubles requires a lot of similar code. For example, to perform addition with ComplexNumbers, you need to overload the + operator three times to handle ComplexNumber + ComplexNumber, ComplexNumber + double, anddouble + 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 ComplexNumbers with doubles. Then add an implicit conversion operator to convert doubles into ComplexNumbers. Verify that the program still works.

2. Copy the program you built for Exercise 1 and overload the ComplexNumber class's / operator to perform division using this equation:

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:

· (10+11i) / (3+2i) = 4 + 1i

· (15+24i) / 3 = 5 + 8i

· 4 / (1+1i) = 2 − 2i

3. Build an application with an 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 OrderItems to an Order. Build a simple user interface to test the classes.

Hints:

· Give the form a class-level Order object and then add items to it.

· Make the + operator return the Order to which it is adding an item.

· Override the OrderItem class's ToString method so you can easily display items in a ListBox.

4. [Advanced] By default, a class's 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 Employees 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.

Department Employees dialog box presenting text boxes for First Name and Last Name with Add and Remove buttons. Below is a Department section with the department name and a box listing its employees.

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 andRemove methods wouldn't work if you hadn't overridden the Equals method. Comment out Equals and GetHashCode to see what happens.)

5. Make a new program and give it a copy of the 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.Employee Equality dialog box presenting a row of First Name and Last Name text boxes for Employees 1 and 2 and a Compare button. Below are the results: == is False and != is True.

Figure 26.2

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.

NOTE

Please select the videos for Lesson 26 online at www.wrox.com/go/csharp24hourtrainer2evideos.