- Creating Classes
- Creating Objects
- Using Access Modifiers
- Creating Fields and Using Initializers
- Creating Methods
- Creating Properties
- Read-only Properties
- Creating Constructors
- Creating Structs
- Creating Static Members
- Creating Static Fields
- Creating Static Properties
- Creating Destructors and Handling Garbage Collection
- Overloading Methods
- Overloading Operators
- Creating Namespaces
- In Brief
Overloading Operators
If you're an OOP programmer, you know that you can also overload operators, not just methods. You do that by defining static methods using the operator keyword. Being able to overload operators like +, -, * and so on for your own classes and structs lets you use those classes and structs with those operators, just as if they were types built into C#. C# doesn't allow as many operators to be overloaded as C++ does. You can see the possibilities for C# in Table 3.3. Note the division into unary operators and binary operatorsunary operators take one operand (like the negation operator, -x), and binary operators take two operands (like the addition operator, x + y).
Table 3.3 Overloading Possibilities for C# Operators
Operators |
Overloading Possibilities |
+, -, !, ~, ++, --, true, false |
These unary operators can be overloaded. |
+, -, *, /, %, &, |, ^, <<, >> |
These binary operators can be overloaded. |
==, !=, <, >, <=, >= |
The comparison operators can be overloaded. |
&&, || |
The conditional logical operators cannot be overloaded, but they are computed with & and |, which can be overloaded. |
[] |
The array indexing operator cannot be overloaded, but you can define indexers in C# (see Chapter 6, "Understanding Collections and Indexers"). |
() |
The cast operator cannot be overloaded directly, but you can define your own conversion operators, as you'll do in this chapter. |
+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= |
Assignment operators cannot be overloaded, but if you overload a binary operator, such as +, += is also overloaded. |
=, ., ?:, ->, new, is, sizeof, typeof |
These operators cannot be overloaded. |
Note also that, unlike C++, the = assignment operator cannot be overloaded in C#. An assignment always performs a simple bit-by-bit copy of a value into a variable. On the other hand, when you overload a binary operator like +, the corresponding compound assignment operator, +=, is automatically overloaded. Cast operations are overloaded by providing conversion methods, as we'll see in a page or two.
For C++ Programmers
Unlike C++, you cannot overload the =, (), [], &&, ||, and new operators in C#.
You can overload operators for either classes or structs. To see how this works, we'll overload the Complex struct we built earlier in the chapter (see Listing 3.6). This struct holds complex numbers like 1 + 2i, where i is the square root of -1, and we'll see how to overload operators like + so that we can add two Complex objects, or the unary negation operator so that if complex holds 1 + 2i, -complex will yield -1 - 2i. All this takes place in ch03_14.cs, which appears in Listing 3.14. We'll take this code apart in the next few sections.
Listing 3.14 Overloading Operators (ch03_14.cs)
class ch03_14 { public static void Main() { Complex complex1 = new Complex(1, 2); Complex complex2 = new Complex(3, 4); System.Console.WriteLine("complex1 = {0}", complex1); System.Console.WriteLine("complex2 = {0}", complex2); Complex complex3 = -complex1; System.Console.WriteLine("-complex1 = {0}", complex3); System.Console.WriteLine("complex1 + complex2 = {0}", complex1 + complex2); if(complex1 == complex2){ System.Console.WriteLine("complex1 equals complex2"); } else { System.Console.WriteLine("complex1 does not equal complex2"); } } } public struct Complex { public int real; public int imaginary; public Complex(int real, int imaginary) { this.real = real; this.imaginary = imaginary; } public override string ToString() { if (imaginary >= 0){ return(System.String.Format("{0} + {1}i", real, imaginary)); } else { return(System.String.Format("{0} - {1}i", real, System.Math.Abs(imaginary))); } } public static Complex operator-(Complex complex) { return new Complex(-complex.real, -complex.imaginary); } public static Complex operator+(Complex complex1, Complex complex2) { return new Complex(complex1.real + complex2.real, complex1.imaginary + complex2.imaginary); } public static implicit operator Complex(int theInt) { return new Complex(theInt, 0); } public static explicit operator int(Complex complex) { return complex.real; } public static bool operator==(Complex complex1, Complex complex2) { if (complex1.real == complex2.real && complex1.imaginary == complex2.imaginary) { return true; } return false; } public static bool operator!=(Complex complex1, Complex complex2) { return !(complex1 == complex2); } public override bool Equals(object obj) { if (!(obj is Complex)) { return false; } return this == (Complex) obj; } public override int GetHashCode() { return (int) System.Math.Sqrt(real * real + imaginary * imaginary); } }
Creating the Complex Struct
We start ch03_14.cs by using the Complex struct we saw earlier in this chapter, which has a constructor you pass the real and imaginary parts to, and we'll add the ToString method. Any time C# needs a string representation of a complex number (as when you pass it to System.Console.WriteLine), it'll call the number's ToString method:
public struct Complex { public int real; public int imaginary; public Complex(int real, int imaginary) { this.real = real; this.imaginary = imaginary; } public override string ToString() { if (imaginary >= 0){ return(System.String.Format("{0} + {1}i", real, imaginary)); } else { return(System.String.Format("{0} - {1}i", real, System.Math.Abs(imaginary))); } } . . . }
Overloading a Unary Operator
The next step is to start overloading operators for Complex numbers. We'll start by overloading the unary negation operator, -. To do that, you add this method to the Complex struct, which uses the operator keyword and is passed a Complex number to negate:
public static Complex operator-(Complex complex) { return new Complex(-complex.real, -complex.imaginary); }
Using System.String.Format
Note the use of System.String.Format in the previous code. This method works just like System.Console. WriteLine, except that it returns a string instead of displaying text to the console. That means we can use it to embed the real and imaginary parts of the complex number into a string.
All we have to do here is to negate the real and imaginary parts of the complex number and return the result, as you see in this code. Now if you created a complex number, 1 + 2i, and negated it like this:
Complex complex1 = new Complex(1, 2); System.Console.WriteLine(-complex1;
You'd see this result:
-1 - 2i
Overloading a Binary Operator
We've been able to overload the - unary operator for complex numbers by adding a static method to the Complex struct that uses the operator keyword and is passed the operand to negate. When you're overloading a binary operator, like the + addition operator, you are passed two operands; in the case of the + operator, those are the complex numbers you're supposed to add. Here's the method you'd add to the Complex struct to overload the + operator for complex numbers:
public static Complex operator+(Complex complex1, Complex complex2) { return new Complex(complex1.real + complex2.real, complex1.imaginary + complex2.imaginary); }
Now if you were to use this code to add 1 + 2i and 3 + 4i:
Complex complex1 = new Complex(1, 2); Complex complex2 = new Complex(3, 4); System.Console.WriteLine("complex1 + complex2 = {0}", complex1 + complex2);
you would see this result:
complex1 + complex2 = 4 + 6i
Overloading Conversion Operations
You can also overload conversion operations. Conversions can be either implicit or explicit, and you use the implicit or explicit keywords in those cases. The name of the operator in this case is the target type you're converting to, and the parameter you're passed is of the type you're converting from. For example, here's how to convert from an integer value to a Complex numbernote that we'll just assign the integer value to the real part of the resulting complex number. Because data will be lost, we'll make this an implicit conversion:
public static implicit operator Complex(int intValue) { return new Complex(intValue, 0); }
On the other hand, converting from a complex number to an int does imply some data loss, so we'll make this an explicit conversion:
public static explicit operator int(Complex complex) { return complex.real; }
Now when you cast from Complex to int explicitly, this method will be called.
Overloading Equality Operators
Overloading the == equality operator is like overloading any binary operator, with a few differences. For one, if you overload ==, C# will insist that you overload != as well, so we'll do both operators here.
For C++ Programmers
If you overload ==, C# will insist that you also overload !=.
When you overload the == operator, you're passed two Complex objects to compare; you return true if they're equal and false otherwise. Complex numbers are equal if their real and imaginary parts are equal, so here's how to overload the == operator for complex numbers:
public static bool operator==(Complex complex1, Complex complex2) { if (complex1.real == complex2.real && complex1.imaginary == complex2.imaginary) { return true; } return false; }
And here's how to overload !=:
public static bool operator!=(Complex complex1, Complex complex2) { return !(complex1 == complex2); }
If you only overload == and !=, C# will give you a warning when you compile your code that you haven't overridden the Object.Equals(object o) method. This method is sometimes used by code instead of the == operator to check for equality (in Visual Basic .NET, for example, you can't overload operators, so code would use the Equals method). For example, to check if complex1 equals complex2, you could call complex1.Equals(complex2). C# wants us to override this method, replacing the default version in the Object class, not overload it, and we'll discuss overriding methods in the next chapter. All that means in this case is that we use the override keyword here. After using the is operator to ensure that the object passed to us is a Complex object, we just compare the current object to the one passed, and return the result of that comparison, like this:
public override bool Equals(object obj) { if (!(obj is Complex)) { return false; } return this == (Complex) obj; }
But there's still more to do. If you've overloaded ==,!=, and Equals, C# will still give you another warning. You haven't overridden the Object.GetHashCode method. A hash method is used to quickly generate a hash code, which is an int that corresponds to the value of an object. Hash codes allow C# to store objects more efficiently in collections, as we'll discuss in Chapter 6. You don't have to override GetHashCodeyou can simply ignore the warning. In this case, we'll return the magnitude of the complex number as its hash code:
public override int GetHashCode() { return (int) System.Math.Sqrt(real * real + imaginary * imaginary); }
Now, at last, you can compare two complex numbers using the == operator, like this, which compares 1 + 2i and 3 + 4i:
Complex complex1 = new Complex(1, 2); Complex complex2 = new Complex(3, 4); if(complex1 == complex2){ System.Console.WriteLine("complex1 equals complex2"); } else { System.Console.WriteLine("complex1 does not equal complex2"); }
Here's what this code produces:
complex1 does not equal complex2
For the full story on operator overloading, run ch03_14.cs; this example implements all the operator overloads we've discussed and puts them to work using the code we've developed. Here's what you see when you run this example:
C:\>ch03_14 complex1 = 1 + 2i complex2 = 3 + 4i -complex1 = -1 - 2i complex1 + complex2 = 4 + 6i complex1 does not equal complex2