方法隐藏在C#中是如何工作的?(第二部分)

方法隐藏在C#中是如何工作的?(第二部分),c#,inheritance,compiler-construction,method-hiding,C#,Inheritance,Compiler Construction,Method Hiding,下面的程序将打印 A:C(A,B) B:C(A,B) (应该如此) 但是,如果我将类B中的关键字“new”更改为“override”,如下所示: public override void Print(C c) 突然,程序开始打印: A:C(A,B) B:I(A) 为什么?好的,那么 public new void Print(C c) { Console.WriteLine("B:C(" + c.A() + "," + c.B() + ")");

下面的程序将打印

A:C(A,B)
B:C(A,B)
(应该如此)

但是,如果我将类B中的关键字“new”更改为“override”,如下所示:

    public override void Print(C c)
突然,程序开始打印:

A:C(A,B)
B:I(A)
为什么?

好的,那么

    public new void Print(C c)
    {
        Console.WriteLine("B:C(" + c.A() + "," + c.B() + ")");
    }

    public void Print(I i)
    {
        Console.WriteLine("B:I(" + i.A() + ")");
    }
这为打印声明了一个新方法。现在,由于B继承了A,您将简单地调用新方法两次。当您覆盖该方法时,当您调用A时,它会更改方法签名,但当您调用B签名时,它会有自己的方法签名

我不确定我是否解释了一个清楚但好的问题

使用新的:

A和B得到了打印方法的相同实现

使用覆盖:

A与B具有不同的方法签名,因为您只在A中更改了B中的方法签名

使用新版本基本上忽略了这一点:

    public void Print(I i)
    {
        Console.WriteLine("B:I(" + i.A() + ")");
    }

这与重载方法的解析方式有关

实际上(稍微简化),编译器首先查看在本例中声明的表达式(B)类型,并查找首先在该类型中声明的候选方法。如果有任何合适的方法(即所有参数都可以转换为方法的参数类型),那么它不会查看任何父类型。这意味着,如果初始声明在父类型中,则重写的方法无法查看派生类型中是否有任何“新声明”的适当方法

下面是一个稍微简单的示例:

using System;

class Base
{
    public virtual void Foo(int x)
    {
        Console.WriteLine("Base.Foo(int)");
    }
}

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

    public void Foo(double d)
    {
        Console.WriteLine("Derived.Foo(double)");
    }
}

class Test
{
    static void Main()
    {
        Derived d = new Derived();
        d.Foo(10);
    }
}
这将打印
Derived.Foo(double)
——即使编译器知道存在一个匹配方法,该方法的参数类型为
int
,参数类型为
int
,并且从
int
int
的转换比从
int
double
的转换“更好”,只有
Foo(double)
方法最初在
Derived
中声明,这意味着编译器忽略
Foo(int)


这在IMO中是非常令人惊讶的。我可以理解为什么
Derived
没有覆盖
Foo
——否则在基类中引入一个新的、更具体的方法可能会意外地改变行为——但显然
Derived
这里知道
base.Foo(int)
,因为它正在覆盖它。这是我认为C#设计师做出错误决定的(相对较少的)一点。

这是一个很好的问题。
所有答案都可以在这里找到:

其要点如下:

…C#编译器将首先尝试 该调用与版本兼容 最初在上声明的[functionName]的 [派生类]。重写方法不可用 被认为是在类上声明的, 它们是 方法在基类上声明。只有 如果C#编译器无法匹配 方法调用上的原始方法 [派生类]是否会尝试匹配调用 使用相同的 名称和兼容参数

因此,因为在派生类上有一个与参数“c”匹配的新方法Print(I)(因为c实现了I),所以该方法优先于“override”方法


当您将方法标记为“new”时,它们都被认为是在派生类上实现的,而Print(cc)方法与参数“C”更为匹配,因此它具有优先权。

这至少是一个关于方法重载在C#中如何工作的问题。我想你在这里强调了一个有趣的情况

在第一种情况下(在方法上使用
new
关键字),编译器决定使用
Print
方法重载类型为C的参数,因为它的类型与传递的参数的类型完全相同(即不需要隐式转换)然而,如果编译器要选择采用I类型参数的
Print
方法,则需要隐式转换到接口I,换句话说,它选择了更“明显”的方法重载

在第二种情况下(使用方法上的
override
关键字),编译器决定使用类型为I的参数的
Print
重载,因为尽管您正在重写类B中的
Print(C)
方法重载,但它在父类A中有效地定义,从而使
Print(I)
方法重载实际上是最高级别的重载,因此也是最直接的重载,即编译器找到的第一个重载

希望这能帮助你理解。如果我需要进一步澄清任何问题,请告诉我


注意:如果我说编译器做了这些事情是错误的,那么请纠正我,尽管对于参数来说,无论是编译器还是CLR/JIT,这都没有什么区别,但是看起来是。

此外,如果您将
d
作为参数传递给一个需要
Base
的方法,您将得到
Derived.Foo(int)
(与编写
(d为基数)相同。Foo(10);
Main()
)中。虽然很清楚这是如何工作的,但我发现这个方法很臭,因此避免在所有情况下隐藏方法。
using System;

class Base
{
    public virtual void Foo(int x)
    {
        Console.WriteLine("Base.Foo(int)");
    }
}

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

    public void Foo(double d)
    {
        Console.WriteLine("Derived.Foo(double)");
    }
}

class Test
{
    static void Main()
    {
        Derived d = new Derived();
        d.Foo(10);
    }
}