.Net学习难点讨论系列2 – 细说C#中new关键字与多态

今天去面试,被问到C#中的new关键字,看了那么多的书对new关键字还是有一定认识,回来又把new复习了一遍,发现了许多以前还不知道的细节。

C#中有两处地方用到new关键字,第一处也是最常见的一处是用在调用构造函数的时候,这种情况也是大家见的最多的一种。另一处是用在派生类中,作用有隐藏成员,切断继承关系等,相信第二处的用法大家明显要比第一处生疏。

对于new隐藏成员的作用,往往是出于使用了一个第三方类库,而你又无法获得这个类库的源代码,当你继承这个类库的某个类时,你需要重新实现其中的一个方法,而又需要与父类中的函数使用同样的函数,这是就需要在自定义的子类中把那个同名函数(或成员)加上new标记,从而隐藏父类中同名的成员。

许个例子来说:

 1 class A 
 2 { 
 3 public void Say() 
 4 { 
 5             … … 
 6 } 
 7 } 
 8  
 9 class B : A 
10 { 
11 public new void Say() 
12 { 
13 Console.WriteLine("B said"); 
14 } 
15 } 

上面两个类,假设A是你由第三方获得的一个类库中的一个类,你无法修改源代码去除A中的这个方法,从而在你的B类中你使用与基类方法同名的Say()时,编译器会给出警告,这是只需在你自己定义的Say方法前加一个new关键字隐藏基类成员即可。

    在隐藏了了基类成员之后,仍然有几种方法来访问基类中的同名方法。第一种使用base关键字(限于派生类内部),代码示例:

 1 class A 
 2 { 
 3 public void Say() 
 4 { 
 5 Console.WriteLine("A said"); 
 6 } 
 7 } 
 8  
 9 class B : A 
10 { 
11 public new void Say() 
12 { 
13 //调用基类被隐藏的同名方法 
14 base.Say(); 
15 Console.WriteLine("B said"); 
16 } 
17 } 

在类的外部,可以强制把子类的对象转型为父类的,然后调用父类方法

 1 class Program 
 2 { 
 3 static void Main(string[] args) 
 4 { 
 5 B a1 = new B(); 
 6 ((A)a1).Say(); 
 7 } 
 8 } 
 9  
10 class A 
11 { 
12 public void Say() 
13 { 
14 Console.WriteLine("A said"); 
15 } 
16 } 
17  
18 class B : A 
19 { 
20 public new void Say() 
21 { 
22 Console.WriteLine("B said"); 
23 } 
24 } 

以上Main()函数也可写成如下形式也可达到同样的效果,但原理不同,分析如下:

1 static void Main(string[] args) 
2 { 
3 A a1 = new B(); 
4 a1.Say(); 
5 } 

这段Main()函数中al虽然没有被转型为A但是,由于new切断继承的原因所以这里调用的Say()A类中定义的,用Reflactor反编译程序集可以看出这点。这也是为什么只能用override而不能用new来进行多态操作的原因关键就在这个new切断继承。说到多态Allen Lee文章中对多态的经历甚是经典,"多态就是使得你能够用一种统一的方式来处理一组各具个性却同属一族的不同个体的机制。"文章见此

其实隐藏基类实现与切断继承是一回事,Donis Marshall在他的Visual C# 2005中的一个例子说明了这一点,代码及分析如下:

 1 static void Main(string[] args) 
 2 { 
 3 YClass obj = new YClass(); 
 4 obj.MethodB(); 
 5 XClass obj_x = new XClass(); 
 6 obj_x.MethodB(); 
 7  
 8 Console.ReadLine(); 
 9 } 
10  
11 public class ZClass 
12 { 
13 public virtual void MethodA() 
14 { 
15 Console.WriteLine("ZClass.MethodA"); 
16 } 
17  
18 public virtual void MethodB() 
19 { 
20 MethodA(); 
21 } 
22 } 
23  
24 public class YClass : ZClass 
25 { 
26 public override void MethodA() 
27 { 
28 Console.WriteLine("YClass.MethodA"); 
29 } 
30 } 
31  
32 public class XClass : ZClass 
33 { 
34 public new void MethodA() 
35 { 
36 Console.WriteLine("XClass.MethodA"); 
37 } 
38 } 

YClass类的obj对象调用由ZClass继承来的MethodB()方法,在MethodB()调用哪个MethodA()的这个选择问题上,由于基类中MethodA()定义为一个虚方法,大多数情况下派生方法(override标识的)被调用,所以调用了YClass.MethodA()

XClass类的obj_x对象调用MethodB()方法,但是此时XClassnew修饰符隐藏了基类对MethodA()的实现。现在ZClass.MethodA()XClass.MethodA()互不相关。因此,编译器就不会将从父类继承来的实现委托给XClass.MethodA。因此,XClass.MethodB调用了ZClass.MethodA

new切断继承,使多态失效这个问题上,一个很小的示例就可以说明问题,如下:

 1 class Program 
 2 { 
 3 static void Main(string[] args) 
 4 { 
 5  
 6 A a1 = new B(); 
 7 A a2 = new C(); 
 8 a1.Say(); 
 9 a2.Say(); 
10  
11 Console.ReadLine(); 
12 } 
13 } 
14  
15 class A 
16 { 
17 public virtual void Say() 
18 { 
19 Console.WriteLine("A said"); 
20 } 
21 } 
22  
23 class B : A 
24 { 
25 public new void Say() 
26 { 
27 Console.WriteLine("B said"); 
28 } 
29 } 
30  
31 class C : A 
32 { 
33 public override void Say() 
34 { 
35 Console.WriteLine("C said"); 
36 } 
37 } 

B不使用new切断继承,而使用override实现多态的情况下,程序的输出应该是:

B said

C said

而用new切断继承后结果为:

A said

C said

其它问题(VC#2005技术内幕中讲到)

具有new修饰符的成员不能被重(当然就是没有new,在没标记virtual时也是不能被重写的),函数可以同时标记标记new virtual这样它可以在不影响隐藏父类的情况下实现一个向下的继承关系即被其子类重写,如下述代码:

 1 class A 
 2 { 
 3 public virtual void Say() 
 4 { 
 5 Console.WriteLine("A said"); 
 6 } 
 7 } 
 8  
 9 class B : A 
10 { 
11 public new virtual void Say() 
12 { 
13 Console.WriteLine("B said"); 
14 } 
15 } 
16  
17 class C : B 
18 { 
19 public override void Say() 
20 { 
21 Console.WriteLine("C said"); 
22 } 
23 } 

B隐藏了A中同名方法,但C重写了B的方法。当然BC也没有关系,所以在Main()中执行如下代码时:

1 A a1 = new B(); 
2 A a2 = new C(); 
3 a1.Say(); 
4 a2.Say(); 

输出为:

A said

A said

但执行:

1 B a1 = new B(); 
2 B a2 = new C(); 
3 a1.Say(); 
4 a2.Say(); 

输出为:

B said

C said

基类的字段和静态成员无法在派生类中被重写。然而它们都可以使用new修饰符来进行隐藏。下面例子中,ZClass有一个静态字段和方法,用于记录ZClass的实例数。YClass继承了ZClass并隐藏了基类型的两个静态成员。新成员计算YClass的实例的个数。因此,两个计数同时发生在基类和派生类的计数器上。代码如下:

 1 class Program 
 2 { 
 3 static void Main(string[] args) 
 4 { 
 5 ZClass obj1 = new ZClass(); 
 6 YClass obj2 = new YClass(); 
 7 YClass obj3 = new YClass(); 
 8 ZClass.DisplayCounter(); 
 9 YClass.DisplayCounter(); 
10  
11 Console.ReadLine(); 
12 } 
13 } 
14  
15 public class ZClass 
16 { 
17 public ZClass() 
18 { 
19 ++count; 
20 } 
21  
22 public static int count = 0; 
23  
24 public static void DisplayCounter() 
25 { 
26 Console.Write("ZClass count:"); 
27 Console.WriteLine(count); 
28 } 
29 } 
30  
31 public class YClass : ZClass 
32 { 
33 public YClass() 
34 { 
35 ++count; 
36 } 
37  
38 private new static int count = 0; 
39 public new static void DisplayCounter() 
40 { 
41 Console.Write("YClass count:"); 
42 Console.WriteLine(count); 
43 } 
44 } 

其输出:

ZClass count:3

YClass count:2

比较如下代码,就可以看出new的作用:

 1 static void Main(string[] args) 
 2 { 
 3 ZClass obj1 = new ZClass(); 
 4 YClass obj2 = new YClass(); 
 5 YClass obj3 = new YClass(); 
 6 ZClass.DisplayCounter(); 
 7 YClass.DisplayCounter(); 
 8  
 9 Console.ReadLine(); 
10 } 
11 } 
12  
13 public class ZClass 
14 { 
15 public ZClass() 
16 { 
17 ++count; 
18 } 
19  
20 public static int count = 0; 
21  
22 public static void DisplayCounter() 
23 { 
24 Console.Write("ZClass count:"); 
25 Console.WriteLine(count); 
26 } 
27 } 
28  
29 public class YClass : ZClass 
30 { 
31 public YClass() 
32 { 
33 ++count; 
34 } 
35  
36 public new static void DisplayCounter() 
37 { 
38 Console.Write("YClass count:"); 
39 Console.WriteLine(count); 
40 } 
41 } 

其输出:

ZClass count:5

YClass count:5

还有一点,new可以切断子类对父类的方法的继承,但不能切断这两者同时对一个接口方法的实现,有关这个问题的详细说明以及关于new的详解,推荐Allen Lee的这篇文章

原文地址:https://www.cnblogs.com/lsxqw2004/p/1352856.html