1. 程式人生 > >CLR via C#學習筆記-第六章-CLR如何調用虛方法、屬性和事件

CLR via C#學習筆記-第六章-CLR如何調用虛方法、屬性和事件

style err rri 實參 寫代碼 調查 pre 好的 屬性

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如何調用虛方法、屬性和事件