首先我們說一下什么是方法重載。在面對對象這樣的高級語言中都允許我們在一個類中定義多個方法名相同、方法間參數(shù)個數(shù)和參數(shù)順序不同的方法,對于參數(shù)個數(shù)不同或者參數(shù)列表不同的情況我們稱之為參數(shù)列表不同。需要注意的是這里沒有提到方法的返回值。也就是決定方法是否構(gòu)成重載有兩個條件:
(1)在同一個類中;
(2)方法名相同;
(3)參數(shù)列表不同。
例如下面的代碼:
對于上面的代碼,(1)沒有參數(shù),(2)使用了一個int類型的參數(shù),(1)和(2)之間就構(gòu)成了重載。(2)與(3)相比僅僅返回值不同,雖然重載不關(guān)心返回值的不同,但是在C#中不允許存在方法名和參數(shù)列表相同、返回值不同的方法,所以(2)和(3)不能同時存在于代碼中,(3)如果不注釋掉上面的代碼是沒有辦法通過編譯的。
上面我們僅僅討論了重載的一些基本常識,下面我們探討一下一些情況稍微復雜的重載情況。
首先我們看第一個版本:
大家猜猜這個程序的運行結(jié)果是什么?
以下是程序運行結(jié)果:
String
Object
對以上代碼進行分析,我們發(fā)現(xiàn)Show()方法有兩種形式,一種是string類型的參數(shù),一種是object類型參數(shù),在一個類中存在方法名相同、參數(shù)列表不同(參數(shù)個數(shù)或者參數(shù)類型不同)的現(xiàn)象我們稱之為overloading,即重載。不過這里的Show()方法的參數(shù)比較特殊,因為string類繼承于Object類,也就是Show()方法的參數(shù)存在一種繼承關(guān)系。從結(jié)果我們可以得出兩點結(jié)論:
(1)從String s = null;Show(s);最后調(diào)用的是static void Show(string s)這個方法我們可以得出,C#中方法調(diào)用是精確匹配的,也就是s是string類型,雖然string類型繼承自object類型,盡管static void Show(Object o)也滿足條件,但是方法聲明中static void Show(string s)這個聲明與s類型的最接近(因為s是string類型,與它最接近),所以執(zhí)行static void Show(string s),而不執(zhí)行static void Show(Object o)這個方法。
(2)從Object o = "123"; Show(o);最后調(diào)用的是static void Show(Object o)這個方法我們可以得出,C#中如果存在方法重載,會根據(jù)其refrence type(引用類型)來調(diào)用對象的方法,而不是根據(jù)instance type(實例類型)來調(diào)用。盡管”123”是string類型,但是它的refrence type是object類型的,所以會調(diào)用static void Show(Object o)這個方法而不是static void Show(string s)。
上面的Main()方法的IL代碼如下:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// 代碼大小 24 (0x18)
.maxstack 1
.locals init ([0] string s,
[1] object o)
IL_0000: nop
IL_0001: ldnull
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: call void OverrideDemo.Program::Show(string)
IL_0009: nop
IL_000a: ldstr "123"
IL_000f: stloc.1
IL_0010: ldloc.1
IL_0011: call void OverrideDemo.Program::Show(object)
IL_0016: nop
IL_0017: ret
} // end of method Program::Main
從上面的IL代碼我們可以看出對于string s=null;這句代碼在IL中表示為:ldnull。
再根據(jù)上面的結(jié)論,我們看下面的代碼:
猜猜上面的代碼執(zhí)行結(jié)果會是怎樣的?
以下是程序運行結(jié)果:
String
String(感謝btbtshu、windman0925提醒此處的筆誤,原來粘貼錯了,成了Object了,現(xiàn)在已更正)
Object
從上面的運行結(jié)果我們可以得出以下結(jié)論:
(1)從Show(null)最后調(diào)用的是static void Show(String s)方法我們更進一步可以說在C#中是方法調(diào)用盡量精確匹配的。盡管null我們可以理解為一個空object對象或者一個空字符串,但是在這里C#還是精確為派生類。這就像我們沒有錢,可以說沒有一分錢也可以說沒有500英鎊,但是沒有一分錢自然就沒有500億英鎊,所以我們跟別人說沒有錢的時候沒有必要說沒有500億英鎊一樣。在這里自然null就表示空字符串。所以Show(null)這個方法會調(diào)用static void Show(String s)這個方法。
這有點像下面的情況:
一次活動大會上,主持人說:“身高不到1.60m的請坐在1到3排,身高不到1.75m的請做到4到6排,其他的請隨便坐?!?br>上面的語句似乎有些邏輯方面的問題,應該說身高超過1.60m但是不到1.75m的請坐到4到6排。但是如果你面對著一群拿著槍的強盜,他說上面的話時,恰好你也在場并且你的身高是1.55m,你會坐到哪一排?你總不可能冒著挨一槍的危險去糾正他的邏輯錯誤吧?最好的辦法是坐到1到3排。因為無論怎么說你的身高是絕對滿足不到1.60m這個條件的(盡管你的身高也滿足強盜說的第二個條件,即身高不到1.75米,但是你肯定不會冒這個危險,從上下句的意思我們也能推斷出人家的意思就是身高在1.61m到1.74m之間的人坐4到6排)。
在上面的代碼中,你在運行環(huán)境的眼中就是一個持槍的強盜,雖然null可以理解為null類型的string或者null類型的object,但是它不能向你問清楚這個到底是null類型的string或者null類型的object,因為string是Object的派生類,所以它按照null類型的string來調(diào)用相應的方法了。
(2)從Show("")最后調(diào)用static void Show(String s)這個方法進一步證明了方法調(diào)用是盡量選擇參數(shù)最匹配的那個執(zhí)行。因為Show("")相當于:string s = ""; Show(s);s的引用類型是string,所以會調(diào)用static void Show(String s)這個方法。
我們在這里可以假設(shè)一下:假如存在一個類A是String類的派生類(實際上string類是sealed的,也就是不可繼承的,所以我說了是假設(shè)),并且存在在上面的代碼改變?nèi)缦拢?/p>
如果上面的假設(shè)成立,上面的代碼運行結(jié)果應該如下:
A
String
(3)為什么Show(1)會調(diào)用static void Show(Object o)這個方法呢?在這個類中與Show(1)最精確的方法重載應該是static void Show(int i)這種方法聲明,但是方法中沒有,因為int是繼承自ValueType類,所以如果沒有static void Show(int i)這種聲明,那么其次接近的聲明應該是static void Show(ValueType v)這種聲明,可惜方法中依然沒有,不過ValueType類繼承自O(shè)bject類,所以比static void Show(ValueType v)還次一點的方法重載聲明應該是static void Show(Object o),而類中也確實存在這種聲明,所以會調(diào)用static void Show(Object o)這個方法。當然從int到Object這個過程中存在一次box,也就是裝箱(裝箱是從值類型到引用類型的轉(zhuǎn)換),這個可以從下面的IL代碼可以看出來。
以下是第二種情況下Main()方法的IL代碼:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// 代碼大小 32 (0x20)
.maxstack 8
IL_0000: nop
IL_0001: ldnull
IL_0002: call void OverrideDemo.Program::Show(string)
IL_0007: nop
IL_0008: ldstr ""
IL_000d: call void OverrideDemo.Program::Show(string)
IL_0012: nop
IL_0013: ldc.i4.1
IL_0014: box [mscorlib]System.Int32
IL_0019: call void OverrideDemo.Program::Show(object)
IL_001e: nop
IL_001f: ret
} // end of method Program::Main
下面我們對第二種情況的代碼做一些變化,代碼如下:
上面的代碼的運行結(jié)果是什么,你能猜出來嗎?
哈哈,上面的程序代碼是沒有運行結(jié)果的,因為它沒有辦法編譯!編譯情況如下:
為什么不能通過編譯呢?
原因就出在Show(null)這個方法這里!如果僅僅有static void Show(string s)和static void Show(Object o)方法構(gòu)成重載關(guān)系,那么null我們既可以理解為空string引用也可以理解為空Object引用,因為string類型的限制更精確一些,所以C#會按照最精確地匹配成string類型,因而會執(zhí)行static void Show(string s)這個方法。這是在前面的代碼中已經(jīng)被證明的。可是現(xiàn)在多了一個static void Show(Program p)方法的重載,null既可以理解成空string類型引用,也可以理解成空Program類型引用,因為string類和Program類都是Object類的派生類,所以按照前面的推論自然不會當成空Object類型的引用。因為String類和Program類之間不存在繼承關(guān)系,按照最精確匹配原則,編譯器無法決定匹配成String類還是Program類最精確,所以編譯無法通過。
附注:最近嘗試將一些比較基礎(chǔ)的理論的知識用比較淺顯的話語表達出來,主要是為了方便初學者理解和學習,也許某些詞語用得不夠professional,,但是如果詞語太professional了怕初學者理解起來有困難,敬請各位大俠諒解,因為本文不是為你們寫的。同時也歡迎各位與我交流經(jīng)驗和心得。最近垃圾郵件太多,所以不便在這里公開我的email,如果各位有興趣與我交流,請加我為csdn好友,這樣就能看見我的QQ和email了.