How operator == is selected?

Viewed 97
internal static class ChoosingEqOperTest
{
    class A
    {
        public static bool operator ==(A a, A a2) => false;
    }

    class B:A
    {
        public static bool operator ==(B a, B a2) => true;
    }

    static void CheckChosenOper<T>(T x, T x2) where T : A
    {
        Console.WriteLine(x==x2);
    }

    internal static void Do()
    {
        var a  = new A();
        var a2 = new A();
        var b  = new B();
        var b2 = new B();
        CheckChosenOper(a,a2);      // 1. "False"
        CheckChosenOper(b,b2);      // 2. "False" !?
        CheckChosenOper<A>(a,a2);   // 3. "False"
        CheckChosenOper<A>(b, b2);  // 4. "False"
        //CheckChosenOper<B>(a, a2); //Complie Error
        CheckChosenOper<B>(b, b2);  // 5. "False" !?
        Console.WriteLine(a == a2); // 6. "False"
        Console.WriteLine(b == b2); // 7. "True"
        Console.WriteLine(a == b2); // 8. "False"
        Console.WriteLine(b == a2); // 9. "False"
    }
}

Some questions:

A) Why #2 & #5 prints "False"? - I expect operator implementation should be taken from B class in these cases.

B) Am I right: As both #8 & #9 prints "False" - Cosen operator implementation is the 1st found, both factual-arguments types could be casted to its parameters types?

c) What are common rules of choosing operator == implementation?

2 Answers

A) Well, your method CheckChosenOper is a generic method, which still "uses" class A as a base class and it's own == operator even if you intentionally specify <B> type. It seems that operators are not overridden in derived classes, and CheckChosenOper method can only work with what is known to it: class A. It cannot go on its own and use reflection to search for other valid equality operators.

B) No, you're wrong. In cases #8 and #9 the compiler has two choices - upcast A to B and compare B1 with B2 and downcast B to A and compare A1 to A2. Since upcasting is a no-no and must be specifically done via casting operator, the compiler goes with the easy A1==A2 choice.

C) Well, if I was writing a struct, I'd surely go for one. Along with !=, GetHashCode, IEquatable<>, and some other stuff. But there must be really good reason to create your own struct(s), as they behave differently.

  1. You are using static binding here (static binding is the default, unless you use dynamic), so overload resolution occurs at compile time. Therefore, the expression x==x2 must resolve to either the == in A, or the one in B. It can't resolve it to both. If you look closely at what information the compiler has, you'll notice that it can't resolve it to the one in B, because it only knows that T is A or a subclass thereof. T could be A, or a sibling of B, in which case calling the B overload would not work at all. You can't pass an A into a B parameter, can you?

    You can see your expected result by making your generic method dynamic rather than generic:

     static void CheckChosenOper(dynamic x, dynamic x2)
     {
         Console.WriteLine(x==x2);
     }
    
  2. The compiler must resolve to the A overload because one of the arguments is A, and As cannot be implicitly converted to B, so it can't be passed into that parameter.

  3. This is all described in the language spec, in a section called Binary operator overload resolution. But most of it follows the same rules as method resolution, so you might want to just read that instead.

Related