如何选择 operator == ?
How operator == is selected?
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
class。
B) 我说得对吗:由于 #8 和 #9 都打印出“False” - Cosen 运算符实现是第一个发现的,所以两种事实参数类型都可以转换为其参数类型?
c) 选择运算符 == 实现的常见规则是什么?
A) 嗯,你的方法 CheckChosenOper
是一个通用方法,它仍然“使用”class A 作为基础 class 并且它自己的 ==
运算符甚至如果您有意指定 <B>
类型。似乎运算符在派生的 classes 中没有被覆盖,并且 CheckChosenOper
方法只能使用它已知的内容: class A. 它不能单独使用反射来搜索其他有效的相等运算符。
B) 不,你错了。在#8 和#9 的情况下,编译器有两个选择——将 A 向上转换为 B 并将 B1 与 B2 进行比较,将 B 向下转换为 A 并将 A1 与 A2 进行比较。由于向上转型是一个禁忌,必须通过转型运算符专门完成,编译器会选择简单的 A1==A2。
C) 好吧,如果我要写一个 struct
,我肯定会写一个。以及 !=
、GetHashCode
、IEquatable<>
和其他一些内容。但是必须有充分的理由来创建您自己的结构,因为它们的行为不同。
你在这里使用了静态绑定(静态绑定是默认的,除非你使用dynamic
),所以重载解析发生在编译时。因此,表达式 x==x2
必须解析为 A
中的 ==
或 B
中的 ==
。它不能同时解决它。如果仔细查看编译器的信息,您会发现它无法将其解析为 B
中的信息,因为它只知道 T
是 A
或其子类。 T
可以是 A
或 B
的兄弟,在这种情况下调用 B
重载根本不起作用。您不能将 A
传递给 B
参数,可以吗?
您可以通过使用通用方法 dynamic
而不是通用方法来查看预期结果:
static void CheckChosenOper(dynamic x, dynamic x2)
{
Console.WriteLine(x==x2);
}
编译器必须解析为 A
重载,因为参数之一是 A
,并且 A
s 不能隐式转换为 B
, 所以不能传入那个参数.
这一切都在语言规范中的 Binary operator overload resolution. But most of it follows the same rules as method resolution 部分进行了描述,因此您可能只想阅读它。
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
class。
B) 我说得对吗:由于 #8 和 #9 都打印出“False” - Cosen 运算符实现是第一个发现的,所以两种事实参数类型都可以转换为其参数类型?
c) 选择运算符 == 实现的常见规则是什么?
A) 嗯,你的方法 CheckChosenOper
是一个通用方法,它仍然“使用”class A 作为基础 class 并且它自己的 ==
运算符甚至如果您有意指定 <B>
类型。似乎运算符在派生的 classes 中没有被覆盖,并且 CheckChosenOper
方法只能使用它已知的内容: class A. 它不能单独使用反射来搜索其他有效的相等运算符。
B) 不,你错了。在#8 和#9 的情况下,编译器有两个选择——将 A 向上转换为 B 并将 B1 与 B2 进行比较,将 B 向下转换为 A 并将 A1 与 A2 进行比较。由于向上转型是一个禁忌,必须通过转型运算符专门完成,编译器会选择简单的 A1==A2。
C) 好吧,如果我要写一个 struct
,我肯定会写一个。以及 !=
、GetHashCode
、IEquatable<>
和其他一些内容。但是必须有充分的理由来创建您自己的结构,因为它们的行为不同。
你在这里使用了静态绑定(静态绑定是默认的,除非你使用
dynamic
),所以重载解析发生在编译时。因此,表达式x==x2
必须解析为A
中的==
或B
中的==
。它不能同时解决它。如果仔细查看编译器的信息,您会发现它无法将其解析为B
中的信息,因为它只知道T
是A
或其子类。T
可以是A
或B
的兄弟,在这种情况下调用B
重载根本不起作用。您不能将A
传递给B
参数,可以吗?您可以通过使用通用方法
dynamic
而不是通用方法来查看预期结果:static void CheckChosenOper(dynamic x, dynamic x2) { Console.WriteLine(x==x2); }
编译器必须解析为
A
重载,因为参数之一是A
,并且A
s 不能隐式转换为B
, 所以不能传入那个参数.这一切都在语言规范中的 Binary operator overload resolution. But most of it follows the same rules as method resolution 部分进行了描述,因此您可能只想阅读它。