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. In math, there's no way to "add" strings together, but yet, in C# we can do it.

In C#, when you create classes, it is possible to define how operators should work with them. This means that if you create an object called Cheese, you can add two Cheese objects together, using the '+' operator, and you get to decide what this means! (Though if you do that, Pepper Jack and Colby better result in Colby-Jack!) This is called "operator overloading", and it is actually a really nice feature of C#. (By the way, C++ allows you to do operator overloading, but Java does not. At least, not yet.)

Some operators can be overloaded, while others cannot. For instance, the '=' operator (assignment operator) cannot be overloaded. You can't define what assignment should do. (And there's a good reason for that. You could really mess stuff up if you were allowed to do that.)

But many other operators can be overloaded. So what operators can be overloaded? Here's a list: +, -, *, /, %, ++, --, ==, !=, >=, <=, >, and <. There's a few others, but we've never talked about them (and they aren't very common at all, so for now, we're skipping them). Additionally, the operators +=, -=, /=, *=, and %= are also overloadable, but when we overload the '+' operator, the '+=' operator is automatically overloaded for us.

Oh, and it is also worth pointing out that the relational operators must be overloaded in pairs—if you overload the == operator, you must also overload the != operator, and if you overload the > operator, you must also overload the < operator as well, and so on.

And one other thing that we should look at is a couple of these operators have a "binary" version, and a "unary" version. For instance the '+' operator. A "binary" version means that it operates on two things. 3 + 4, for example. The '+' applies to two separate numbers, 3 and 4. A "unary" version means that it operates on only one thing. -2, for example. The '-' in this case operates only the 2.

So what can't you overload? The logical operators '&&' and '||', the assignment operator '=', the dot operator '.', the new operator, and a few others that we haven't really discussed.

The '[' and ']' operator (used for indexing) can be overloaded in its own way, using indexers, which we'll look at 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 Point 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:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace OperatorOverloading
{
    public class Vector
    {
        public double X { get; set; }
        public double Y { get; set; }
 
        public Vector(double x, double y)
        {
            X = x;
            Y = y;
        }
    }
}

If you remember from a math or physics class, you can add together two vectors. When you add two vectors together, the result is another vector, but with the x and y components added together.

To overload the '+' operator, we simply add in the following code as a member of the Vector class:

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, which should make sense, since we want to have access to the operator throughout the program, and since it belongs to the class as a whole, rather than any specific instance of the class. We then specify a return type, Vector in this case. We then use the operator keyword, along with the operator that we're overloading ('+') in this case. We then have two parameters, which are the two sides of the '+' operator.

If we had a unary operator, like '-' (as in the negation operator, which makes things negative) we'd only have one parameter:

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

Notice, too, that we can have multiple versions of the same operator:

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

Now you can add a vector and a "scalar" (just a plain old number). Though from a math standpoint, this doesn't make any sense.

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.

By the way, if you look closely at these operator overloads, you can tell that they are basically just a method. (The only real difference is the operator keyword.) C# is basically just taking any place in code where you use the operator and turning it into a method call. All of this is simply to make things look nice. It is what they call "syntactic sugar".

So our completed class might look something like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace OperatorOverloading
{
    public class Vector
    {
        public double X { get; set; }
        public double Y { get; set; }
 
        public Vector(double x, double y)
        {
            X = x;
            Y = y;
        }
 
        public static Vector operator +(Vector v1, Vector v2)
        {
            return new Vector(v1.X + v2.X, v1.Y + v2.Y);
        }
 
        public static Vector operator +(Vector v, double scalar)
        {
            return new Vector(v.X + scalar, v.Y + scalar);
        }
 
        public static Vector operator -(Vector v)
        {
            return new Vector(-v.X, -v.Y);
        }
 
        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);
        }
    }
}

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.