[你必須知道的.NET] 第六回:深入淺出關鍵字---base和this
作者:Anytao
本文將介紹以下內容:
- 物件導向基本概念
- 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技術架構中本質訴求。對學習者來說,只有從本質上來把握概念,才能在變化非凡的應用中,一眼找到答案。
言歸正傳,開篇的幾個題目,不知讀者是否有了各自的答案,我們不妨暢所欲言,做更深入的討論,以便揭開其真實的面紗。
參考文獻
(USA)Stanley B.Lippman, C# Primer
(USA)David Chappell, Understanding .NET
(Cnblog)Bear-Study-Hard,C#學習筆記(二):建構函式的執行序列
廣而告之
[預告]
另外鑒於前幾個主題的討論中,不管是類型、關鍵字等都涉及到參考型別和實值型別的話題,我將於近期發表相關內容的探討,主要包括3個方面的內容,這是本系列近期動向,給自己做個廣告。祝各位愉快。
[聲明]
本文的關鍵字指的是C#中的關鍵字概念,並非一般意義上的.NET CRL範疇,之所以將這個主題加入本系列,是基於在.NET體系下開發的我們,何言能逃得過基礎語言的只是要點。所以大可不必追究什麼是.NET,什麼是C#的話題,希望大家理清概念,有的放肆。