Freigeben über


Why are overloaded operators always static in C#?

A language design question was posted to the Microsoft internal C# discussion group this morning: "Why must overloaded operators be static in C#? In C++ an overloaded operator can be implemented by a static, instance or virtual method. Is there some reason for this constraint in C#? "

Before I get into the specifics, there is a larger point here worth delving into, or at least linking to. Raymond Chen immediately pointed out that the questioner had it backwards. The design of the C# language is not a subtractive process; though I take Bob Cringely's rather backhanded compliments from 2001 in the best possible way, C# is not Java/C++/whatever with the kludgy parts removed. Former C# team member Eric Gunnerson wrote a great article about how the process actually works.

Rather, the question we should be asking ourselves when faced with a potential language feature is "does the compelling benefit of the feature justify all the costs?" And costs are considerably more than just the mundane dollar costs of designing, developing, testing, documenting and maintaining a feature. There are more subtle costs, like, will this feature make it more difficult to change the type inferencing algorithm in the future? Does this lead us into a world where we will be unable to make changes without introducing backwards compatibility breaks? And so on.

In this specific case, the compelling benefit is small. If you want to have a virtual dispatched overloaded operator in C# you can build one out of static parts very easily. For example:

public class B {
public static B operator+(B b1, B b2) { return b1.Add(b2); }
protected virtual B Add(B b2) { // ...

And there you have it. So, the benefits are small. But the costs are large. C++-style instance operators are weird. For example, they break symmetry. If you define an operator+ that takes a C and an int, then c+2 is legal but 2+c is not, and that badly breaks our intuition about how the addition operator should behave.

Similarly, with virtual operators in C++, the left-hand argument is the thing which parameterizes the virtual dispatch. So again, we get this weird asymmetry between the right and left sides. Really what you want for most binary operators is double dispatch -- you want the operator to be virtualized on the types of both arguments, not just the left-hand one. But neither C# nor C++ supports double dispatch natively. (Many real-world problems would be solved if we had double dispatch; for one thing, the visitor pattern becomes trivial. My colleague Wes is fond of pointing out that most design patterns are in fact necessary only insofar as the language has failed to provide a needed feature natively.)

And finally, in C++ you can only define an overloaded operator on a non-pointer type. This means that when you see c+2 and rewrite it as c.operator+(2), you are guaranteed that c is not a null pointer because it is not a pointer! C# also makes a distinction between values and references, but it would be very strange if instance operator overloads were only definable on non-nullable value types, and it would also be strange if c+2 could throw a null reference exception.

These and other difficulties along with the ease of building your own single (or double!) virtual dispatch mechanisms out of static mechanisms, makes it easy to decide to not add instance or virtual operator overloading to C#.

Comments

  • Anonymous
    May 14, 2007
    Static operator overloading is one of my main complaints about the language. The problem was not apparent to me until generics were introduced. Try to overload the operators in a generic type. You can not use generic constraints since overloaded operators are static and the compiler will not compile the code. This is a major problem with very inelegant solutions.

  • Anonymous
    May 15, 2007
    You wrote: > [In C++,] If you define an operator+ that takes a C and an int, then c+2 is > legal but 2+c is not, and that badly breaks our intuition about how the > addition operator should behave. Pardon my ignorance and my inference, but it sounds like you're saying that if you define an operator*(Matrix, Vector) you'll automatically get an operator*(Vector, Matrix) defined. This seems more dangerous than a compile-time failure!

  • Anonymous
    May 15, 2007
    No, sorry, that is not what I am saying.  What I'm saying is that with "instance" operators in C++ you CANNOT define a symmetric operator even if you want to, if one of the operators is a built-in type or a type that you cannot change the definition of for some reason. With static operators you can choose to define the operator symmetrically or asymmetrically.  With instance operators you don't always have that choice.

  • Anonymous
    May 15, 2007
    Thanks for the clarification. In fact, I generally use non-member operators in C++ anyway -- the most compelling reason for this is that it allows implicit conversions of the LHS (which I think, on reflection, was your point).

  • Anonymous
    May 17, 2007
    There is actually a real-world scenario where instance operators would be quite useful. The project I'm currently working on involves creating a small Domain-Specific Language. To add a little twist, the DSL is defined in very abstract terms (mostly interfaces) to allow us to have multiple implementation back-ends. The specific problem we have is with expression-type objects where we want to use some inversion of control - have them defined as interfaces in the DSL and implemented in external, pluggable modules. It would be awfully nice to add instance operators to those interfaces to make the DSL slightly less awkward to use.

  • Anonymous
    June 20, 2007
    Welcome to the XXVIII Community Convergence. In these posts I try to wrap up events that have occurred

  • Anonymous
    August 14, 2007
    Raymond has an interesting post today about two subtle aspects of C#: how order of evaluation in an expression

  • Anonymous
    May 14, 2008
    The comment has been removed

  • Anonymous
    August 31, 2009
    It seems as though the solution would be a method internal static Object add(Object obj_1, Object obj_2) {    return obj_1 + obj_2; } dartme18 at g(oogle) mail dot com Does that help?

  • Anonymous
    October 16, 2009
    I would prefer instance operator overloads for the ability to overload the op= operators... that is, +=, -=, etc. are much cleaner as instance operators since they inherently are acting on the lhs object and order is irrelevent.  It would seem more efficient to have these operators work directly on the lhs object rather than implement as the underlying operator followed by assignment.

  • Anonymous
    August 29, 2011
    >> In this specific case, the compelling benefit is small. If you want to have a virtual dispatched overloaded operator in >> C# you can build one out of static parts very easily. For example: >> public class B { >>     public static B operator+(B b1, B b2) { return b1.Add(b2); } >>     protected virtual B Add(B b2) { // ... >> And there you have it. So, the benefits are small. This inadvertently modifies the value of b1 if the programmer writes  b3 = b1 + b2;  So no it does not work. >> only definable on non-nullable value types, and it would also be strange if c+2 could throw a null >> reference exception. Why would it be strange if c+2 could throw a null-reference exception if c 'is' null. My biggest misery is that with "static B operator+(B b1, B b2) { ... }", memory allocation gets performed even when the programmer used just "b1 += b2";  My program is currently running straight 10 times slower (measured) due to this.