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

本文深入探讨了C#中new关键字的多种用途,包括构造函数调用、成员隐藏及切断继承关系等,并通过实例展示了如何在派生类中正确使用new关键字。

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

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

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

许个例子来说:

class A

{

public void Say()

{

            … …

}

}

 

class B : A

{

public new void Say()

{

Console .WriteLine("B said" );

}

}

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

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

class A

{

public void Say()

{

Console .WriteLine("A said" );

}

}

 

class B : A

{

public new void Say()

{

// 调用基类被隐藏的同名方法

base .Say();

Console .WriteLine("B said" );

}

}

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

class Program

{

static void Main(string [] args)

{

B a1 = new B ();

((A )a1).Say();

}

}

 

class A

{

public void Say()

{

Console .WriteLine("A said" );

}

}

 

class B : A

{

public new void Say()

{

Console .WriteLine("B said" );

}

}

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

static void Main(string [] args)

{

A a1 = new B ();

a1.Say();

}

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

 

 

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

static void Main(string [] args)

{

YClass obj = new YClass ();

obj.MethodB();

XClass obj_x = new XClass ();

obj_x.MethodB();

 

Console .ReadLine();

}

 

public class ZClass

{

public virtual void MethodA()

{

Console .WriteLine("ZClass.MethodA" );

}

 

public virtual void MethodB()

{

MethodA();

}

}

 

public class YClass : ZClass

{

public override void MethodA()

{

Console .WriteLine("YClass.MethodA" );

}

}

 

public class XClass : ZClass

{

public new void MethodA()

{

Console .WriteLine("XClass.MethodA" );

}

}

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

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

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

class Program

{

static void Main(string [] args)

{

 

A a1 = new B ();

A a2 = new C ();

a1.Say();

a2.Say();

 

Console .ReadLine();

}

}

 

class A

{

public virtual void Say()

{

Console .WriteLine("A said" );

}

}

 

class B : A

{

public new void Say()

{

Console .WriteLine("B said" );

}

}

 

class C : A

{

public override void Say()

{

Console .WriteLine("C said" );

}

}

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

B said

C said

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

A said

C said

 

 

 

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

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

class A

{

public virtual void Say()

{

Console .WriteLine("A said" );

}

}

 

class B : A

{

public new virtual void Say()

{

Console .WriteLine("B said" );

}

}

 

class C : B

{

public override void Say()

{

Console .WriteLine("C said" );

}

}

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

A a1 = new B ();

A a2 = new C ();

a1.Say();

a2.Say();

输出为:

A said

A said

但执行:

B a1 = new B ();

B a2 = new C ();

a1.Say();

a2.Say();

输出为:

B said

C said

 

 

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

class Program

{

static void Main(string [] args)

{

ZClass obj1 = new ZClass ();

YClass obj2 = new YClass ();

YClass obj3 = new YClass ();

ZClass .DisplayCounter();

YClass .DisplayCounter();

 

Console .ReadLine();

}

}

 

public class ZClass

{

public ZClass()

{

++count;

}

 

public static int count = 0;

 

public static void DisplayCounter()

{

Console .Write("ZClass count:" );

Console .WriteLine(count);

}

}

 

public class YClass : ZClass

{

public YClass()

{

++count;

}

 

private new static int count = 0;

public new static void DisplayCounter()

{

Console .Write("YClass count:" );

Console .WriteLine(count);

}

}

其输出:

ZClass count:3

YClass count:2

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

static void Main(string [] args)

{

ZClass obj1 = new ZClass ();

YClass obj2 = new YClass ();

YClass obj3 = new YClass ();

ZClass .DisplayCounter();

YClass .DisplayCounter();

 

Console .ReadLine();

}

}

 

public class ZClass

{

public ZClass()

{

++count;

}

 

public static int count = 0;

 

public static void DisplayCounter()

{

Console .Write("ZClass count:" );

Console .WriteLine(count);

}

}

 

public class YClass : ZClass

{

public YClass()

{

++count;

}

 

public new static void DisplayCounter()

{

Console .Write("YClass count:" );

Console .WriteLine(count);

}

}

其输出:

ZClass count:5

YClass count:5

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值