Why must we define both == and != in C#?

后端 未结 13 2074
清酒与你
清酒与你 2020-11-27 08:55

The C# compiler requires that whenever a custom type defines operator ==, it must also define != (see here).

Why?

I\'m curious to k

相关标签:
13条回答
  • 2020-11-27 09:35

    I can't speak for the language designers, but from what I can reason on, it seems like it was intentional, proper design decision.

    Looking at this basic F# code, you can compile this into a working library. This is legal code for F#, and only overloads the equality operator, not the inequality:

    module Module1
    
    type Foo() =
        let mutable myInternalValue = 0
        member this.Prop
            with get () = myInternalValue
            and set (value) = myInternalValue <- value
    
        static member op_Equality (left : Foo, right : Foo) = left.Prop = right.Prop
        //static member op_Inequality (left : Foo, right : Foo) = left.Prop <> right.Prop
    

    This does exactly what it looks like. It creates an equality comparer on == only, and checks to see if the internal values of the class are equal.

    While you can't create a class like this in C#, you can use one that was compiled for .NET. It's obvious it will use our overloaded operator for == So, what does the runtime use for !=?

    The C# EMCA standard has a whole bunch of rules (section 14.9) explaining how to determine which operator to use when evaluating equality. To put it overly-simplified and thus not perfectly accurate, if the types that are being compared are of the same type and there is an overloaded equality operator present, it will use that overload and not the standard reference equality operator inherited from Object. It is no surprise, then, that if only one of the operators is present, it will use the default reference equality operator, that all objects have, there is not an overload for it.1

    Knowing that this is the case, the real question is: Why was this designed in this way and why doesn't the compiler figure it out on its own? A lot people are saying this wasn't a design decision, but I like to think it was thought out this way, especially regarding the fact all objects have a default equality operator.

    So, why doesn't the compiler automagically create the != operator? I can't know for sure unless someone from Microsoft confirms this, but this is what I can determine from reasoning on the facts.


    To prevent unexpected behavior

    Perhaps I want to do a value comparison on == to test equality. However, when it came to != I didn't care at all if the values were equal unless the reference was equal, because for my program to consider them equal, I only care if the references match. After all, this is actually outlined as default behavior of the C# (if both operators were not overloaded, as would be in case of some .net libraries written in another language). If the compiler was adding in code automatically, I could no longer rely on the compiler to output code that should is compliant. The compiler should not write hidden code that changes the behavior of yours, especially when the code you've written is within standards of both C# and the CLI.

    In terms of it forcing you to overload it, instead of going to the default behavior, I can only firmly say that it is in the standard (EMCA-334 17.9.2)2. The standard does not specify why. I believe this is due to the fact that C# borrows much behavior from C++. See below for more on this.


    When you override != and ==, you do not have to return bool.

    This is another likely reason. In C#, this function:

    public static int operator ==(MyClass a, MyClass b) { return 0; }
    

    is as valid as this one:

    public static bool operator ==(MyClass a, MyClass b) { return true; }
    

    If you're returning something other than bool, the compiler cannot automatically infer an opposite type. Furthermore, in the case where your operator does return bool, it just doesn't make sense for them create generate code that would only exist in that one specific case or, as I said above, code that hides the default behavior of the CLR.


    C# borrows much from C++3

    When C# was introduced, there was an article in MSDN magazine that wrote, talking about C#:

    Many developers wish there was a language that was easy to write, read, and maintain like Visual Basic, but that still provided the power and flexibility of C++.

    Yes the design goal for C# was to give nearly the same amount of power as C++, sacrificing only a little for conveniences like rigid type-safety and garbage-collection. C# was strongly modeled after C++.

    You may not be surprised to learn that in C++, the equality operators do not have to return bool, as shown in this example program

    Now, C++ does not directly require you to overload the complementary operator. If your compiled the code in the example program, you will see it runs with no errors. However, if you tried adding the line:

    cout << (a != b);
    

    you will get

    compiler error C2678 (MSVC) : binary '!=' : no operator found which takes a left-hand operand of type 'Test' (or there is no acceptable conversion)`.

    So, while C++ itself doesn't require you to overload in pairs, it will not let you use an equality operator that you haven't overloaded on a custom class. It's valid in .NET, because all objects have a default one; C++ does not.


    1. As a side note, the C# standard still requires you to overload the pair of operators if you want to overload either one. This is a part of the standard and not simply the compiler. However, the same rules regarding the determination of which operator to call apply when you're accessing a .net library written in another language that doesn't have the same requirements.

    2. EMCA-334 (pdf) (http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-334.pdf)

    3. And Java, but that's really not the point here

    0 讨论(0)
  • 2020-11-27 09:35

    Other than that C# defers to C++ in many areas, the best explanation I can think of is that in some cases you might want to take a slightly different approach to proving "not equality" than to proving "equality".

    Obviously with string comparison, for example, you can just test for equality and return out of the loop when you see nonmatching characters. However, it might not be so clean with more complicated problems. The bloom filter comes to mind; it's very easy to quickly tell if the element is not in the set, but difficult to tell if the element is in the set. While the same return technique could apply, the code might not be as pretty.

    0 讨论(0)
  • 2020-11-27 09:45

    Just to add to the excellent answers here:

    Consider what would happen in the debugger, when you try to step into a != operator and end up in an == operator instead! Talk about confusing!

    It makes sense that CLR would allow you the freedom to leave out one or other of the operators - as it must work with many languages. But there are plenty of examples of C# not exposing CLR features (ref returns and locals, for example), and plenty of examples of implementing features not in the CLR itself (eg: using, lock, foreach, etc).

    0 讨论(0)
  • 2020-11-27 09:46

    Probably for if someone needs to implement three-valued logic (i.e. null). In cases like that - ANSI standard SQL, for instance - the operators can't simply be negated depending on the input.

    You could have a case where:

    var a = SomeObject();
    

    And a == true returns false and a == false also returns false.

    0 讨论(0)
  • 2020-11-27 09:53

    If you overload == for your custom type, and not != then it will be handled by the != operator for object != object since everything is derived from object, and this would be much different than CustomType != CustomType.

    Also the language creators probably wanted it this way to allow the most most flexibility for coders, and also so that they are not making assumptions about what you intend to do.

    0 讨论(0)
  • 2020-11-27 09:57

    If you look at implementations of overloads of == and != in the .net source, they often don't implement != as !(left == right). They implement it fully (like ==) with negated logic. For example, DateTime implements == as

    return d1.InternalTicks == d2.InternalTicks;
    

    and != as

    return d1.InternalTicks != d2.InternalTicks;
    

    If you (or the compiler if it did it implicitly) were to implement != as

    return !(d1==d2);
    

    then you are making an assumption about the internal implementation of == and != in the things your class is referencing. Avoiding that assumption may be the philosophy behind their decision.

    0 讨论(0)
提交回复
热议问题