CLR via C#學習筆記-第六章-CLR如何調用虛方法、屬性和事件
6.6.1 CLR如何調用虛方法、屬性和事件
本節重點是方法,但討論也與虛屬性和虛事件密切相關。屬性和事件實際作為方法實現,以後的章節會討論他們。
方法
方法代表在類型或類型的實例上執行某些操作的代碼。在類型上執行操作,稱為靜態方法;在類型的實例上執行操作,稱為非靜態方法。
所有方法都有名稱、簽名和返回類型。CLR允許類型定義多個同名方法,只要每個方法都有一組不同的參數或者一個不同的返回類型。
但除了IL匯編語言,沒有任何利用了這一特點的語言。大多數語言包括C#,在判斷方法的唯一性時,除了方法名之外,都只以參數為準,方法返回類型會被忽略。
C#在定義轉換操作符方法時實際放寬了這個操作,詳情在第八章。
以下Employee類定義了三種不同的方法
internal class Employee { //非虛實例方法 public Int32 GetYearsEmployed(){} //虛方法 public virtual String GetProgressReport{} //靜態方法 public static Employee Lookup(String name){} }
編譯以上代碼,編譯器會在程序集的方法定義表中寫入3個記錄項,每個記錄項都用一組標值flag指明方法是實例方法、虛方法還是靜態方法。
寫代碼調用這些方法,生成調用代碼的編譯器會檢查方法定義的標值flag,判斷應如何生成IL代碼來正確調用方法。
CLR提供兩個方法來調用指令
- call
該IL指令可調用靜態方法、實例方法和虛方法。
用call指令調用靜態方法,必須指定方法的定義類型。用call指令調用實例方法或虛方法,必須指定引用了對象的變量。
call指令假定該變量不為bull。換言之,變量本身的類型指明了方法的定義類型。如果變量的類型沒有定義該方法,就檢查基類型來查找匹配方法。
call指令經常用於以非虛方式調用虛方法。
- callvirt
該IL指令可調用實例方法和虛方法,不能調用靜態方法。用callvirt指令調用實例方法或虛方法,必須指定引用了對象的變量。
用callvirt指令調用非虛實例方法,變量的類型指定了方法的定義類型。用callvirt指令調用虛實例方法,CLR調查發出調用的對象的實際類型,然後以多態方式調用方法。
為了確定類型,發出調用的變量決不能為null。換言之,編譯這個調用時JIT編譯器會生出代碼來驗證變量的值是不是null。
如果是,callvirt指令造成CLR拋出空引用異常。正是由於要進行這種額外的檢查,所以callvirt指令的執行速度比call稍慢。
註意,即使callvirt指令調用的是非虛實例方法,也要執行這種null檢查。
call和callvirt的實際使用
調用靜態方法,IL會調用call指令,調用虛實例方法、非虛實例方法時,IL調用 callvirt方法。
這意味著,當對象為null時,調用對象方法會拋出空引用異常。
但編譯器有時用call而不是callvirt調用虛方法,雖然剛開始有點難以理解,但下面代碼證明了有時真的需要這樣做
internal class SomeClass { //ToString是基類Object定義的虛方法 public override String ToString() { //編譯器使用IL指令call //以非虛方式調用Object的ToString方法 //如果編譯器用callvirt而不是 //那麽該方法將遞歸調用自身,直至棧溢出 return base.ToString(); } }
調用虛方法base.ToString時,C#編譯器生成call指令來確保以非虛方式調用基類的ToString方法。
這是必要的,因為如果以虛方式調用ToString,調用會遞歸執行。
值類型傾向使用call
編譯器調用值類型定義的方法時傾向於使用call指令,因為值類型是密封的。
這意味著即使值類型含有虛方法也不要考慮多態性,這使調用更快。
此外,值類型實例的本質保證他永不為null,所以永不拋出空引用異常。
最後,如果以虛方式調用值類型中的虛方法,CLR要獲取對值類型的類型對象的引用,以便引用(類型對象中的)方法表,這要求對值類型裝箱。
裝箱對堆造成更大壓力,迫使更頻繁的垃圾回收,使性能受到影響。
無論用call還是callvirt調用實例方法還是虛方法,這些方法通常接收隱藏的this實參作為方法的第一個參數。this實參引用要操作的對象。
類型的設計原則
類型設計的時候應盡量減少虛方法數量。首先調用虛方法的速度比調用非虛方法慢。其次,JIT編譯器不能內嵌inline虛方法,這進一步影響性能。第三,虛方法使組建版本控制變得更脆弱。第四,定義基類型時,經常要提供一組重載的簡便方法convenience method。如果希望這些方法是多態的,最好的辦法就是使最復雜的方法成為虛方法,使所有重載的簡便方法成為非虛方法。
遵循這個原則,還可在改善組件版本控制的同時,不至於對派生類型產生負面影響。
CLR via C#學習筆記-第六章-CLR如何調用虛方法、屬性和事件