Operator Overloading in C#

by Dave Redding

Going back as far as the punched card, computer scientists have concerned themselves with the mathematical nature of their work. Programming languages have long provided addition, subtraction, multiplication, division and equality checks for the primitive data types like INT, FLOAT and so forth. With C#—as with most modern programming languages—we have the ability to make the operators work with programmer-defined types.

Let’s look at a typical example of operator usage in C#:

int a = b + c

This code is simple and concise. We are taking two operands of an assumed numeric type and assigning the value to a new variable called a. Nothing fancy going on here, but let’s look at a different way to write this same code.

Let’s assume we have this method, and it does all the fancy stuff behind the scenes to add two numbers together.

public int Combine(int a, int b);

Using the same methodology as above, we would assign a by saying:

int a = Combine(b,c);

Programmatically, this is identical to using our operator as shown earlier. The only thing we’ve changed is the syntax. That is all that operators truly give us—concise syntax.

So how does this fit into our daily C# lives? Well, we’ve always had a handful of operators that have had dedicated inputs and outputs. These were practically hard-wired into our compilers. Now we have the option of using those same operators, but with our own objects as operands. Let’s see an example of where this would be useful.

Let’s say we work for a science company on an application development team that measures temperatures in Fahrenheit, Celsius and Kelvin. For one reason or another, our application stores each reading in its own object. That is to say Fahrenheit, Celsius and Kelvin are all objects in their own right.

So what do we do when we want to display our output in Celsius, given that our inputs are a combination of all three measurements? We need to convert, which typically gives this type of glue code:

Fahrenheit f = ConvertToFahrenheit(Celsius1).Value + ConvertToFahrenheit(Kelvin1);

This is easy to read—and convenient enough—but wouldn’t it be nice to make the same code more expressive with less?

Fahrenheit f = Celsius1 + Kelvin1

Here we are overloading two different types of operators: the implicit casting operator and the + operator.

Overloading Simple Binary Operators

The + operator is a type of binary operator—binary in that it has two operands. Let’s look at an implementation:

public static Fahrenheit operator +(Kelvin k, Celsius c)
{
  //Verify all values are normalized, and then add them
  return ((Fahrenheit)k).Value + ((Fahrenheit)c).Value;
}

From this perspective, we haven’t really done anything different—other than move our functionality into a method called +. It’s short, sweet and concise. The only thing to keep in mind when overloading these types of binary operators is that the class that contains them must either be a return value or a parameter. Personally, I like to put these methods in the class of the type they are returning.

Overloading the Implicit/Explicit Casting Operators (Unary Operators)

Taking our previous example a bit further, let’s convert from one temperature type to another:

public static implicit operator Fahrenheit(Celsius b)
{return new Fahrenheit(1.8 * b.GetValue()+32);}

Again, we are not really doing anything special here. We are simply converting a Celsius value into a Fahrenheit one. The benefit is that we only have to write this once in code, and since it’s an implicit conversion, we don’t even need to expressly type anything to use the method:

Fahrenheit tf = Celsius1
Is identical to
Fahrenheit tf = (Fahrenheit)Celsius1

The same rules apply here as they do with other operators; the method must be declared in the class that is the return type or the parameter type.

Typically it’s a good idea to write out the casting, just to make the code clear. If you want to require the implementers of your class to explicitly cast from type a to type b, then you can replace implicit in the method signature with the keyword explicit.

Casting overloading has many great uses. For instance, when populating a business object from an object in a data access layer, I typically write a casting overload within the business object that takes the data object as a parameter. That way, I keep all the code for populating the business object in the business object itself, rather than having some conversion methods hanging out in limbo. Give it a try sometime.

Other Operators

There are many operators in C#, some of which require a little special consideration. Specifically I’m talking about the equality comparisons:

>   <   =   != 

These operators have a dedicated return value of bool and are declared:

public static bool operator >(Fahrenheit f1 , Fahrenheit f2)

The special thing to keep in mind with these is that they are implemented in pairs. For instance, you can’t implement greater than (>) without implementing less than (<). The nice thing here is that if you do implement one without the other, you’ll get a compile time error.

The operator Fahrenheit.operator > (Fahrenheit) requires a matching operator (<) to also be defined.

In general, operators are great at simplifying those sometimes cumbersome conversion routines and predicates. They also make maintenance a breeze, since you can keep important code in one place and use it everywhere you utilize your class.

One thing to watch out for is overusing your operator overloads. For example, just because you can add two Person objects together to form a PersonGroup doesn’t mean it will be easily understood by other developers down the road. The goal of a software developer should be to write concise and manageable code. If you think an operator overload is the right solution, then give it a try—just remember it’s not the only solution.

Happy overloading!

[email protected]
877.663.0877
© Copyright 1995-2019 - STOUT SYSTEMS DEVELOPMENT INC. - All Rights Reserved
envelopephone-handsetlaptop linkedin facebook pinterest youtube rss twitter instagram facebook-blank rss-blank linkedin-blank pinterest youtube twitter instagram