[你必须知道的.NET] 第六回:深入浅出关键字---base和this
Author:王涛
Date:2007-5-4
©2007 Anytao.com ,原创作品,转贴请注明作者和出处。
本文将介绍以下内容:
- 面向对象基本概念
- base 关键字深入浅出
- this 关键字深入浅出
1. 引言
new 关键字引起了大家的不少关注,尤其感谢Anders Liu 的补充,让我感觉博客园赋予的交流平台真的无所不在。所以,我们就有必要继续这个话题,把我认为最值得关注的关键字开展下去,本文的重点是访问关键字(Access Keywords ):base 和this 。虽然访问关键字不是很难理解的话题,我们还是有可以深入讨论的地方来理清思路。还是老办法,我的问题先列出来,您是否做好了准备。
- 是否可以在静态方法中使用base 和this ,为什么?
- base 常用于哪些方面?this 常用于哪些方面?
- 可以base 访问基类的一切成员吗?
- 如果有三层或者更多继承,那么最下级派生类的base 指向那一层呢?例如.NET 体系中,如果以base 访问,则应该是直接父类实例呢,还是最高层类实例呢?
- 以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() { } // 限定被相似的名称隐藏的成员 public Vehicle(string name, int speed) { this.name = name; this.speed = speed; } public virtual void ShowResult() { Console.WriteLine("The top speed of {0} is {1}.", name, speed); } public void Run() { // 传递当前实例参数 Action.ToRun(this); } // 声明索引器,必须为this ,这样就可以像数组一样来索引对象 public string this[int param] { get{return array[param];} set{array[param] = value;} } } public class Car: Vehicle { // 派生类和基类通信,以base 实现,基类首先被调用 // 指定创建派生类实例时应调用的基类构造函数 public Car() : base("Car", 200) { } public Car(string name, int speed) : this() { } public override void ShowResult() { // 调用基类上已被其他方法重写的方法 base.ShowResult(); Console.WriteLine("It's a car's result."); } } public class Audi : Car { public Audi() : base("Audi", 300) { } public Audi(string name, int speed) : this() { } public override void ShowResult() { // 由三层继承可以看出,base 只能继承其直接基类成员 base.ShowResult(); base.Run(); Console.WriteLine("It's audi's result."); } } public class BaseThisTester { public static void Main(string[] args) { Audi audi = new Audi(); audi[1] = "A6"; audi[2] = "A8"; Console.WriteLine(audi[1]); audi.Run(); audi.ShowResult(); } } }
3.2 示例说明
上面的示例基本包括了base 和this 使用的所有基本功能演示,具体的说明可以从注释中得到解释,下面的说明是对注释的进一步阐述和补充,来说明在应用方面的几个要点:
- base 常用于,在派生类对象初始化时和基类进行通信。
- base 可以访问基类的公有成员和受保护成员,私有成员是不可访问的。
- this 指代类对象本身,用于访问本类的所有常量、字段、属性和方法成员,而且不管访问元素是任何访问级别。因为,this 仅仅局限于对象内部,对象外部是无法看到的,这就是this 的基本思想。另外,静态成员不是对象的一部分,因此不能在静态方法中引用this 。
- 在多层继承中,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 的实例化过程大概可以小结为以下顺序执行,详细可以参考示例代码分析。
- 执行System.Object.Object();
- 执行Vehicle.Vehicle(string name, int speed);
- 执行Car.Car();
- 执行Car.Car(string name, int speed);
- 执行Audi.Audi();
- 执行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 (class Anytao.net.My_Must_net.Audi V_0) IL_0000: nop // 使用newobj 指令创建新的对象,并调用构造函数初始化 IL_0001: newobj instance void 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 void Anytao.net.My_Must_net.Vehicle::set_Item(int32, string) IL_0013: nop IL_0014: ldloc.0 IL_0015: ldc.i4.2 IL_0016: ldstr "A8" IL_001b: callvirt instance void Anytao.net.My_Must_net.Vehicle::set_Item(int32, string) IL_0020: nop IL_0021: ldloc.0 IL_0022: ldc.i4.1 IL_0023: callvirt instance string Anytao.net.My_Must_net.Vehicle::get_Item(int32) IL_0028: call void [mscorlib]System.Console::WriteLine(string) IL_002d: nop IL_002e: ldloc.0 IL_002f: callvirt instance void Anytao.net.My_Must_net.Vehicle::Run() IL_0034: nop IL_0035: ldloc.0 //base.ShowResult 最终调用的是最高级父类Vehicle 的方法, // 而不是直接父类Car.ShowResult() 方法,这是应该关注的 IL_0036: callvirt instance void 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 技术框架中本质诉求。对学习者来说,只有从本质上来把握概念,才能在变化非凡的应用中,一眼找到答案。
言归正传,开篇的几个题目,不知读者是否有了各自的答案,我们不妨畅所欲言,做更深入的讨论,以便揭开其真实的面纱。