In this article, we are going to learn about operator overloading in C#. It gives us various flexibility when we work with user-defined types.

To download the source code for this article, you can visit our GitHub repository.

Let’s start.

What Is Operator Overloading?

Operator overloading is a technique to redefine a built-in operator. C#, with the help of operator overloading, allows us to use the same built-in operators in different ways. We can build user-defined implementations of various operations where one or both of the operands are of the user-defined type.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

Rules for Operator Overloading

To overload an operator, we must use the operator keyword. We have to use both public and static modifiers in the declaration. The unary operator will have one and the binary operator will have two input parameters. At least one parameter must be of type Type or Type? in each case, where Type is the type containing the operator declaration.

Can We Overload All Operators?

We can overload most of the operators in C#. But there are some operators that we can’t overload and some that we can with certain conditions:

OperatorsComments
+, -, !, ~, ++, --, true, falseWe can overload these unary operators.
+, -, *, /, %, &, |, ^ (logical OR), <<, >>We can overload these binary operators.
==, !=, <, >, <=, >=We can overload these binary relational operators. But we must overload them in pairs. So if we overload the == operator, we also need to overload the != operator and vice versa. Other pairs are the < and > operators and the <= and >= operators.
&&, ||We can't overload these conditional logical operators directly.
+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=We can't overload these compound assignment operators explicitly. But if we overload a binary operator, the corresponding compound assignment operator will also be overloaded indirectly. For example, the += operator is evaluated using the + operator. So, if we overload the binary operator +, the += operator will be implicitly overloaded.
^ (Index from end operator), =, ., ?., ?:, ??, ??=, .., ->, =>, (), as, await, checked, unchecked, default, delegate, is, nameof, new, sizeof, stackalloc, switch, typeof, withWe can't overload these operators.

Overloading Unary Operator

The return type of the unary operators (+, -, !, ~) can be any type except the void. But for the ++, -- operators, the return type must be the Type that contains the unary operator declaration. For the true and false operators, it must be bool type. Let’s inspect the syntax of overloading a unary operator:

public static returnType operator unaryOperator(Type t)
{
    //body
}

Here, the modifier is always public and static as we mentioned in the rules. The operator is a keyword that we need for operator overloading. unaryOperator will be replaced with our desired operator that we want to overload. The Type will be user-defined, may it be class or struct.

Now, we are going to see how to overload a unary operator with an example. We will use the Student class throughout the article for this demonstration:

public class Student
{
    private readonly int _rollNo;
    private readonly int _level;
    private readonly string _name;
    private readonly int _age;
    private int _numberOfPassedCourses;

    public Student(int rollNo, int level, string name, int age, int passedCourses)
    {
        _rollNo = rollNo;
        _level = level;
        _name = name;
        _age = age;
        _numberOfPassedCourses = passedCourses;
    }

    public int GetNumberOfPassedCourses() => _numberOfPassedCourses;

    public string GetName() => _name;
}

Let’s consider that John is a student. He passed two courses. Recently he has appeared in a new course exam and has passed it successfully. So, his number of passed courses will be three. But we can’t do that by just doing john++. Here we can use operator overloading to make our life easier. We can overload the ++ operator so that it works as we wish:

public static Student operator ++(Student student)
{
    student._numberOfPassedCourses++;
    return student;
}

We overload the ++ operator here. It takes the student as a parameter and increases the _numberOfPassedCourses value by one. Now we can use the ++ operator by calling john++:

var john = new Student(1, 1, "John", 7, 2);
john++;

Console.WriteLine($"{john.GetName()} has passed {john.GetNumberOfPassedCourses()} courses.");

We can see the output as expected:

John has passed 3 courses.

Overloading Binary Operator

The binary operator will always take two parameters as input and return any value except the void. Between two parameters, one must be a type Type that contains the operator declaration.

Let’s consider John and Alice. We can overload the > operator in such a way that it will return who is older between the two. As the > operator is a relational operator, we must overload the < operator as well:

public static bool operator <(Student studentLeft, Student studentRight)
{
    if (studentLeft._age < studentRight._age)
        return true;
    else
        return false;
}

public static bool operator >(Student studentLeft, Student studentRight)
{
    if (studentLeft._age > studentRight._age)
        return true;
    else
        return false;
}

Now we can call the > and < operators directly over John and Alice:

var john = new Student(1, 1, "John", 7, 2);
var alice = new Student(1, 5, "Alice", 12, 5);

Console.WriteLine(john > alice ? $"{john.GetName()} is older than {alice.GetName()}." : $"{john.GetName()} is younger than {alice.GetName()}.");

And, we can see the output:
John is younger than Alice.

Overloading Equality Operator

When overloading the == operator, we need to overload the != operator as well. Because both are relational operators, we need to overload them in pairs.

We also need to override the Equals method here. The equality operator is intended to be a syntactically convenient way to access the functionality of the Equals method. So, the logic of the equality operator must be identical to that of the Equals method.

For overriding the Equals method, it is important to override the GetHashCode() method. Because if two objects are the same, they must have the same hashcode. So, if we override the Equals method to make a particular comparison of two objects, and the method considers the two objects to be the same, then the hash code of the two objects must be the same as well.

Now, the Equals method provides a reference-based comparison. We will override it so that it can give a value-based comparison. If the _rollNo, _level, and _age of two students are identical, we can assume that they indicate the same student. So, let’s override the Equals and the GetHashCode() methods in the Student class:

public override bool Equals(object student)
{
    if (student == null)
        return false;
    if (GetType() != student.GetType())
        return false;

    var student1 = (Student)student;
    return (_rollNo == student1._rollNo) && (_level == student1._level) && (_age == student1._age);
}

public override int GetHashCode()
{
    return ToString().GetHashCode();
}

After checking the null and the type, we compare the _rollNo, _level, and _age and return the result.

Now let’s overload == and != operators so that the logic becomes identical to that of the Equals method:

public static bool operator ==(Student studentLeft, Student studentRight)
{
    if (studentLeft.Equals(studentRight))
        return true;
    else
        return false;
}

public static bool operator !=(Student studentLeft, Student studentRight)
{
    if (studentLeft.Equals(studentRight))
        return false;
    else
        return true;
}

So, both methods are using the logic of the Equals method.

After that, let’s use these overloaded operators:

var john = new Student(1, 1, "John", 7, 2);
var alice = new Student(1, 5, "Alice", 12, 5);
var johnSecond = new Student(1, 1, "John", 7, 2);

Console.WriteLine(john.Equals(johnSecond) ? "Both indicate the same person." : "They indicate different persons.");
Console.WriteLine(john == johnSecond ? "Both indicate the same person." : "They indicate different persons.");

johnSecond++;

Console.WriteLine(john.Equals(johnSecond) ? "Both indicate the same person." : "They indicate different persons.");
Console.WriteLine(john == johnSecond ? "Both indicate the same person." : "They indicate different persons.");
Console.WriteLine(john != alice ? "They indicate different persons." : "Both indicate the same person.");

Here we use the Equals method, and the == and != operators to compare our objects based on _rollNo, _level, and _age. Then, we change the value of _numberOfPassedCourses in johnSecond and again test the Equals method and the == operator.

Now, if we run the program, we can see the result:

Both indicate the same person.
Both indicate the same person.
Both indicate the same person.
Both indicate the same person.
They indicate different persons.

What Are the Advantages of Operator Overloading?

Operator overloading provides us with various advantages while coding. It makes the code easier and more understandable. Operator overloading offers support for user-defined types in the same way as built-in types do. We can use notation that is closely related to the target domain.

Conclusion

In this article, we’ve learned about operator overloading, the rules, syntax, and benefits. We’ve also seen an example and implementation to understand it better.

 

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!