Note: This article assumes that you have read all previous “Beginner” articles, and you know how to create an instance of a class.
Introduction
Operator Overloading is a feature of C# that acts as syntactic sugar in the code, and eases the use of libraries in your programs. Because this feature is not present in Java, operator overloading makes C# appealing to many programmers including myself. With this feature, you can create your own user-defined implementations of operators (such as > and <) for your own user-defined classes and structs. This feature is particularly useful for creation of libraries, as it will not only help you, but it will help others that use your library in the future.
In this article, I will teach you the use cases of Operator Overloading, when not to use it, and how to best implement it in C#.
Which operators can be overloaded?
MSDN provides a complete list of operators that can be overloaded. In short, unary operators (as in the + of i++), binary operators (as in the + of obj1 + obj2), comparison operators (as in > and <), and conversion operators can be overloaded.
Use Cases
Suppose you have a class called Vector3f for storing three floats (x, y and z). These three floats correspond to mathematical vectors used for operations on three-dimensional vectors (this is commonly found in graphics libraries). We would traditionally need separate methods to operate on them: a method to add vector values, one to subtract them, one to multiply them, etc. Such an implementation would look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
class Vector3f { public float x, y, z; public void Add(Vector3f vec) { x += vec.x; y += vec.y; z += vec.z; } public void Sub(Vector3f vec) { x -= vec.x; y -= vec.y; z -= vec.z; } public void Mul(Vector3f vec) { x *= vec.x; y *= vec.y; z *= vec.z; } public void Div(Vector3f vec) { x /= vec.x; y /= vec.y; z /= vec.z; } } |
We would then use this class in our program, and operate on the objects, as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class Operator { public static void Main() { // Define the vectors Vector3f vec1 = new Vector3f(2,2,2); Vector3f vec2 = new Vector3f(2,2,2); // Add 'vec2' to 'vec1' vec1.Add(vec2); // These should print '4' to the screen Console.WriteLine (vec1.x); Console.WriteLine (vec1.y); Console.WriteLine (vec1.z); // Now subtract vec2 from vec1 vec1.Sub(vec2); // Now multiply vec2 and vec1 vec1.Mul(vec2); // Now divide vec2 from vec1 vec1.Div(vec2); // this will divide vec2 from vec1 } } |
However, this code seems awfully redundant and hard to maintain. Wouldn’t it be neat if you could simply perform operations on the objects directly, such as vec1 + vec2?
Defining Overloads – In Practice
Operator-overloading reduces the need to explicitly call methods for such trivial tasks. If you were using Java, you would have stick with implementing methods for each operation of the vectors, since Java does not support operator-overloading. In C#, fortunately, we can use operator-overloading to implement syntactic sugar for trivial tasks like vec1 + vec2 as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
class Vector3f { public float x, y, z; public Vector3f(float x, float y, float z) { this.x = x; this.y = y; this.z = z; } public static Vector3f operator+ (Vector3f vec1, Vector3f vec2) { return new Vector3f (vec1.x + vec2.x, vec1.y + vec2.y, vec1.z + vec2.z); } public static Vector3f operator- (Vector3f vec1, Vector3f vec2) { return new Vector3f (vec1.x - vec2.x, vec1.y - vec2.y, vec1.z - vec2.z); } public static Vector3f operator* (Vector3f vec1, Vector3f vec2) { return new Vector3f (vec1.x * vec2.x, vec1.y * vec2.y, vec1.z * vec2.z); } public static Vector3f operator/ (Vector3f vec1, Vector3f vec2) { return new Vector3f (vec1.x / vec2.x, vec1.y / vec2.y, vec1.z / vec2.z); } } |
From here on, we simply use the operators in our program as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
using System; class Operator { public static void Main() { var vec1 = new Vector3f (5.5, 5.5, 5.5); var vec2 = new Vector3f (9.8, 9.8, 9.8); var vec3 = vec1 + vec2; Console.WriteLine (vec3.x); // 15.3 Console.WriteLine (vec3.y); // 15.3 Console.WriteLine (vec3.z); // 15.3 // Similarly, we can use "+=" // Which uses our implementation of "+" vec3 += vec1; } } |
Any operator that can be overloaded gets overloaded in the following way:
1 2 3 4 |
public static <return type> operator <operator to be overloaded>(<parameters>) { // implementation } |
So, if you want to implement equality (==) and inequality (!=), you can do so as follows:
1 2 3 4 5 6 7 8 9 |
public static bool operator == (Vector3f vec1, Vector3f vec2) { return vec1.x == vec2.x && vec1.y == vec2.y && vec1.z == vec2.z; } public static bool operator != (Vector3f vec1, Vector3f vec2) { return vec1.x != vec2.x && vec1.y != vec2.y && vec1.z != vec2.z; } |
Similarly, if you wanted to overload the increment (++) and decrement (–) operators, you would do so as follows:
1 2 3 4 5 6 7 8 9 |
public static Vector3f operator ++ (Vector3f vec) { return new Vector3f(vec.x + 1, vec.y + 1, vec.z + 1); } public static Vector3f operator -- (Vector3f vec) { return new Vector3f(vec.x - 1, vec.y - 1, vec.z - 1); } |
Now, these overloaded operators can be used on the objects as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
using System; class Operator { public static void Main() { var vec1 = new Vector3f(2, 2, 2); var vec2 = new Vector3f(4, 4, 4); var vec3 = vec1; if (vec3 == vec1) { Console.WriteLine("Vectors are equal"); } if (vec1 != vec2) { Console.WriteLine("Vectors are not equal"); } vec1++; vec2--; } } |
When NOT to use Operator Overloading
Because it eases the use of libraries, some people (just for the sake of shrinking the code base) use operator-overloading in places where there’s a chance of an ambiguity. You should only overload operators on logical types where its meaning is obvious. Consider this class for holding an array:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class ArrayHolder { public int[] array; public ArrayHolder(int[] a) { array = a; } public static ArrayHolder operator +(ArrayHolder a, int b) { var temp = a.array; foreach (var i in temp) { temp [i] = b; } return new ArrayHolder(temp); } } |
In this case, we overloaded the + operator, so that we can add any integer to all of the array’s members. The problem is that a programmer wouldn’t know which number (in the array) is being added to. Since there is no addition through indexing, it is pretty ambiguous whether the variable being added is simply an integer or an array of integers.
A simple way to make your code less ambiguous is to provide the equivalent methods for overloaded-operators. For example, if a user of your library can’t understand operators that you have overloaded, then she or he could just use the methods (and achieve the desired results).
You should also overload in a symmetric manner. For instance, if you are overloading + and -, then it also makes sense to overload * and /. Similarly, if you are overloading equality (==) then you should overload inequality (!=) too.
Overall, I think that operator-overloading is a really useful feature… if used properly where it makes sense. It’s a good idea to use it on things like logical numbers, vector-like structures (quaternions, matrices), etc. In short, it should be used in places where the resulting effects would be obvious.
Will you be willing to use this feature in your library? Or would you just prefer to use methods for the sake of consistency with your Java code? What are your thoughts?