Operator Overloading

Operator Overloading

The Crash Course

  • Operator overloading allows us to define how the traditional operators, like +,-,*, and / work for classes and objects that we create.
  • Operator overloading works for these operators: +, -, *, /, %, ++, --, ==, !=, >=, <=, >, and <, and a few others that we haven't talked about before.
  • Operator overloading does not work for these operators: && and ||, the assignment operator =, the dot operator., the new operator, and a few others that we haven't really discussed.
  • An example of overloading the + operator looks like this: public static Vector operator +(Vector v1, Vector v2) { return new Vector(v1.X + v2.X, v1.Y + v2.Y); }
  • All operators must be marked public and static.
  • When you overload the relational operators, they must be done in pairs. So if you overload ==, you must also overload != and so on.
  • You can also overload the [ and ] indexing operator, but that is done in a slightly different way and is covered in the next tutorial.

Introduction

We've used a lot of operators (+, -, /, *, ==, +=, …) in our days of programming. They all have built-in functionality that does certain specific things. Mostly, these are defined by math, going back thousands of years. In a few cases, we've seen some not-so-normal uses for these operators, like using the + operator for appending ("sticking together") two strings. Math doesn't define how to add strings, but we can do it in C# because somebody defined how it should work.

In C#, you can do a similar thing for the classes you create. You can define how many of the C# operators work with your type. You could make your Cheese class have an addition operator, allowing you to mix your pepper jack instance with your Colby instance to get a Colby jack instance! Okay, the cheese example was a little wild. But you'll make types where defining how a few operators work will make programming with them much simpler.

Not all operators can be overloaded, but many can. There are good reasons why they aren't supported for those you can't.

The following operators can be overloaded: +, -, *, /, %, ++, , ==, !=, <, >, <=, >=. There are a few others, but we haven't talked about them, and they aren't that common, so we'll skip them for now.

The operators like += and *= are not directly overloadable, but when you overload +, += will do what you'd expect with it.

And the relational operators must be overloaded in pairs, so if you overload ==, you must also overload !=, etc.

You can't overload the indexing operator ([ and ]) in the way described here, but they can be overloaded using an indexer, which we'll cover in the next tutorial.

Overloading Operators

You can overload operators for any class you want. But for the sake of simplicity, I'm going to pick a class that should make some sense for overloading operators. I'm going to do operator overloading for a Vector class, which stores an x and y coordinate. If you're familiar at all with vectors in math and physics, then this should make sense to you. So let's start with the basic class:

public class Vector
{
    public double X { get; set; }
    public double Y { get; set; }
 
    public Vector(double x, double y)
    {
        X = x;
        Y = y;
    }
}

In math, adding two vectors together is done by adding the components of the two vectors in a pair-wise manner. That is, adding the vector <1, 2> and <5, -1> will be <1 + 5, 2 + -1>, which becomes <6, 1>.

If we want to add the + operator to our Vector class, we do the following:

public static Vector operator +(Vector v1, Vector v2)
{
    return new Vector(v1.X + v2.X, v1.Y + v2.Y);
}

All operator overloads must be public and static. This one takes two Vector instances and returns a final Vector as a result. Operator overloads often work only with the type they belong to, but you could make an overload that adds two instances of one class and results in an instance of a different class, or you could have one of your parameter types be another type. The operator keyword is what tells the compiler we're making a new operator for our class instead of just a plain old method.

If we were overloading the unary + or - (like a plain negative or positive sign) then our operator would only have one parameter:

public static Vector operator -(Vector v)
{
    return new Vector(-v.X, -v.Y);
}

The relational operators can be overloaded in the exact same way, only they must return a bool:

public static bool operator ==(Vector v1, Vector v2)
{
    return ((v1.X == v2.X) && (v1.Y == v2.Y));
}
 
public static bool operator !=(Vector v1, Vector v2)
{
    return !(v1 == v2);
}

Remember that I said that the relational operators must be overloaded in pairs, so if you have a == operator overload, you must also have a != operator overload, as shown here, though you can simply call the other operator if you want.

Now that we've defined our operator overloads, we can use them just like we use operators normally:

Vector a = new Vector(5, 2);
Vector b = new Vector(-3, 4);
Vector result = a + b; // We use the operator overload here.
// At this point, result is <2, 6>.

What's Next?

Operator overloading is a powerful but simple trick that can make your code a whole lot more readable. While we're on this topic, we'll next cover indexers, which is a very similar task for the [ and ] operators.