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

打印以下程序

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(" + cA() + "," + cB() + ")"); } } public class B : A { public new void Print(C c) { Console.WriteLine("B:C(" + cA() + "," + cB() + ")"); } public void Print(I i) { Console.WriteLine("B:I(" + iA() + ")"); } } 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) 

为什么?

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

有效地(稍微简化),编译器首先查看表达式(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类型,并且从intint的转换比从intint的转换“更好” double ,事实上只有Foo(double)方法最初在Derived 声明意味着编译器忽略Foo(int)

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

好的,所以

  public new void Print(C c) { Console.WriteLine("B:C(" + cA() + "," + cB() + ")"); } public void Print(I i) { Console.WriteLine("B:I(" + iA() + ")"); } 

这声明了一种新的打印方法。 现在因为Binheritance自A,你只需要两次调用新方法。 当您覆盖该方法时,这会在您调用A时更改方法签名,但是当您调用B签名时,它会有自己的方法签名。

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

使用新的:

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

使用覆盖:

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

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

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

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

它的要点是这样的:

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

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

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

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

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

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

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

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