13

以下程序打印

A:C(A,B)
B:C(A,B)

(正如它应该)

public interface I
{
    string A();
}

public class C : I
{
    public string A()
    {
        return "A";
    }

    public string B()
    {
        return "B";
    }
}

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

public class B : 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() + ")");
    }
}

class Program
{
    public static void Main(string[] args)
    {
        A a = new A();
        B b = new B();
        C c = new C();
        a.Print(c);
        b.Print(c);
    }
}

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

    public override void Print(C c)

突然程序开始打印:

A:C(A,B)
B:I(A)

为什么?

4

4 回答 4

8

这与如何解决重载方法有关。

实际上(有些简化),在这种情况下,编译器首先查看表达式 (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)- 即使编译器知道有一个参数为 type 的匹配方法int,并且参数是 type int,并且从 to 的转换比从 tointint转换“更好” ,事实上只有方法最初在表示编译器忽略.intdoubleFoo(double)DerivedFoo(int)

这是非常令人惊讶的 IMO。我可以理解为什么如果Derived不覆盖会出现这种情况Foo- 否则在基类中引入一个新的、更具体的方法可能会意外地改变行为 - 但显然这里Derived知道Base.Foo(int)正在覆盖它。这是我认为 C# 设计者做出错误决定的(相对少数)点之一。

于 2009-04-02T16:27:04.643 回答
1

好的,所以

    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 获得相同的 Print Method 实现。

使用覆盖:

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

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

    public void Print(I i)
    {
        Console.WriteLine("B:I(" + i.A() + ")");
    }
于 2009-04-02T16:26:51.927 回答
1

这是一个很好的问题。
所有答案都可以在这里找到:http: //msdn.microsoft.com/en-us/library/6fawty39 (VS.80).aspx

它的要点是这样的:

... C# 编译器将首先尝试使调用与最初在 [派生类] 上声明的 [functionName] 版本兼容。覆盖方法不被视为在类上声明,它们是在基类上声明的方法的新实现。只有当 C# 编译器无法将方法调用匹配到 [派生类] 上的原始方法时,它才会尝试将调用匹配到具有相同名称和兼容参数的重写方法。

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

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

于 2009-04-02T16:36:07.193 回答
0

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

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

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

希望这能帮助你理解。让我知道是否需要进一步澄清任何要点...

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

于 2009-04-02T16:37:36.660 回答