深入剖析new override和virtual关键字

    在老师上课期间,老师只不过很简单的介绍了一下new、override、virtual这几个关键字。上课根本就没有消化,直到自己在看博客园中王涛写的《你必须知道的.Net》和网上一些资料的后,才弄明白了其中的含义。我想并不是每个人都有机会和心思去读一本好几百页的书的,所以肯定还有很多初学者和像我一样一开始不懂的人。而我在这里也只不过分享一下自己的体会。如果有什么不对,请高手指出,我将做修改。

Vitual:

     在MSDN上面的解释为virtual 关键字用于修饰方法、属性、索引器或事件声明,并使它们可以在派生类中被重写。例如,此方法可被任何继承它的类重写。(MSDN版本为MSDN Library for Visual Studio 2008 简体中文,如果下面没有特殊说明就说明参考的地方是一样的)。

New:

     在 C# 中,new 关键字可用作运算符、修饰符或约束。

     New在定义的时候有3中用法。

     new 运算符 用于创建对象和调用构造函数。

     new 修饰符 用于向基类成员隐藏继承成员。

  new 约束 用于在泛型声明中约束可能用作类型参数的参数的类型。

  而今天我所讲的是其中第二种修饰符。

Override:

  要扩展或修改继承的方法、属性、索引器或事件的抽象实现或虚实现,必须使用 override 修饰符。

 

  上面只不过是大体的介绍了3个关键字的含义。接着我们来看一段代码让我们彻底的弄明白这3个关键字的用法。

 1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5
6 namespace 你必须知道的NET.博客园
7 {
8 class 文章1代码
9 {
10 public static void Main()
11 {
12 Base num = new Base();
13 num.ShowNumber();
14 Derived intNum = new Derived();
15 intNum.ShowNumber();
16 intNum.ShowInfo();
17
18 Base number = new Derived();
19 number.ShowInfo();
20 number.ShowNumber();
21
22 Console.ReadKey();
23 }
24 }
25
26
27
28 class Base {
29 public static int i = 123;
30
31 public virtual void ShowInfo() {
32 Console.WriteLine("base calss ----");
33 }
34
35 public virtual void ShowNumber(){
36 Console.WriteLine(i.ToString());
37 }
38 }
39
40 class Derived : Base {
41 new public static int i = 456;
42
43 public new virtual void ShowInfo() {
44 Console.WriteLine("Derived class");
45 }
46
47 public override void ShowNumber()
48 {
49 Console.WriteLine("Base number is {0}",Base.i.ToString());
50 Console.WriteLine("New number is {0}",i.ToString());
51 }
52 }
53 }

  首先我们从内存分配的角度来看这段代码,我们先从Managed Heap中分析。如果对内存概念不理解的话,可以先看一下内存分配(堆栈和托管堆)的知识,这样可以更快的理解我所讲的内容。

 

  这个是我画的内存分配图,画的有点乱(我不知道博客园那些高手用的是什么画图工具,画的比我好多了,知道的请给我留言),

首先我们从类定义出发,Base这个类为:

 class Base {
public static int i = 123;

public virtual void ShowInfo() {
Console.WriteLine("base calss ----");
}

public virtual void ShowNumber(){
Console.WriteLine(i.ToString());
}
}

  我们定义这个类的时候在方法中都添加了virtual这个关键字,为后面的继承实现多态提供可能。

  重点是看我们定义的第二个类Derived类,定义如下:

 1  class Derived : Base {
2 new public static int i = 456;
3
4 public new virtual void ShowInfo() {
5 Console.WriteLine("Derived class");
6 }
7
8 public override void ShowNumber()
9 {
10 Console.WriteLine("Base number is {0}",Base.i.ToString());
11 Console.WriteLine("New number is {0}",i.ToString());
12 }
13 }
 

  首先我们看ShowInfo()这个方法,方法中的关键字为new,根据定义我们基类中也有同样的方法,那么我们就隐藏了基类中的ShowInfo()这个方法,但事实上基类中的ShowInfo()还是存在的。接着我们又定义了ShowNumber()这个方法,这个方法的关键字是override,根据定义我们首先继承基类中可以被继承的成员和函数,然后把基类中原来的ShowNumber()函数找到,然后改写里面的内容。可能有些人还没有明白,那我举个例子,如果有个人给了你一张写着一篇文章的纸,相当于我们这里的基类,然后你拿到这篇文章看,相当于继承这个概念,你觉得好的地方可以原封不动的保留着,相当于继承,有些不怎么好的句子你可以用橡皮擦擦掉改成自己的优美的句子,橡皮擦擦掉后我们在也看不到痕迹了,这里相当于我们说的override这个关键字;还有一些不怎么好的句子,我们只不过用一些修改符号来改成自己的句子,在原稿中原来的句子还是存在的,这里相当于我们说的new这个关键字。

  在主函数中我们可以看到一开始我们首先定义了一个Base类型的变量num,然后再托管堆中分配一个Base的空间(空间的状况可以参考上面那张图中指向为1的那块部分),就这样一句Base num = new Base()执行好了,然后我们调用num变量的ShowNumber()这个方法,从图中可以看出,应该是123.紧接着我们在后面的声明了Derived类型的intNum这个变量,然后指向了Derived()这块托管堆的地址。这样又一句Deriverd intNum = new Derived()在内存中分配好了,接着我们要开始调用它们的方法,showNumber()和ShowInfo()这两个方法。同理可以从图中看出应该输出为Base number is 123 New number is 456和Derived class。

  接着是第二个难点了,我们Base number = new Derived();很多新手对上面的句子还是掌握的,但是到了这句,就糊里糊涂的当做死记记住了。现在我来告诉你一种简单的记法,我们要先从后面的那部分开始,new Derived() 然后加个is 然后再加前面的变量类型,然后成的句子可以写成这样Base number = new Derived() is Base我是把is读为属于(这个是我自己为了方便记忆,并没有什么依据,所以这里我只不过作为一个参考),要是属于Base这个类那么这种定义是合法的,但是如果反过来定义,那就不行了,如:Derived test = new Base()当编译的时候就会报错。

  定义的事我已经讲得很清楚了,接着我们来看看内存的分配情况吧,我们定义了一个number这个变量,这个变量指向Derived()这个类,但是问题来了,两个类型不一致呀,听我慢慢道来,其实前面的变量只不过是一种范围,告诉内存从Derived这个类开始我要移动多少的位子,要是移不到的位子,就是读不出来的位子了。好了我们现在来看ShowInfo()这个函数,以为我们在派生类中用的是new这个关键字,所有在派生类中还是保留着这个基类的ShowInfo()(你不知道的话在重新看看前面的),而我们在派生类中新添加进去的ShowInfo()这个方法对于number移动不了这么远的位子,好了我想你应该明白了输出的语句是什么了,没错就是”Base Class ----”,接着看showNumber()这个函数,因为这个函数在基类的ShowNumber()函数上进行了修改,而number这个变量可以知道基类中任何变量和函数的内容,而知道的内容被改了,所有就只能读到被改得内容了。可能有些人会问,在这中不是改了方法,那应该在内存中写在后面或者前面的内存不够写的问题,其实真正的内存的分配不是我这样的,类似于下面那样图,我这里只不过是为了方便读者能理解所有才这么画,如果有误导,请见谅。

 

  这个也是大概的类的定义,TypeHandle这个是方法的指针指向所对应的方法的地址。

 

  接着我们在来看看IL代码,然复习一遍今天所写的代码吧.

IL代码:

 1 .method public hidebysig static void  Main() cil managed
2 {
3 .entrypoint
4 // 代码大小 61 (0x3d)
5 .maxstack 1
6 .locals init ([0] class '你必须知道的NET.博客园'.Base num,
7 [1] class '你必须知道的NET.博客园'.Derived intNum,
8 [2] class '你必须知道的NET.博客园'.Base number)
9 IL_0000: nop
10 IL_0001: newobj instance void '你必须知道的NET.博客园'.Base::.ctor()
11 IL_0006: stloc.0
12 IL_0007: ldloc.0
13 IL_0008: callvirt instance void '你必须知道的NET.博客园'.Base::ShowNumber()
14 IL_000d: nop
15 IL_000e: newobj instance void '你必须知道的NET.博客园'.Derived::.ctor()
16 IL_0013: stloc.1
17 IL_0014: ldloc.1
18 IL_0015: callvirt instance void '你必须知道的NET.博客园'.Base::ShowNumber()
19 IL_001a: nop
20 IL_001b: ldloc.1
21 IL_001c: callvirt instance void '你必须知道的NET.博客园'.Derived::ShowInfo()
22 IL_0021: nop
23 IL_0022: newobj instance void '你必须知道的NET.博客园'.Derived::.ctor()
24 IL_0027: stloc.2
25 IL_0028: ldloc.2
26 IL_0029: callvirt instance void '你必须知道的NET.博客园'.Base::ShowInfo()
27 IL_002e: nop
28 IL_002f: ldloc.2
29 IL_0030: callvirt instance void '你必须知道的NET.博客园'.Base::ShowNumber()
30 IL_0035: nop
31 IL_0036: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
32 IL_003b: pop
33 IL_003c: ret
34 } // end of method '文章1代码'::Main

  首先我们申明了一个公共的有CLR来自动管理的不可以继承的Main()方法。然后定义了3个变量,第一个变量调用Base::.ctor()这个构造函数来初始化自己的变量,然手调用Base::ShowNumber()这个方法。接着第二个变量调用了Derived::.ctor()这个构造函数来初始化自己的方法,然手调用Base::ShowNumber()(被重写过,但是方法是属于Base这个类)和.Derived::ShowInfo()(方法是派生类的方法,基类同名的方法被派生类所隐藏掉了),最后一个变量调用的是Derived::.ctor()这个构造函数,然后调用Base::ShowInfo()(Base的ShowInfo()方法,因为是隐藏的但是确确实实是存在的而且找的到的)和Base::ShowNumber()(被派生类重新了,内容改变了)。

  最后的运行结果为:

 

  这个是我对3个关键字的总结,希望对那些C#初学者,和c#学习迷茫的人能有所帮助。

 

  最后我也希望那些喜欢编程的,喜欢C#的的程序员们和那些业余爱好者,能跟我一起成长。QQ:371002515,欢迎你们加我QQ进行技术的交流,最后的最后我想说的是微软因为我们而精彩。

 

 

posted @ 2012-02-13 14:27  唯吴独尊  阅读(1917)  评论(11编辑  收藏  举报