Skip to content

C# - Operator Overloading

Operator overloading allows you to define custom implementations for C#‘s built-in operators when used with user-defined types like classes or structs. This can make your code more intuitive and readable, allowing you to write vector1 + vector2 instead of vector1.Add(vector2).

Overloaded operators are implemented as public static methods with a special name: the operator keyword followed by the operator symbol. They take one (for unary) or two (for binary) parameters and return a result.

Let’s define a Box class. In modern C#, we use properties for data access and constructors for initialization, which is much cleaner than old-style Set... methods.

public class Box
{
// Auto-implemented properties for dimensions
public double Length { get; private set; }
public double Breadth { get; private set; }
public double Height { get; private set; }
// A read-only computed property for Volume
public double Volume => Length * Breadth * Height;
// Constructor for easy initialization
public Box(double length, double breadth, double height)
{
Length = length;
Breadth = breadth;
Height = height;
}
// Overload the + operator to add two Box objects
public static Box operator +(Box b1, Box b2)
{
// Creates a new box with combined dimensions
return new Box(b1.Length + b2.Length, b1.Breadth + b2.Breadth, b1.Height + b2.Height);
}
// Override ToString() for a clean string representation
public override string ToString()
{
return $"Box (L: {Length}, B: {Breadth}, H: {Height})";
}
}
using System;
// Create two Box instances
var box1 = new Box(6.0, 7.0, 5.0);
var box2 = new Box(12.0, 13.0, 10.0);
Console.WriteLine($"Volume of Box1: {box1.Volume}");
Console.WriteLine($"Volume of Box2: {box2.Volume}");
// Add the two boxes using the overloaded '+' operator
Box box3 = box1 + box2;
Console.WriteLine($"Details of Box3: {box3}");
Console.WriteLine($"Volume of Box3: {box3.Volume}");
// Output:
// Volume of Box1: 210
// Volume of Box2: 1560
// Details of Box3: Box (L: 18, B: 20, H: 15)
// Volume of Box3: 5400

Overloadable vs. Non-Overloadable Operators

Section titled “Overloadable vs. Non-Overloadable Operators”
CategoryOperatorsOverloadable?
Unary+, -, !, ~, ++, --, true, falseYes
Binary+, -, *, /, %, &, |, ^, <<, >>Yes
Comparison==, !=, <, >, <=, >=Yes (must be overloaded in pairs)
Conditional Logical&&, ||No (but they are short-circuited using & and |)
Assignment+=, -=, *= etc.No (implicitly used if the binary operator is overloaded)
Member/Other., [], (), ?:, new, is, as, typeofNo ([] is defined via an indexer, not operator overloading)

Best Practices for Overloading Comparison Operators

Section titled “Best Practices for Overloading Comparison Operators”

When you overload the equality operators (==, !=), you must follow certain rules to ensure your type behaves predictably with the rest of the .NET ecosystem.

  • Overload in Pairs: If you overload ==, you must also overload !=. The same applies to < and >, and <= and >=.
  • Override Equals() and GetHashCode(): This is critical. Many parts of .NET, especially collections like Dictionary and HashSet, rely on Equals() and GetHashCode(), not ==. Your == operator and Equals method should have consistent logic.
  • Handle Nulls: Your == and != operators must correctly handle cases where one or both operands are null.

Example: Overloading Equality on a Vector2D Struct

Section titled “Example: Overloading Equality on a Vector2D Struct”

A struct is a good candidate for this, as they are value types.

public readonly struct Vector2D : IEquatable<Vector2D>
{
public double X { get; }
public double Y { get; }
public Vector2D(double x, double y) { X = x; Y = y; }
// 1. Overload ==
public static bool operator ==(Vector2D v1, Vector2D v2)
{
return v1.X == v2.X && v1.Y == v2.Y;
}
// 2. Overload != (paired with ==)
public static bool operator !=(Vector2D v1, Vector2D v2)
{
return !(v1 == v2);
}
// 3. Override Equals(object) for general equality
public override bool Equals(object? obj)
{
return obj is Vector2D other && this == other;
}
// 4. Implement IEquatable<T> for type-safe equality
public bool Equals(Vector2D other)
{
return this == other;
}
// 5. Override GetHashCode whenever Equals is overridden
public override int GetHashCode()
{
return HashCode.Combine(X, Y);
}
}
// --- Usage ---
var vec1 = new Vector2D(3, 4);
var vec2 = new Vector2D(3, 4);
var vec3 = new Vector2D(5, 6);
Console.WriteLine($"vec1 == vec2: {vec1 == vec2}"); // True
Console.WriteLine($"vec1 != vec3: {vec1 != vec3}"); // True
Console.WriteLine($"vec1.Equals(vec2): {vec1.Equals(vec2)}"); // True

Guideline: Only overload operators when their meaning is clear and intuitive, as with mathematical types (vectors, complex numbers) or data structures. Overloading + on a Person class, for example, would be ambiguous and confusing.