方法隐藏如何在C#中起作用? (第二部分)

时间:2009-04-02 16:18:12

标签: c# inheritance compiler-construction method-hiding

以下程序打印

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 个答案:

答案 0 :(得分: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) - 即使编译器知道匹配方法的参数类型为int,参数类型为int,并且转换为{{1} }} int比从intint的转换“更好”,事实上只有double方法最初声明 Foo(double)表示编译器忽略Derived

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

答案 1 :(得分: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签名时,它会有自己的方法签名。

我不确定我是否在解释清楚但好的问题。

使用new:

A和B获得相同的Print方法实现。

使用覆盖:

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

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

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

答案 2 :(得分:1)

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

它的主旨是:

  

... C#编译器将首先尝试制作   该调用与版本兼容   最初声明的[functionName]   [派生类]。覆盖方法不是   被视为在课堂上宣布,   它们是a的新实现   在基类上声明的方法。只要   如果C#编译器无法匹配   方法调用原始方法   [派生类]会尝试匹配通话   一个被覆盖的方法与相同   名称和兼容参数。

因为你在派生类上有一个新方法Print(I i),它匹配参数“c”,(因为c实现了我),该方法优先于“override”方法。

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

答案 3 :(得分:0)

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

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

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

希望这有助于您理解。如果我需要进一步克服任何问题,请告诉我......

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