In this lesson, you will learn why operator overloading is technically static in C# and how to design your polymorphic classes to handle these operators correctly when interacting with inheritance hierarchies.
The Static Nature of Operators
In C#, when you overload an operator (like + or ==), the compiler determines which method to call based on the compile-time type of the variables, not the runtime type. This is a major difference from virtual methods, which use dynamic dispatch. Because operators must be static, they cannot be virtual or override.
The Edge Case: Polymorphism vs. Overloading
Consider a scenario where you have a base class Shape and a derived class Circle. If you define an operator + for Shape, it will not automatically "know" how to handle Circle objects if you pass them as Shape references.
Because the compiler chooses the operator based on the declared type, if you write code like this:
Shape s1 = new Circle(5);
Shape s2 = new Circle(10);
Shape result = s1 + s2; // The compiler uses the Shape operator!
Even if s1 and s2 are Circle objects, the compiler forces them through the Shape operator. If that operator isn't designed to handle specific derived properties, you lose the "polymorphic behavior" you expect.
The Solution: The "Bridge" Pattern
To get around this limitation, you should use the Template Method Pattern or a protected virtual method. You implement the static operator to simply delegate the work to a virtual instance method:
public class Shape {
public static Shape operator +(Shape a, Shape b) => a.Add(b);
// Delegate to a virtual method that CAN be overridden
protected virtual Shape Add(Shape other) {
/* logic for generic shape */
return new Shape();
}
}
public class Circle : Shape {
protected override Shape Add(Shape other) {
// Now you have access to runtime types via pattern matching!
if (other is Circle c) { /* logic for Circle + Circle */ }
return base.Add(other);
}
}
By delegating to a virtual instance method, you effectively "inject" polymorphism back into the static operator call, allowing derived classes to provide their own specialized addition logic while maintaining the convenience of operator syntax.
Key Takeaway: Since C# operators are static, they do not support polymorphism; you must delegate operator logic to virtual instance methods to allow derived classes to define their own behavior.