• [你必须知道的.NET] 第六回:深入浅出关键字base和this


    本文将介绍以下内容:

    • 面向对象基本概念
    • base关键字深入浅出
    • this关键字深入浅出

     

    1. 引言

    new关 键字引起了大家的不少关注,尤其感谢Anders Liu的 补充,让我感觉博客园赋予的交流平台真的无所不在。所以,我们就有必要继续这个话题,把我认为最值得关注的关键字开展下去,本文的重点是访问关键字(Access Keywords):base和this。虽然访问关键字不是很难理解的话题,我们还是有可以深入讨论的地方来理清思路。还是老办法,我的问 题先列出来, 您是否做好了准备。

    1. 是否可以在静态方法中使用base和this,为什么?
    2. base常用于哪些方面?this常 用于哪些方面?
    3. 可以base访 问基类的一切成员吗?
    4. 如果有三层或者更多继承,那么最下级派生类的base指向那一层呢?例如.NET体系中,如果以base访问,则应该是直接父类实例呢,还是最高层类实例呢?
    5. 以base和this应用于构造函数时,继承类对象实例化的执行顺序如何? 

    2. 基本概念

    base和this在C#中被归于访问关键字,顾名思义,就是用 于实现继承机制的访问操作,来满足对对象成员的访问,从而为多态机制提供更加灵活的处理方式。 

    2.1 base关键字

    其用于在派生类中实现对 基类公有或者受保护成员的访问,但是只局限在构造函数、实例方法和实例属性访问器中,MSDN中小 结的具体功能包括:

    • 调用基类上已被其他方法重写的方法。
    • 指定创建派生类实例时应调用的基类构造函数。 

    2.2 this关键字

    其用于引用类的当前实 例,也包括继承而来的方法,通常可以隐藏this,MSDN中 的小结功能主要包括:

    • 限定被相似的名称隐藏的成员
    • 将对象作为参数传递到其他方法
    • 声明索引器 

    3. 深入浅出

    3.1 示例为上

    下面以一个小示例来综合 的说明,base和this在访问操作中的 应用,从而对其有个概要了解,更详细的规则和深入我们接着阐述。本示例没有完全的设计概念,主要用来阐述base和this关键字的使用要点和难点阐述,具体的如下: 

    base和this示例
    using System;

    namespace Anytao.net.My_Must_net
    {
        
    public class Action
        {
            
    public static void ToRun(Vehicle vehicle)
            {
                Console.WriteLine("{0} is running.", vehicle.ToString());
            }
        }

        
    public class Vehicle
        {
            
    private string name;
            
    private int speed;
            
    private string[] array = new string[10];

            
    public Vehicle()
            {
            }

            
    //
    限 定被相似的名称隐藏的成员
     speed)
            {
                
    .name = name;
                
    .speed = speed;
            }

            
     ShowResult()
            {
                Console.WriteLine("The top speed of {0} is {1}.", name, speed);
            }

            
     Run()
            {
                
    传递当前实例参数
    );
            }



            
    ,这样就可以像数组一样来索引对象
     param]
            {
                
     array[param];}
                
    {array[param] = value;}
            }
        }

        
     Car: Vehicle
        {
            
    实现,基类首先被调用
            //
    指定创建派生类实例时应调用的基类构造函数
     Car()
                : 
    ("Car", 200)
            { }

            
     speed)
                : 
    ()
            { }

            
     ShowResult()
            {
                
    调用基类上已被其他方法重写的方法
    .ShowResult();
                Console.WriteLine("It's a car's result.");
            }

        }

        
     Audi : Car
        {
            
     Audi()
                : 
    ("Audi", 300)
            { }

            
     speed)
                : 
    ()
            { 
            }

            
     ShowResult()
            {
                
    只能继承其直接基类成员
    .ShowResult();
                
    .Run();
                Console.WriteLine("It's audi's result.");
            }
        }

        
     BaseThisTester
        {
            
    [] args)
            {
                Audi audi = 
     Audi();
                audi[1] = "A6";
                audi[2] = "A8";
                Console.WriteLine(audi[1]);
                audi.Run();
                audi.ShowResult();
            }
        }
    }

    3.2 示例说明

    上面的示例基本包括了base和this使用的所有基本功能演示,具体的说 明可以从注释中得到解释,下面的说明是对注释的进一步阐述和补充,来说明在应用方面的几个要点:

    1. base常用于,在派生类对象初始化时和基类进行通信。
    2. base可以访问基类的公有成员和受保护成员,私有成员是不可访问的。
    3. this指代类对象本身,用于访问本类的所有常量、字段、属性和方法成 员,而且不管访问元素是任何访问级别。因为,this仅仅局限于对象内部,对象外部是无法看到的, 这就是this的基本思想。另外,静态成员不是对象的一部分,因此不能在静态方法中引用this。
    4. 在 多层继承中,base可 以指向的父类的方法有两种情况:一是有重载存在的情况下,base将指向直接继承的父类成员的方 法,例如Audi类中的 ShowResult方 法中,使用base访问的将是Car.ShowResult()方 法,而不能访问Vehicle.ShowResult()方法; 而是没有重载存在的情况下,base可以指向任何上级父类的公有或者受保护方法,例如Audi类 中,可以使用base访问基类Vehicle.Run() 方法。这些我们可以使用ILDasm.exe,从IL代码中得到答案。 

    .method public hidebysig virtual instance void 
            ShowResult() cil managed
    {
      
    // 代码大小       27 (0x1b)
      .maxstack  8
      IL_0000:  nop
      IL_0001:  ldarg.0
      
    //base调用父类成员
      IL_0002:  call       instance void Anytao.net.My_Must_net.Car::ShowResult()
      IL_0007:  nop
      IL_0008:  ldarg.0
      
    //base调用父类成员,因为没有实现Car.Run(),所以指向更 高级父类
      IL_0009:  call       instance void Anytao.net.My_Must_net.Vehicle::Run()
      IL_000e:  nop
      IL_000f:  ldstr      "It's audi's result."
      IL_0014:  call       
    void [mscorlib]System.Console::WriteLine(string)
      IL_0019:  nop
      IL_001a:  ret
    // end of method Audi::ShowResult

    3.3 深入剖析 

    如果有三次或者更多继 承,那么最下级派生类的base指向那一层呢?例如.NET体 系中,如果以base访问,则应该是直接父类实例呢,还是最高层类实例呢?

    首先我们有必要了解类创 建过程中的实例化顺序,才能进一步了解base机制的详细执行过程。一般来说,实例化过程首先要先 实例化其基类,并且依此类 推,一直到实例化System.Object为止。因此,类实例化,总 是从调用System.Object.Object()开始。因此示例中的类 Audi的实例化过程大概可以小结为以下顺序执行,详细可以参考示例代码分析。

    1. 执行System.Object.Object();
    2. 执行Vehicle.Vehicle(string name, int speed);
    3. 执行Car.Car();
    4. 执行Car.Car(string name, int speed);
    5. 执行Audi.Audi();
    6. 执行Audi.Audi(string name, int speed)

    我们在充分了解其实例化 顺序的基础上就可以顺利的把握base和this在 作用于构造函数时的执行情况,并进一步了解其基本功能细节。

    下面更重要的分析则是, 以ILDASM.exe工具为基础来分析IL反 编译代码,以便更深层次的了解执行在base和this背 后的应用实质,只有这样我们才能说对技术有了基本的剖析。

    Main方 法的执行情况为:

    IL分析base和this执行
    .method public hidebysig static void  Main(string[] args) cil managed
    {
      .entrypoint
      
    // 
    代 码大小       61 (0x3d)
      .maxstack  3
      .locals init (
     Anytao.net.My_Must_net.Audi V_0)
      IL_0000:  nop
      
    指 令创建新的对象,并调用构造函数初始化
     Anytao.net.My_Must_net.Audi::.ctor()
      IL_0006:  stloc.0
      IL_0007:  ldloc.0
      IL_0008:  ldc.i4.1
      IL_0009:  ldstr      "A6"
      IL_000e:  callvirt   instance 
     Anytao.net.My_Must_net.Vehicle::set_Item(int32,
                                                                                  
    )
      IL_0013:  nop
      IL_0014:  ldloc.0
      IL_0015:  ldc.i4.2
      IL_0016:  ldstr      "A8"
      IL_001b:  callvirt   instance 
     Anytao.net.My_Must_net.Vehicle::set_Item(int32,
                                                                                  
    )
      IL_0020:  nop
      IL_0021:  ldloc.0
      IL_0022:  ldc.i4.1
      IL_0023:  callvirt   instance 
     Anytao.net.My_Must_net.Vehicle::get_Item(int32)
      IL_0028:  call       
    )
      IL_002d:  nop
      IL_002e:  ldloc.0
      IL_002f:  callvirt   instance 
     Anytao.net.My_Must_net.Vehicle::Run()
      IL_0034:  nop
      IL_0035:  ldloc.0
      
    最 终调用的是最高级父类Vehicle的方法,
      //
    而不是直接父类Car.ShowResult()方法,这是应该关注的
     Anytao.net.My_Must_net.Vehicle::ShowResult()
      IL_003b:  nop
      IL_003c:  ret
    // end of method BaseThisTester::Main


     因此, 对重写父类方法,最终指向了最高级父类的方法成员。

     4. 通用规则

    • 尽量少用或者不用base和this。除了决议子类的名称冲突和在一个构造函数中调用其他的构造函数之外,base和this的使用容易引起不必要的结果。
    • 在静态成员中使用base和this都是不允许的。原因是,base和this访问的都是类的实例,也就是对象,而静态成员只能由类来访问,不能由对象来访问。
    • base是为了实现多态而设计的。
    • 使用this或base关键字只能指定一个构造函数,也就是说不可同时将this和base作用在一个构造函数上。
    • 简单的来说,base用 于在派生类中访问重写的基类成员;而this用于访问本类的成员,当然也包括继承而来公有和保护成 员。
    • 除了base, 访问基类成员的另外一种方式是:显示的类型转换来实现。只是该方法不能为静态方法。

    5. 结论

    base和this关键字,不是特别难于理解的内容,本文之所以将其作为系列的主题,除了对其应用规则做以小结之外,更 重要的是在关注其执行细节的基础上,对语言背景建立更清晰的把握和分析,这些才是学习和技术应用的根本所在,也是.NET技 术框架中本质诉求。对学习者来说,只有从本质上来把握概念,才能在变化非凡的应用中,一眼找到答案。 

    言归正传,开篇的几个题 目,不知读者是否有了各自的答案,我们不妨畅所欲言,做更深入的讨论,以便揭开其真实的面纱。
  • 相关阅读:
    Java面試題(实用性高)
    索引的概述?
    给Eclipse提速的7个技巧
    ETL数据仓库
    实用SQL语句大全
    考证
    PL/SQL 程序
    eclipse
    httpd.conf详解,因为php始终报fileinfo扩展无法加载的错
    dockerfile创建镜像及容器
  • 原文地址:https://www.cnblogs.com/top5/p/1665188.html
Copyright © 2020-2023  润新知