• 深入剖析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进行技术的交流,最后的最后我想说的是微软因为我们而精彩。

    Stallman 先生认为最大的快乐是让自己发展的软件让大家来使用了!

  • 相关阅读:
    两数之和
    IntelliJ IDEA为类和方法自动添加注释
    IDEA main 函数的快捷键
    Mac终端开启代理
    Pycharm节能模式
    使用正则表达式替换构造字典
    使用代理爬取微信文章
    利用 Scrapy 爬取知乎用户信息
    Scrapy选择器的用法
    Scrapy命令行基本用法
  • 原文地址:https://www.cnblogs.com/Jimmy009/p/2349374.html
Copyright © 2020-2023  润新知