DEV Community

Cover image for C# Essentials: Operator Overloading and Custom Sorting Made Simple
Manjunath Shenoy
Manjunath Shenoy

Posted on

C# Essentials: Operator Overloading and Custom Sorting Made Simple

This blog delves into the concept of operator overloading in C#. We will also see how to perform sorting on Custom objects. While these topics are foundational, I’ve tried to present it in a way that highlights nuances we often overlook. My goal is to explain the subject so clearly that a single read-through leaves a lasting impression.

GitHub Source Code

1.Introduction to Operator Overloading in C#

Operator overloading is an interesting and intuitive feature in C#. It allows you to redefine how operators like +, -, *, or / behave for your custom classes or structs, making code more expressive and natural.

For example, consider the System.String class. It has a static method named Concat to concatenate two strings:

string name = "manju";
string type = "dev";
string result = string.Concat(name, type); // Output: "manjudev"
Enter fullscreen mode Exit fullscreen mode

But instead of using the Concat method, you can achieve the same result with the + operator:

string result = name + type; // Output: "manjudev"

This behavior is possible because the + operator is overloaded for the string class. Similarly, we can overload operators for our own classes to make them intuitive and natural to use. Let’s explore this concept with an example.

Create a Calculator Class (custom class for our egample)

Create a Calculator class that supports arithmetic operations (+, -, *, /). Instead of relying on verbose method calls, you can define operator overloads to make the syntax more concise and intuitive.

Eg code

public class Calculator
{
    public int Value { get; set; }

    public Calculator(int value)
    {
        Value = value;
    }

    public static Calculator operator +(Calculator a, Calculator b)
    {
        return new Calculator(a.Value + b.Value);
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, you can use the + operator with instances of Calculator:

Calculator calc1 = new Calculator(10);
Calculator calc2 = new Calculator(20);

Calculator result = calc1 + calc2;
Console.WriteLine(result.Value); // Output: 30
Enter fullscreen mode Exit fullscreen mode

Above makes the syntax concise and intuitive, resembling built-in types.

Addressing the IntelliSense Gap

Operator overloading is indeed powerful, but it comes with a limitation: IntelliSense (the auto-suggestion feature in IDEs) does not show overloaded operators. This means users may not immediately realize that the +, -, or other operators are available for the class. See egample below. When I type in “Calculator” and then a “dot”, operators methods don’t show up

Intellisense don’t show up for operator methods<br>

To bridge this gap, it’s a good practice to provide a named method alongside the operator overload. The + operator can internally call this method: For example:

public static Calculator Add(Calculator a, Calculator b)
{
    return new Calculator(a.Value + b.Value);
}
public static Calculator operator +(Calculator a, Calculator b)
{
    return Add(a, b);
}
Enter fullscreen mode Exit fullscreen mode

Now, users can either use the + operator OR call the Add method directly:

Calculator result1 = calc1 + calc2; // Using the operator
Calculator result2 = Calculator.Add(calc1, calc2); // Using the method
Enter fullscreen mode Exit fullscreen mode

This dual approach enhances discoverability and usability, especially for developers unfamiliar with the class.

When and Why to Use Operator Overloading

Operator overloading is best suited for scenarios where custom classes represent mathematical or logical entities (e.g., vectors, matrices, fractions, or even complex numbers). However, it should be used judiciously to avoid confusion. Ensure that the overloaded operators behave in a way that aligns with user expectations.
But let’s have a bit of fun! Think of operator overloading as going with the flow of intuition. If objects of a custom class can logically be added, multiplied, or divided, then why not make it happen? For example, imagine you have a Pest class. It might make sense that pests can be:

  • Added (+): A swarm of pests grows when combined with another swarm.
  • Multiplied (*): The infestation grows exponentially. -** Divided (/)**: Maybe it's reduced by pest control. By thinking creatively, operator overloading can make code more engaging and intuitive, while also injecting a touch of whimsy into programming.

2.Simplifying Sorting Custom Objects in C#: Understanding IComparable vs. IComparer

When I started working with sorting custom objects in C#, I found it tricky to differentiate between the two interfaces: IComparable and IComparer. Both are used for sorting, but they have distinct purposes. Let's simplify the concepts with some practical analogies and example

✅IComparable: "I Am Comparable"

Think of a class that implements IComparable as someone who compares themselves with others. For instance, imagine an Employee who is ambitious, a perfectionist, or just reflective. This person would compare their own performance or rank with another Employee.

Here’s the method it exposes:
public int CompareTo(Employee? other)

This method allows an object of the class to compare itself to another object of the same class. For example:

public class Employee : IComparable<Employee>
  {
      // Properties
      public string Name { get; set; }
      public int Age { get; set; }
      public string Role { get; set; }

      // Constructor
      public Employee(string name, int age, string role)
      {
          Name = name;
          Age = age;
          Role = role;
      }

      // Override ToString for better display
      public override string ToString()
      {
          return $"Name: {Name}, Age: {Age}, Role: {Role}";
      }

      public int CompareTo(Employee other)
      {
          if (other == null) return 1; // Current instance is greater if other is null
          return string.Compare(this.Name, other.Name, StringComparison.OrdinalIgnoreCase);
      }
  }
Enter fullscreen mode Exit fullscreen mode

✅IComparer: "I Compare Others"

Now, imagine an Evaluator or an Interviewer who doesn’t evaluate themselves but compares two candidates (e.g., Employees or Customers). This is the role of a class implementing IComparer.

Here’s the method it exposes:

public int Compare(Employee? x, Employee? y)

This method allows the comparer to evaluate two different objects. For instance:

 internal class CustomerComparer : IComparer<Customer>
 {      

     public int Compare(Customer? x, Customer? y)
     {
         if (x == null && y == null) return 0; // Both are null, considered equal
         if (x == null) return -1; // Null is less than non-null
         if (y == null) return 1;  // Non-null is greater than null
         return string.Compare(x.Name, y.Name, StringComparison.OrdinalIgnoreCase);
     }
 }
Enter fullscreen mode Exit fullscreen mode

Putting It All Together

✅IComparable Example:

Program.cs

v

oid SortingComparableEg()
{
    List<Employee> employees = new List<Employee>{
       new ("Manju s", 30, "Software Engineer"),
        new ("Tom s", 25, "Devops"),
        new ("Kristy s", 45, "Devops")
   };

    employees.Sort();// Sort employees by Name since our Employee class does so
    Console.WriteLine("Employees sorted by Name:");
    foreach (var employee in employees)
    {
        Console.WriteLine(employee);
    }
}
Enter fullscreen mode Exit fullscreen mode

✅IComparer Example:

Program.cs

void SortingComparerEg()
{
    List<Customer> customers = new List<Customer>{
        new ("Manju s", 30),
        new ("Tom s", 2),
        new ("Kristy", 33)
   };
    customers.Sort(new CustomerComparer());// CustomerComparer is used to sort
    Console.WriteLine("Customer sorted by Name:");
    foreach (var customer in customers)
    {
        Console.WriteLine(customer);
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion:

In conclusion, operator overloading and sorting custom objects are foundational yet versatile features in C#. This blog aims to present these concepts in a simple, pragmatic way that leaves a lasting impression. By focusing on practical applications and clear explanations, I hope to make these topics approachable and memorable for readers. Happy coding!

GitHub Source Code

Connect with Me:

If you enjoyed this post and would like to stay updated with more content like this, feel free to connect with me on social media:

▶️YouTube : Subscribe to my YouTube Channel for video tutorials and tons of videos on various subjects

✅Medium : Follow me on Medium where I share more technical articles and insights.

📧Email: Email me on techspacedeck@gmail.com for any questions, comments, or just to say hi!
I appreciate your support and look forward to connecting with you! Happy coding! And Please do not forget to clap 👏

Tip: Bookmark this blog and revisit it whenever you want a quick refresher on these operators. Happy coding! 😊

Top comments (0)