方法隐藏如何在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
类型,并且从int
到int
的转换比从int
到int
的转换“更好” 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,它看起来都是如此。