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

发布日期:2007.5.4 作者:Anytao

©2007 Anytao.com ,原创作品,转贴请注明作者和出处。

 

本文将介绍以下内容:

  • 面向对象基本概念
  • 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示例

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执行

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

 4. 通用规则

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

5. 结论

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

言归正传,开篇的几个题目,不知读者是否有了各自的答案,我们不妨畅所欲言,做更深入的讨论,以便揭开其真实的面纱。 

 

参考文献

(USA)Stanley B.Lippman, C# Primer

(USA)David Chappell, Understanding .NET

(Cnblog)Bear-Study-HardC#学习笔记(二):构造函数的执行序列

广而告之

[预告]

另外鉴于前几个主题的讨论中,不管是类型、关键字等都涉及到引用类型和值类型的话题,我将于近期发表相关内容的探讨,主要包括3个方面的内容,这是本系列近期动向,给自己做个广告。祝各位愉快。 

[声明] 

本文的关键字指的是C#中的关键字概念,并非一般意义上的.NET CRL范畴,之所以将这个主题加入本系列,是基于在.NET体系下开发的我们,何言能逃得过基本语言的只是要点。所以大可不必追究什么是.NET,什么是C#的话题,希望大家理清概念,有的放肆。

温故知新

[开篇有益]

[第一回:恩怨情仇:is和as]

[第二回:对抽象编程:接口和抽象类]

[第三回:历史纠葛:特性和属性]

[第四回:后来居上:class和struct]

[第五回:深入浅出关键字---把new说透]

©2007 Anytao.com

原创作品,转贴请注明作者和出处,留此信息。

本贴子以现状提供且没有任何担保,同时也没有授予任何权利。
This posting is provided "AS IS" with no warranties, and confers no rights.

 

posted @ 2007-05-04 00:31 Anytao 阅读(6815) 评论(49)  编辑 收藏 所属分类: A DotNet

  回复  引用  查看    
#1楼 2007-05-04 07:32 | 徐鸿翼      
base用于继承层次中的访问,this是对象自身中的访问,还是有很大区别的。
对IL的分析,受益良多,呵呵:)

  回复  引用    
#2楼 2007-05-07 15:14 | 福利彩票 [未注册用户]
还是看不大懂。http://***/ 福利彩票
  回复  引用  查看    
#3楼 [楼主]2007-05-07 22:33 | Anytao      
@徐鸿翼
谢谢支持。
  回复  引用  查看    
#4楼 [楼主]2007-05-07 22:33 | Anytao      
@folen
感谢支持。
  回复  引用  查看    
#5楼 [楼主]2007-05-07 22:33 | Anytao      
@福利彩票
其实这个主题并不难理解,感谢支持。
  回复  引用  查看    
#6楼 [楼主]2007-06-01 18:06 | Anytao      
@念时
感谢支持。
  回复  引用    
#7楼 2007-08-16 10:05 | shen126 [未注册用户]
base 关键字只能用在构造函数里吗?好像不是这样……
  回复  引用  查看    
#8楼 2007-09-22 09:24 | 镜涛      
呵呵,一直在看这个专题的文章,感觉把以前的东西系统化了,在大脑中形成了系统。受益颇多。
  回复  引用  查看    
#9楼 [楼主]2007-09-26 11:13 | Anytao      
@Stanley.Luo
:-)
  回复  引用  查看    
#10楼 [楼主]2007-09-26 11:14 | Anytao      
@shen126
见原文“其用于在派生类中实现对基类公有或者受保护成员的访问,但是只局限在构造函数、实例方法和实例属性访问器中,MSDN中小结的具体功能包括:

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


:-)
  回复  引用  查看    
#11楼 [楼主]2007-09-26 11:15 | Anytao      
@chinaifne
:-)
  回复  引用  查看    
#12楼 [楼主]2007-09-26 11:16 | Anytao      
@镜涛
呵呵,系统化的来一次梳理,正是我原本的打算,希望能够保持如一。
  回复  引用  查看    
#13楼 2007-10-17 14:17 | 小猴子      
好贴一定要回。。。
  回复  引用  查看    
#14楼 [楼主]2007-10-17 19:22 | Anytao      
呵呵,欢迎常来分享所得与交流。
  回复  引用  查看    
#15楼 2007-10-23 16:51 | 静水≈深流      
LZ 辛苦一下 帮我看看此问题如何解决
也是关于 this和base的
http://www.cnblogs.com/RunDeep/archive/2007/10/23/934835.html
  回复  引用  查看    
#16楼 [楼主]2007-10-23 17:59 | Anytao      
@静水≈深流
已经留言,但是对那个需求不是很清楚,见谅:-)
  回复  引用  查看    
#17楼 2007-10-29 09:34 | flankerfc      

在C#代码中,你写到:
//由三层继承可以看出,base只能继承其直接基类成员

但是在IL代码中,你写到:
//base.ShowResult最终调用的是最高级父类Vehicle的方法,
//而不是直接父类Car.ShowResult()方法,这是应该关注的

-------------------------------------------------------

没搞明白,base到底调用直接基类,还是最高级基类??

  回复  引用  查看    
#18楼 [楼主]2007-10-29 16:07 | Anytao      
@flankerfc
你好,谢谢你的讨论:

public override void ShowResult()
{
//由三层继承可以看出,base只能继承其直接基类成员
base.ShowResult();
base.Run();
Console.WriteLine("It's audi's result.");
}
中,上述说法的确欠妥,在直接基类中有方法实现时,则base只能继承其直接基类成员,而如果直接基类没有该实现时,则base方法会向上访问,例如base.Run()其实方法的是Vehicle::Run()

而IL分析中关于访问Vehicle::ShowResult的分析,是基于在Audi父类的ShowResult中有base的向上访问,因此最终会追溯到最高级父类,这是原因所在。

关于多层访问的描述有些欠妥,谢谢讨论,我考虑考虑,及时修订。


  回复  引用    
#19楼 2007-12-21 17:36 | Roy.Shen [未注册用户]
看了25楼的回复,才理解了作者对于“因此,对重写父类方法,最终指向了最高级父类的方法成员”的描述。其实就是调用直接父类的方法成员,如果直接父类没有就再往上级找,直到找到为止,是么。

谢谢您的文章,受益匪浅
  回复  引用  查看    
#20楼 [楼主]2007-12-25 09:15 | Anytao      
@Roy.Shen
的确如此,我在25楼的回复更具体一些,谢谢你的参与:-)
  回复  引用    
#21楼 2008-02-18 10:11 | zhenli627 [未注册用户]
一直看,很好哦!希望继续更多。。。
  回复  引用  查看    
#22楼 [楼主]2008-02-18 13:19 | Anytao      
@zhenli627
最近太忙了,不过这个系列一定会坚持下去,几乎大部分的内容都会以《你必须知道的.NET》一书来呈现。这个系列将继续我的其他一些心得和收获:-)
  回复  引用  查看    
#23楼 2008-03-04 13:13 | hoodlum1980      
this是当前对象的引用,实际上是作为“隐式”的一个参数传递到方法的。
  回复  引用  查看    
#24楼 2008-04-03 15:42 | 二把刀      
楼主文章写的太好了,已经学习了一个星期了,收获太大了!谢谢楼主
  回复  引用  查看    
#25楼 [楼主]2008-04-03 17:01 | Anytao      
@二把刀
呵呵,谢谢你的阅读。边基础研究,边应用体验,会有更多收获,欢迎指教:-)
  回复  引用  查看    
#26楼 [楼主]2008-05-10 11:24 | Anytao      
@hoodlum1980
this是当前对象的引用,实际上是作为“隐式”的一个参数传递到方法的。
--------------------------------
所言甚是,从对象创建、内存分配的角度来理解这个“隐式”的过程,会更加清晰和有收获:-)
  回复  引用    
#27楼 2008-06-16 16:42 | tom.pan [未注册用户]
看了下,有点迷惑,看了评论才知道有的地方说法不是很准确。
关于base,我用最简单方法的总结下:用到base.fun()的时候,先去找它的爸爸的fun()方法,没有则去爸爸的爸爸找fun()方法,如此一层层向上。
  回复  引用  查看    
#28楼 [楼主]2008-06-17 22:20 | Anytao      
@tom.pan
:-)
  回复  引用  查看    
#29楼 2008-06-19 12:16 | 残香恨      
看楼主的这个系列很久了,谢谢楼主给我们带来这么一个很实用的系列!
关于继承,我也一直很迷惑,还是以代码来说明问题吧,如下
class A
{
public int i;
}
class B : A
{
public int j;
}
class C : B
{
public void Show()
{
base.i = 1;//(1)
base.j = 2;//(2)
Console.WriteLine(this.i);//(3)
Console.WriteLine(this.j);//(4)
}
}
以上代码的输出结果为:1和 2
通过IL Disassembler查看IL代码时,我们发现在C类的字段中,并没有i字段和j字段;同时我们发现(1)处对应的IL代码是A.i,(2)处对应的IL代码是B.j,从楼主的15章《继承本质论》来看,字段A.i和B.j都会拷贝到类C中,作为类C的字段,所以在上例中,我们通过this.i,this.j访问到和base.i,base.j相同的字段。我们似乎可以得出这样的结论,通过base关键字,我们可以访问从父类继承来的可访问的(public,protected)实例字段和实例方法,通过this,我们可以访问本类的所有实例字段和实例方法。至于IL代码中的A.i,B.j,我想可能是用来标明这个实例字段或实例方法的出处,本例中,字段i来自A类,字段j来自B类,即在new对象来时要从A类、B类拷贝实例字段。
不知道我这样理解对不对?
  回复  引用  查看    
#30楼 [楼主]2008-06-24 23:42 | Anytao      
@残香恨
呵呵,感谢你的讨论。
从你的论述中,有些是正确的,有些则不尽然。我认为最大的问题是,将IL的分析结果和内存分配并非是一回事儿。所以IL中的A.i或B.j其实和c#高级语言的表述是一回事儿:-)

关于base和this的论述,我觉得本文已经有了较详细的论述,可以做以参考:-)

关于对象的创建过程,new对象时,CLR会按照继承层次进行遍历,子类会继承所有父类的成员,所以在子类的内存布局中是包含所有继承层次的数据成员,而不管其访问修饰符是否为public或protected。
  回复  引用  查看    
#31楼 2008-06-26 17:19 | 残香恨      
@Anytao
感谢你的回复!
关于继承,可能我们在理解上有出入。我看过的书中,都说public,protected是可以继承的,表现出来的是派生类实例可以直接访问这些实例字段,private的实例字段是不可以直接访问的,必须通过基类提供的实例方法来对其进行操作。但实际上,如您所说,通过构造函数的一层层向上调用,把所有实例字段都在派生类实例中分配了内存。
注:这里我们只讨论实例字段
有一点不明白的地方是:
“我认为最大的问题是,将IL的分析结果和内存分配并非是一回事儿。所以IL中的A.i或B.j其实和c#高级语言的表述是一回事儿”?
这里的后一句我不明白,还请说明。
对于您所说的“子类会继承所有父类的成员”,我觉得这里的含义应该是父类的实例字段(即成员变量)和public,protected方法。我对成员的理解是成员变量和成员方法。
  回复  引用  查看    
#32楼 [楼主]2008-07-01 22:45 | Anytao      
@残香恨
呵呵,感谢你的探讨和分析,关于继承我首先推荐你阅读我的另一篇文章《继承本质论》
http://www.cnblogs.com/anytao/archive/2007/09/10/must_net_15.html
希望从中可以有所收获。

如果看后还有疑问,欢迎继续交流:-)


  回复  引用  查看    
#33楼 2008-07-09 13:46 | AGPSky      
public Car(string name, int speed)
: this()
{ }
这里的this()的作用是什么呢
请AnyTao老师明示一下。例子中没理解。

  回复  引用  查看    
#34楼 [楼主]2008-07-11 09:55 | Anytao      
@AGPSky
this作用于构造函数时,表示对本类构造函数的调用,这种方式常用于创建多个构造函数的情况,通过this()或者this(参数列表)可以方便的调用已经存在的构造函数。

写一个简单的实例,就很好理解了:
class MyInfo
{
public MyInfo()
{
Console.WriteLine("Call default constructor.");
}

private string name;

public MyInfo(string name)
: this()
{
Console.WriteLine("Your name is " + name);
}

static void Main(string[] args)
{
MyInfo mi = new MyInfo("Anytao");
}
}
  回复  引用  查看    
#35楼 2008-07-11 10:48 | AGPSky      
多谢了,这下明白了。

  回复  引用    
#36楼 2008-09-04 13:44 | kofkyo [未注册用户]
执行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)。

如果使用Audi audi = new Audi();
Audi.Audi(string name, int speed)是不会到达被调用的吧

  回复  引用  查看    
#37楼 [楼主]2008-09-04 16:08 | Anytao      
@kofkyo
没有明白你指的“是不会到达被调用”的意思?可否详细说明?
  回复  引用  查看    
#38楼 2008-09-04 17:07 | kofkyo      
Audi audi = new Audi();
那么
Audi.Audi(string name, int speed)是不会执行的吧
  回复  引用  查看    
#39楼 [楼主]2008-09-04 18:57 | Anytao      
@kofkyo
是的,不会被执行,因此此处调用的是无参构造函数,多谢提醒:-)

标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  博客园首页

  新闻频道

  社区

  小组

  博问

  网摘

  闪存

  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2007-07-17 11:54 编辑过


相关链接: