C# 如何用重载和重写的方法解释这种行为?

C# 如何用重载和重写的方法解释这种行为?,c#,overloading,virtual-functions,C#,Overloading,Virtual Functions,有谁能这么好地解释一下为什么这段代码显示Derived.DoWork(double)。我可以对这种行为做出一些解释,但我希望有人能为我澄清这一点 using System; public class Base { public virtual void DoWork(int param) { Console.WriteLine("Base.DoWork"); } } public class Derived : Base { public overri

有谁能这么好地解释一下为什么这段代码显示
Derived.DoWork(double)
。我可以对这种行为做出一些解释,但我希望有人能为我澄清这一点

using System;

public class Base
{
    public virtual void DoWork(int param) {
        Console.WriteLine("Base.DoWork");
    }
}

public class Derived : Base
{
    public override void DoWork(int param) {
        Console.WriteLine("Derived.DoWork(int)");
    }

    public void DoWork(double param) {
        Console.WriteLine("Derived.DoWork(double)");
    }

    public static void Main() {
        int val = 5;
        Derived d = new Derived();
        d.DoWork(val);
    }
}

埃里克·利珀特过去常说

在派生类中首先声明的方法比在基类中首先声明的方法更接近

因此,从上面的链接来看,派生类更接近于所选择的类

仔细实施此行为是为了避免

为了完整起见,我将分享以下要点:

  • 在派生类中首先声明的方法比在基类中首先声明的方法更接近

  • 嵌套类中的方法比包含类中的方法更接近

  • 接收类型的任何方法都比任何扩展方法更接近

  • 在嵌套命名空间的类中找到的扩展方法比在外部命名空间的类中找到的扩展方法更接近

  • 在当前命名空间中的类中找到的扩展方法比在using指令提到的命名空间中的类中找到的扩展方法更接近

  • 在using指令中提到的命名空间中的类中找到的扩展方法(该指令位于嵌套命名空间中)比在using指令中提到的命名空间中的类中找到的扩展方法(该指令位于外部命名空间中)更接近


此行为在C语言规范中有定义,特别是第7.5.3节“重载解决方案”。这里有一个,否则请参考本地应具有的CSharp Language Specification.docx,例如C:\Program Files(x86)\Microsoft Visual Studio 12.0\VC\Specification\1033\CSharp Language Specification.docx

在这种情况下,标记为override的方法被排除在外,因此,
double
重载是唯一有效的选项(emphasis mine):

这些上下文中的每一个都定义了候选函数成员集 以及参数列表,如中所述 详情请参见上述章节。例如,一组 方法调用的候选对象不包括标记为 重写(§7.4),基类中的方法如果有,则不是候选方法 派生类中的方法适用(§7.6.5.1)


这种行为显然是故意的:

'选择重载时,如果派生类中声明了任何兼容的方法,则忽略基类中声明的所有签名,即使它们在同一派生类中被重写!'

这正是我所想的答案!他正在将一个整数传递到
d.DoWork
中,因此它应该打印“派生的.DoWork(int)”,而不是“派生的.DoWork(double)”。另请参见@trispid,尽管
Derived
覆盖
DoWork(int)
,编译器仍然认为此方法属于
Base
DoWork(double)
是兼容的,因为
int
隐式转换为
double
,并且在
派生的
中也有定义,因此它是“更接近的”。作为证明,如果您将
Derived
更改为
newpublic void DoWork(int)
,您将看到
new
关键字告诉编译器将
DoWork(int)
视为现在属于
Derived
并将与其匹配。我对它进行了测试,如果有人好奇,VB.NET将调用整数版本。这是不同语言团队的一个有趣选择。首先,构建了T中声明的名为N的所有可访问成员集(第3.5节)和T的基本类型(第7.3.1节)。包含覆盖修饰符的声明将从集合中排除。如果没有名为N的成员存在且可访问,则查找不会产生匹配,并且不会对以下步骤进行计算。我很好奇,是否有任何情况下,这种行为会非常有用,并且会被故意分解?至少,C#设计团队肯定有什么想法。对于熟悉其他OOP语言的人来说,比如Java(包括我),这种行为是不透明的,可能被不公平地认为是不合理的。事实上,我找到了原因。它还存在于一个重复的堆栈溢出问题中。