运算符是被重载而不是被覆盖的。这意味着实际调用的方法在编译时确定,而不是在运行时确定。为了理解这种差异,您应该像这样考虑:
假设您有以下代码
public class MyBase
{
public virtual void DoSomething() { Console.WriteLine("Base"); }
}
public class A : MyBase
{
public override void DoSomething() { Console.WriteLine("A"); }
}
public class B : MyBase
{
public override void DoSomething() { Console.WriteLine("B"); }
}
现在你可以创建 A 和 B 的实例:
MyBase a = new A();
MyBase b = new B();
a.DoSomething();
b.DoSomething();
发生的情况是每个 MyBase 实例都包含一个指向必须调用的 DoSomething()
实际实现的 vtable 的隐藏引用。因此,当运行时需要在 a
上调用 DoSomething()
时,它将查看 a
的 vtable,并找到打印 "A"
的版本。重要的部分是这发生在运行时。
现在,当您重载方法时,您创建一个具有相同名称但不同签名的方法。
public class C
{
public void Print(string str) { Console.WriteLine("string: " + string); }
public void Print(int i) { Console.WriteLine("int: " + i); }
public void Print(object obj) { Console.WriteLine("object: " + obj.ToString()); }
}
var c = new C();
c.Print("1");
c.Print(1);
c.Print((object)1);
这次编译器决定调用哪个方法,而不是在运行时。但编译器并不是很聪明。当它看到 (object)1
时,它看到的是一个对象的引用,并尝试找到一个可以接受对象作为参数的 Print()
版本。
有时编译器会找到两个可用的版本。例如,"1"
是一个字符串,但它也是一个对象,因此第一个和第三个版本的 Print()
都能够接受一个字符串。在这种情况下,编译器选择具有最具体参数类型的版本。有时编译器无法选择,就像以下示例中一样:
public static void Print(string a, object b) { Console.WriteLine("V1"); }
public static void Print(object a, string b) { Console.WriteLine("V2"); }
Print("a", "b");
在这种情况下,没有一个版本比另一个更具体,因此编译器会产生编译错误:
调用以下方法或属性时存在歧义:'Test.Program.Print(string, object)' 和 'Test.Program.Print(object, string)'
正如我在开头所说的,
==
运算符被重载了。这意味着编译器会选择使用哪个版本。最通用的版本是两个操作数都是
Object
类型的版本。这个版本比较引用。这就是为什么
(object) i == (object) j
返回 false 的原因。