you're saying that if an operator doesn't cause the compiler to throw a fit in *ANY* OOP language, the code is valid? I disagree...
In every OOP langauge, there are intrinsic types and user-defined types. Intrinsic types (like numeric types, boolean, and so on) are types the compiler knows about, and knows how to generate code to perform whatever operation a given operator requires.
For user-defined types that are not derived from intrinsic types, or derived from types that provide a virtual member that corresponds to an operator, the compiler *cannot* generate code for a given operator, so it has no choice but to throw an exception.
In the CLR, The == and != operators are the *only* operators that can be used on any type, and that is because all types are derived from System.Object, which provides a virtual Equals() method (overriden by System.ValueType(), System.String, and many others), and that is what the == and != operators delegate to.
In CLR, with == and !=, what happens depends on whether the type being compared is a reference type or a value type. For reference types, the Equals() member of System.Object() returns true if the operands both refer to the same object.
For value types, System.ValueType() overloads Equals() to perform a byte-by-byte comparison of each object's data.
Hence, in the CLR, == and != are implemented for every object, but any other operator must be overloaded for a given type (or one of its base types), or a compiler error will be generated.
And that's why the compiler must throw an error - because it must generate code for the operator, and if it doesn't know what code to generate (e.g., it doesn't know how to compare the operands), then how can there not be an error?
And why not use it? In most cases in OOP languages, using == to compare two Strings is absolutely wrong... in that case you are comparing whether two objects are physically the same object, not whether the two string values are the same things. Sometimes that may return the correct (read: expected) value, but most of the time you really shouldn't do it. While that case may not hold true in C#, it is a fallacy to say "In OOP"
The reason why one should use operators rather than explicit member functions is quite simple.
There are tens of thousands of classes, and each of them are free to implement functions that correspond to what operators do. The reason why you should use operators is quite simply because you don't have to remember what member function provides the implementation for each and every type. That's the purpose of overloading operators. It allows the programmer to not have to be intimately familiar with a type, and remember what the name of the method that compares two instances for equality, or a relational function like is-greater-than, and so on.
So, for example, you have two types written by two different people and each of them decides that the member function that tells if one instance is greater than another instance, will have two completely different names.
public class DumbWidget
{
public static bool IsLargerThan( DumbWidget first, DumbWidget second )
{
// compare instances
}
public static bool IsSmallerThan( DumbWidget first, DumbWidget second )
{
// compare instances
}
}
public class DumbGadget
{
public static bool IsTallerThan( DumbGadget first, DumbGadget second )
{
// compare instances
}
public static bool IsShorterThan( DumbGadget first, DumbGadget second )
{
// compare instances
}
}
Notice that the two classes were designed by different people, and each had their own idea about what the relational comparison member functions should be called. There's no protocol for that, there's no requirement or rule that states you must use a certain name, so it's pot luck as to what name will be used.
And this is why you should use overloaded operators:
public class SmarterWidget
{
public static bool IsLargerThan( SmarterWidget first, SmarterWidget second )
{
// compare instances
}
public static bool IsSmallerThan( SmarterWidget first, SmarterWidget second )
{
// compare instances
}
public static bool operator >( SmarterWidget left, SmarterWidget right )
{
return IsLargerThan( left, right );
}
public static bool operator <( SmarterWidget left, SmarterWidget right )
{
return IsSmallerThan( left, right );
}
}
In SmarterWidget, you don't have to remember what the names of the member functions are. You don't need to use intellisense... you only need to use your basic instincts.
It really shouldn't be that difficult to understand that, when types provide overloaded operators that call corresponding member functions (whose name is not the same for every type), the programmer is not required to remember what each type's respective member function is called.
With operators, they don't have to remember anything! They just use the damn operator, and that's that!
ThisDate > ThatDate doesn't always work in every OOP language, but won't upset the compiler because you are doing a valid comparison.... only not of the values as you would think, but of the objects.
That's the point... That's not a valid comparison. The class defines the concept of what 'is-greater-than means, not the compiler.
The fact that you believe that the C++ mentality (where '==' generally means "are these two pointers pointing at the same memory address?), should apply to higher-level languages and higher level class abstractions is wrong.
Java's == is also wrong for strings, because the need to tell if two strings refer to the same physical object is rare, in contrast to the need to compare their values lexically.
Operators should perform the most common comparison, the one that most people would expect it to, and unfortunately that is not what you seem to think most would expect == to do.
Java was written by a C++ programmer, and designed to be friendly to C++ programmers. That was the mistake.
Think of C# as Java with the many of the mistakes corrected.