Skip to main content

C# - Operator Overloading Part 1 [Intermediate]


Operator overloading in C# is pretty easy and extensible, and there is plenty of documentation out there on the internet for most kinds of operator overloading. In fact the MSDN docs themselves do a good job of explaining how to do basic operator overloading. But what is not covered anywhere are the fringe cases of operator overloading - how overloading interacts with inheritance, and other things even weirder than that.

Now, you might be asking why anyone would ever want to do anything crazy like that - and the answer is probably never. Operator overloading by itself in code is often questionable - there are many cases where it actually decreases the readability of the code. So crazy operator overloading, where what actually happens behind the scenes is even more confusing than normal, is probably something to avoid at all costs. But hey, we aren't here to debate techniques - we're here to do some crazy overloading!

Basic Operator Overloading
Just in case you need a quick overview of basic operator overloading in C#, here you go. To overload a unary operator for a class, you write a method in that class with the following signature:
public static SomeClass operator ++(SomeClass arg)

For instance, this signature is for overloading the ++ operator for SomeClass. The declaration is actually pretty simple - most of the stuff here is normal for a method declaration. The only odd things are the 'operator' keyword (which lets .NET know that this a operator overload function), and the name of the function, instead of being an actual name, is the operator being overloaded. The public and static keywords make sense (in fact it won't compile without them), because it wouldn't make much sense to have a private or non-static operator overload. There are some constraints on the arguments and the return types (and the constraints differ depending on the operator being overloaded), but for the most part you can treat them like standard method arguments and return types. The contents can be any sort of calculation - as long as what is returned matches the return type.

For overloading binary operators, the only difference is that the operator function takes two arguments. Here, we are overloading the addition operator:
class SomeClass
{
    private int someValue;

    public SomeClass(int val)
    { someValue = val; }

    public static SomeClass operator +(SomeClass arg1, SomeClass arg2)
    {
         return new SomeClass(arg1.someValue + arg2.someValue);
    }
}
 
And of course now you can now use this overload like the following:
SomeClass var1 = new SomeClass(1);
SomeClass var2 = new SomeClass(2);
SomeClass var3 = var1 + var2;
 
And now var3.someValue holds the value 3.

Now that you know the syntax for overloading operators, you might be wondering what operators you are allowed to overload. Well, with C#, the answer is almost every single one. For binary operators, you can overload any of the following:
+, -, !, \~, ++, --, true, false

Most of those probably make sense, but what does it mean to overload true or false? Well, first, take a look at the following implementation:
class SomeClass
{
    private int someValue;

    public SomeClass(int val)
    { someValue = val; }

    public static bool operator true(SomeClass arg1)
    { return arg1.someValue >= 0 }

    public static bool operator false(SomeClass arg1)
    { return arg1.someValue < 0 }
}
 
You can't overload only true or false - you have to overload them both if you want one (which makes sense - how could you even mentally define one without defining the other).

And this is how it can be used:
SomeClass var1 = new SomeClass(1);
if(var1)
{
  //do something
}
 
var1 would be treated as true, because the true operator returned true. I don't actually think overloading the operators true and false is very useful - I think a much better way to get the same functionality is to create an operator overload to implicitly cast your object to a boolean (we will get into how to do that later on).

Back to what you can actually overload. For binary operators, you can overload the following:
+, -, *, /, %, &, |, \^, \<\<, >>

All of those should make sense, so I'll move on to comparison operators:
==, !=, \<, >, \<=, >=

The only caveat here is that these operators need to be overloaded in pairs - i.e., if you overload ==, you must overload !=. This makes sense, for the same reason that you must overload true and false as a pair.

You can't directly overload && or || - but not to worry, you can get an overloaded 'effect' by overloading either the true and false operators, or by defining an implicit cast to boolean.

The same goes for all the 'assignment plus operator' operators:
+=, -=, *=, /=, %=, &=, |=, \^=, \<\<=, >>=

You can't overload them directly, but if you think about it, every one of them is a combination of = and some operator that you can overload - and so by overloading, say, +, you end up overloading +=.
You can't overload any of these at all:
=, ., ?:, ->, new, is, sizeof, typeof

But please, I would hope you didn't actually want to do that in the first place.

One cool operator that doesn't fit in any of these categories is the index operator: []

You can actually 'overload' it (Microsoft doesn't call it overloading - they call it 'declaring an indexer'), and it comes in handy sometimes. It doesn't use operator overload syntax - instead, it uses the C# property syntax. For instance:
class SomeClass
{
    private int[] someValues = new int[3]{1,2,3};

    public int this [int index]
    {
      set{ someValues[index] = value; }
      get{ return someValue[index]; }
    }
}
 
And now you can use it like this:
SomeClass var1 = new SomeClass();
var1[0] = var1[2];
 
And after that operation, the array in var1 looks like: {3,2,1}.

There is one other set of overloading operations to talk about before we start pushing operator overloading to its limits - and that is cast overloading. There are two types of cast overloads: implicit and explicit. Explicit casting is when you write code like:
double val = 4.555;
int intVal = (int)val;
 
That (int) is an explicit cast.
Implicit casting is what occurs in the following situation:
int intVal = 1;
double val = intVal;
 
Here, the int intVal is implicitly casted to a double - you as the programmer don't even have to think about it.

So lets take a look at some cast overloads:
class SomeClass
{
    private int someValue;

    public SomeClass(int val)
    { someValue = val; }

    public static implicit operator bool(SomeClass arg1)
    {
         return arg1.someValue >=0;
    }

    public static explicit operator int(SomeClass arg1)
    {
       return arg1.someValue;
    } 
}
 
Here, we have an implicit overload for casting to bool, and an explicit overload for casting to int. The syntax is close to other operator overloads, but not quite the same. We don't have to declare a return type in the normal spot, because the name of the cast is the return type.

Now that we have these casts defined, they can be used like this:
SomeClass var1 = new SomeClass(1);
int val = -1;
if(var1)
  val = (int)var1;
 
Here, var1 will evaluate to true in the if, thanks to the implicit boolean cast. And then, val will get the value 1, because of the explicit int cast.

But enough with the simple stuff - what happens if we do the unexpected with operator overloading?
Some Strange Arguments 
 
So operator overloads are just methods, right? So that means we should be able to do all sorts of crazy things with them. Sadly (or fortunately, depending on your perspective) that is not the case. For instance, you can't use the keywords ref or out with the arguments. For unary operators (like ++ and --) both the argument and the return type have to be the class that the overload is declared for. So that means that the following won't compile:
class SomeClass
{
    public static SomeClass operator ++(int arg1)
    { return new SomeClass(); }
}
//Error: The parameter type for ++ or -- operator must be the containing type
 
or this:
class SomeClass
{
    public static int operator ++(SomeClass arg1)
    { return new 0; }
}
//Error: The parameter type for ++ or -- operator must be the containing type
 
But it makes sense that that wouldn't compile, because the 'input' and the 'output' of ++ when your actually using it in code need to be the same type.

Don't lose hope, though - because you can do odd things with the arguments and return types of binary operators. For instance, this is legal:
class SomeClass
{
    public static int operator +(SomeClass arg1, int arg2)
    { return new 0; }
}
 
And will work in the following statement:
SomeClass var1 = new SomeClass();
int i = var1 + 2;
 
However it will not work with:
int i = 2 + var1;
 
In this second case it will actually try and add var1 as an integer to '2' - i.e., use the regular integer '+'. This is because the type of the first argument to the overload function needs to match the type of the value before the '+', and the second argument type has to match what is after the '+'. To get it to work in both directions you need to write two functions:
class SomeClass
{
    public static int operator +(SomeClass arg1, int arg2)
    { return new 0; }
    public static int operator +(int arg1, SomeClass arg2)
    { return new 0; }
}
 
Be aware that declaring overloads like this has the potential to mess with your use of the 'assignment plus operator' operators, like +=. In this case, because of how we declared these overloads, the following code will cause a compiler error:
SomeClass var1 = new SomeClass();
var1 += 5;
//Error: Cannot implicitly convert type 'int' to 'SomeClass'
 
It throws this error because the overloaded operator is returning an int, and we are trying to place it into a SomeClass variable.

One thing to note - the type of at least one of the arguments to the overload function must be the type of the class that you are declaring it in. So something like the following will not compile:
class SomeClass
{
    public static SomeClass operator +(int arg1, int arg2)
    { return null; }
}
//Error: One of the parameters of a binary operator must be the containing type
 
Another thing to be aware of - you don't want to declare the 'same' overload in two different classes. By the 'same', I mean two overloads that have identical signatures. For instance, the following won't compile:
class OneClass
{
    public static OneClass operator +(OneClass arg1, 
        TwoClass arg2)
    { return null; }
}
class TwoClass
{
    public static OneClass operator +(OneClass arg1, 
        TwoClass arg2)
    { return null; }
}
//Error: The call is ambiguous between the following methods or properties: 
'OneClass.operator +(OneClass, TwoClass)' and 'TwoClass.operator +(OneClass, TwoClass)'

It won't compile because the compiler can't figure out which of the overload methods to use.

That covers it for part one of crazy operator overloading. Check back soon for part two, where we will deal with polymorphism and inheritance, as well a number of other weird twists which no one in their right mind would use in their code.

Comments

Popular posts from this blog

C# Snippet - Shuffling a Dictionary [Beginner]

Randomizing something can be a daunting task, especially with all the algorithms out there. However, sometimes you just need to shuffle things up, in a simple, yet effective manner. Today we are going to take a quick look at an easy and simple way to randomize a dictionary, which is most likely something that you may be using in a complex application. The tricky thing about ordering dictionaries is that...well they are not ordered to begin with. Typically they are a chaotic collection of key/value pairs. There is no first element or last element, just elements. This is why it is a little tricky to randomize them. Before we get started, we need to build a quick dictionary. For this tutorial, we will be doing an extremely simple string/int dictionary, but rest assured the steps we take can be used for any kind of dictionary you can come up with, no matter what object types you use. Dictionary < String , int > origin = new Dictionary < string , int >();

C# Snippet - The Many Uses Of The Using Keyword [Beginner]

What is the first thing that pops into your mind when you think of the using keyword for C#? Probably those lines that always appear at the top of C# code files - the lines that import types from other namespaces into your code. But while that is the most common use of the using keyword, it is not the only one. Today we are going to take a look at the different uses of the using keyword and what they are useful for. The Using Directive There are two main categories of use for the using keyword - as a "Using Directive" and as a "Using Statement". The lines at the top of a C# file are directives, but that is not the only place they can go. They can also go inside of a namespace block, but they have to be before any other elements declared in the namespace (i.e., you can't add a using statement after a class declaration). Namespace Importing This is by far the most common use of the keyword - it is rare that you see a C# file that does not h

C# WPF Printing Part 2 - Pagination [Intermediate]

About two weeks ago, we had a tutorial here at SOTC on the basics of printing in WPF . It covered the standard stuff, like popping the print dialog, and what you needed to do to print visuals (both created in XAML and on the fly). But really, that's barely scratching the surface - any decent printing system in pretty much any application needs to be able to do a lot more than that. So today, we are going to take one more baby step forward into the world of printing - we are going to take a look at pagination. The main class that we will need to do pagination is the DocumentPaginator . I mentioned this class very briefly in the previous tutorial, but only in the context of the printing methods on PrintDialog , PrintVisual (which we focused on last time) and PrintDocument (which we will be focusing on today). This PrintDocument function takes a DocumentPaginator to print - and this is why we need to create one. Unfortunately, making a DocumentPaginator is not as easy as