【问题标题】:How operator == is selected?如何选择运算符 ==?
【发布时间】:2023-10-29 10:45:01
【问题描述】:
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"
    }
}

一些问题:

A) 为什么#2 & #5 打印“False”? - 在这些情况下,我希望运算符实现应取自 B 类。

B) 我说得对吗:由于 #8 和 #9 都打印“False” - Cosen 运算符实现是第一个发现的,这两种事实参数类型都可以转换为其参数类型?

c) 选择 operator == 实现的常用规则是什么?

【问题讨论】:

  • 使用通用约束where T : A,该函数将使用A中的运算符。如果你改变它where T : BCheckChosenOper(b, b2);将打印true

标签: c# .net generics operator-keyword equality-operator


【解决方案1】:

A) 好吧,您的方法 CheckChosenOper 是一个泛型方法,即使您有意指定 &lt;B&gt; 类型,它仍然“使用”类 A 作为基类并且它是自己的 == 运算符。似乎派生类中的运算符没有被覆盖,CheckChosenOper 方法只能使用它已知的:A 类。它不能自行运行并使用反射来搜索其他有效的相等运算符。

B) 不,你错了。在 #8 和 #9 的情况下,编译器有两个选择 - 将 A 向上转换为 B 并将 B1 与 B2 进行比较,将 B 向下转换为 A 并将 A1 与 A2 进行比较。由于向上转换是一个禁忌,并且必须通过转换运算符专门完成,因此编译器采用简单的 A1==A2 选择。

C) 好吧,如果我写的是struct,我肯定会去写一个。还有!=GetHashCodeIEquatable&lt;&gt; 和其他一些东西。但是必须有充分的理由来创建自己的结构,因为它们的行为不同。

【讨论】:

    【解决方案2】:
    1. 你在这里使用静态绑定(静态绑定是默认的,除非你使用dynamic),所以重载决议发生在编译时。因此,表达式x==x2 必须解析为A 中的==,或者B 中的那个。它无法解决这两个问题。如果仔细查看编译器有哪些信息,您会发现它无法将其解析为B 中的那个,因为它只知道TA 或其子类。 T 可以是A,或者是B 的兄弟,在这种情况下调用B 重载根本不起作用。您不能将A 传递给B 参数,对吗?

      您可以通过将泛型方法设为dynamic 而不是泛型来查看预期结果:

       static void CheckChosenOper(dynamic x, dynamic x2)
       {
           Console.WriteLine(x==x2);
       }
      
    2. 编译器必须解析为A 重载,因为其中一个参数是A,而As 无法隐式转换为B,因此无法将其传递给该参数。

    3. 这一切都在语言规范中的一个名为Binary operator overload resolution 的部分中进行了描述。但其中大部分遵循与method resolution 相同的规则,因此您可能只想阅读它。

    【讨论】:

    • 1.哦,我想我明白了 - '因为所有 ref-types 共享一个通用实现,编译器必须选择 1 个运算符实现,这将适合所有可能的情况。
    • @user1234567 我真的不知道您所说的“所有引用类型共享单个通用实现”是什么意思,但它必须只选择一个的原因是因为“选择”发生在编译时,并且您的代码中只有一个表达式 - x==x1
    • 说“所有 ref-types 共享单个泛型实现”,我的意思是,假设编译器可以检查代码并查看此泛型方法的所有用法,因此它可以满足 @ 987654344@ 使用 & (假设)可以为每个遇到的类型参数创建 1 个单独的实现(如 C++ 模板)。在这种情况下,为类 B 构建实现,它可能会从类 B 中选择 operator==。但!这是关于假设世界的故事,但我们生活在真实的世界中 =)