程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> [CLR via C#]6. 類型和成員基礎

[CLR via C#]6. 類型和成員基礎

編輯:C#入門知識

6.1 類型的各種成員

  在一個類型中,可以定義0個或多個以下種類的成員:

  1)常量    常量就是指出數據值恆定不變的符號。這些符號通產用於是代碼更容易閱讀和維護。常量通常與類型關聯,而不與類型的實例關聯。從邏輯上講,常量始終是靜態成員。   2)字段    字段表示一個只讀或可讀/寫的數據值。字段可以是靜態的,這時是類型狀態的一部分;字段也可以是實例(非靜態)的,這時字段是對象狀態的一部分。強烈建議將字段聲明成為私有字段,防止類型或對象狀態被外部代碼破壞。   3)實例構造器    實例構造器是將新對象的實例字段初始化為良好初始化狀態(即完成預期初始化)的一種特殊方法。   4)類型構造器    類型構造器是將類型的靜態字段初始化為良好初始化狀態(即完成預期初始化)的一種特殊方法。   5)方法    方法是一個特殊的函數,作用是更改或查詢一個類型或狀態的狀態。作用於類型時,成為靜態方法;作用於對象是,稱為實例方法。方法一般會對類型或對象的字段執行讀寫操作。   6)操作符重載    操作符重載實際是一個方法,它定義了將一個特定的操作符作用於對象時,應該如何操作這個對象。由於不是所有編程語言都支持操作符重載,所以操作符重載方法不是"公共語言規范"(CLS)的一部分。   7)轉換操作符    轉換操作符是定義如何顯示或隱式地將對象從一種類型轉型為另一種類的方法。於不是所有編程語言都支持操作符重載,所以操作符重載方法不是"公共語言規范"(CLS)的一部分。   8)屬性 利用屬性(propety),可以使用一種簡單的、字段風格的語法來設置或查詢類型或對象的部分邏輯狀態,同時保證狀態不被遭到破壞。作用於類型的稱為靜態屬性,作用於對象的稱為實例屬性。屬性可以是沒有參數的(比較普遍),但也可以有參數的(相當少見,但對集合類來說很常見)。   9)事件    利用靜態事件,一個類型可以向一個或多個靜態或實例方法發出通知。而利用實例事件,一個對象可以向一個或多個靜態或實例方法發出通知。提供事件的類型或對象的狀態發生改變,通常就會引發事件。事件包含兩個方法,允許靜態或實例方法登記或注銷對該事件的關注。除了這兩個方法,事件通常還使用一個委托字段來維護已登記的方法集。   10)類型    類型可定義嵌套於其中的其他類型。通常用這個方法將大的、復雜的類型分解成更小的構建單元,以簡化實現。   無論使用什麼編程語言,它的編譯器都必須能處理你的源代碼,為上述的每一種成員生成元數據和IL代碼。無論使用哪種編程語言,元數據的格式都是完全一致的。所以才使得CLR能成為"公共語言運行時"。元數據是所有語言都生成和使用的公共信息。   CLR使用公共元數據格式決定常量、字段、構造器等成員在運行時的行為。簡單地說,元數據是整個.NET Framework開發平台的關系,它實現了編程語言、類型和對象的無縫集成。

  6.2 類型的可見性

  在文件范圍中定義類型時,可以將類型的可見性指定為public或internal。

  public類型不僅對它定義的程序集中的所有代碼可見,還對其它程序集中的代碼可見。   internal類型僅對定義程序集中的所有代碼可見,對其它程序集中的代碼不可見。定義類型時,如果不顯式定義類型的可見性,C#編譯器默認將類型的可見性設為internal。   CLR和C#可以通過友元程序集(friend assembly)來使得一個程序集對另一個可見性為internal的程序集進行public操作。   構建程序集時,可以使用System.Runtime.CompilerServices命名空間中定義一個名為InternalsVisibleTo的attribute來標明它認為的"友元"的其它程序集。這個attribute要獲取一個字符串參數,這個字符串參數要標識友元程序集的名稱和公鑰。
using Systeml
using System.Runtime.CompilerServices 
 
[assembly:InternalsVisibleTo("Winterllect,publickKey=12345678....asdf")]
.....
  "友元程序集"功能只適合發布時間相同的程序集,最好是打包一起發布。因為友元程序集相互依賴程度很好,錯開太久可能發生版本不兼容的問題。     6.3 成員的可訪問性

  定義類型的成員(包括嵌套類型)時,可指定成員的可訪問性(accessibility)。

  CLR和C#中的成員可訪問性對比:    CLR術語 C#術語 描述 Private private 成員只能由定義類型或任何嵌套類型中的方法訪問 Family protected 訪問僅限於包含類或從包含類派生的類型 Family and Assembly 不支持 成員只能由定義類型、任何嵌套類型或者同一程序集中定義的任何派生類型中的方法訪問 Assembly internal 訪問僅限於當前程序集 Family or Assembly protected internal 訪問僅限於從包含類派生的當前程序集或類型 Public public 訪問不受限制        在C#中如果沒有顯示聲明成員的可訪問性,編譯器通常默認選private。   CLR要求接口類型的所哦成員都具有public訪問性。所以C#就禁止開發人員顯示指定接口的可訪問性。   一個派生類重寫它的基類中定義的一個成員時,C#編譯器要求原始成員和重寫成員具有相同的訪問性。但CLR不然,從一個基類派生時,CLR允許放寬成員的可訪問性的限制,但不可收緊。因為CLR承諾派生類總是可以轉換成為基類,並獲取對基類方法的訪問權控制。

  6.4 靜態類

  在C#中,要用static關鍵字定義不可實例化的類。這個關鍵字只能應用於類,不能引用於結構 (值類型)。這是因為CLR總是允許值類型實例化。

  C#編譯器對靜態類做了如下限制:   1)靜態類必須直接從基類System.Object派生,從其他任何基類派生都是無意義的。繼承只適用於對象,而你不能創建靜態類的實例。   2)靜態類不能實現任何接口,因為只有使用類的實例,才能調用接口的方法。   3)靜態類只能定義靜態成員(字段、方法、屬性和事件),任何實例成員都將導致編譯器報錯。   4)靜態類不能作為字段、方法參數或局部變量使用,因為它們都代表引用了一個實例的變量。   使用關鍵字static定義一個類,將導致C#編譯器將該類同時標記為abstract(不可實例化)和sealed(不可派生)。編譯器不會再類型中生成一個實例構造方法(.ctor)。

  6.5 分部類、結構和接口

  partial這個關鍵字告訴C#編譯器,一個類、結構或者接口的定義源代碼可能分散到一個或者多個源代碼文件中。

  主要有三方面原因促使我們將某個類型的源代碼分散到多個文件中:   1)源代碼控制    使用partinal關鍵字,可以將類型的代碼分散到多個源代碼文件中,每個文件都可以單獨簽出使多個開發人員同時編輯該類型。   2)在同一個文件中,將一個類或結構分解成不同的邏輯單元   3)代碼拆分     6.6 組件、多態和版本控制     組件軟件編程(Component Software programming,CSP)正是OOP發展到極致的一個成功。下面列舉了組件的一些特點。   1)組件(.NET中成為程序集)有"已經發布"的意思   2)組件有自己的標識(名稱、版本、語言文化和公鑰)   3)組件永遠維持自己的標識(程序集中的代碼永遠不會靜態鏈接到另一個程序集中:.NET總是使用動態鏈接)   4)組件清楚指明它所依賴的組件(引用元數據表)。   5)組件要文檔化它的類和成員。   6)組件必須指明它要求的安全權限。   7)組件要發布一個在任何"維護版本"中都不會改變的接口(對象)。"維護版本"(servicing version)代表組件的新版本,它向後兼容於組件的原始版本。通常,"維護版本"包含bug修復、安全補丁或者一些小的功能增強。但在"維護版本"中,不能要求任何新的依賴關系,也不能要求任何附加的安全權限。   CSP的很大一部分都涉及到版本控制。組件隨時間不斷變化,並根據不同的時間表來發布。   .NET中,版本號包括4個部分:主版本號(mahor version)、次版本號(minor version)、內部版本(build number)和修訂號(recision)。   major/minor部分通常代表程序集的一個連續的、穩定的功能集,而build/revision部分通常代表對這個功能的一次維護。   將某個組件(程序集)中定義的一個類型作為另一個組件(程序集)中的一個類型的基類使用時,便會發生版本控制問題。   C#提供了5個能影響組件版本控制的關鍵字,可將它們應用類型以及/或者類型成員。 C#關鍵字 類型 方法/屬性/事件 常量/字段 abstract 表示不能構造該類型的實例 表示為了構造派生類型的實例,派生類型必須重寫並實現這個成員 不允許 virtual 不允許 表示這個成員可由派生類重寫 不允許 override 不允許 表示派生類型重寫了基類型的成員 不允許 sealed 表示該類型不能用作基類 表示這個成員不能被派生類型重寫,只能將該關鍵字應用於准備重寫一個虛方法的方法 不允許 new 引用於嵌套類型、方法、屬性、時間、常量或字段時,表示該成員與基類型中相似的成員無任何關系      CLR是如何調用虛方法、屬性和事件?   以下Employee類定義了三種不同的方法:
internal class Employee {
    //非虛實例方法
    public         int32     GetYearsEmployed() { ... }
    //虛方法
    public virtual String    GenProgressReport() { ... }
    //靜態方法
    public static  Employee  Lookup(String name) { ... }     
}

  編譯器編譯上述代碼,會生成的程序集的方法定義表中寫入三個記錄項,每個記錄項都用一組標識(flag)來指明該方法是實例犯法、虛方法還是靜態方法。

  CLR提供了兩個方法調用指令:   1)call    這個調用指令可調用靜態方法、實例方法和虛方法。用call指令調用靜態方法時,必須指定是哪個類型定義了要有CLR調用的方法。用call指令調用實例方法或虛方法時,必須指定引用了一個對象的變量。call指令假定該變量不為null。換言之,該變量本身的類型指明了要有CLR調用的方法是在哪個類型中定義的。如果變量的類型沒有定義該方法,就檢查基類型來查找匹配的方法。call指令經常用於以非虛的方式調用一個虛方法。   2)callvirt    這個調用指令可調用實例方法和虛方法,但不能調用靜態方法。用callvirt指令調用實例方法或虛方法時,必須指定引用了一個對象的變量。用callvirt指定調用非虛實例方法時,變量的類型指明了最終由CLR調用的方法是在哪個類型中定義的。用callvirt指令調用虛實例方法時,CLR會調查發出調用的哪個對象的實際類型,然後以多態方式調用。為了確定類型,用來發出調用的變量決不能為null。換言之,編譯這個調用時,JIT編譯器會生成代碼來驗證變量的值是不是為null。正是由於要進行這種額外的檢查,所有callvirt指令執行速度比call稍慢。注意,即使callvirt指令調用的是一個非虛的實例方法,也會執行這種null檢查。   C#使用callvirt指令調用所有的實例方法。   編譯器調用由值類型定義的方法時,會傾向使用call指令,因為之類的密封的。這就意味著即使你非要在值類型中寫一些虛方法也不用考慮多態的問題。   無論是call還是callvirt來調用實例方法或虛方法,這些方法通常接受一個隱藏的this實參作為方法的第一個參數。this實參引用的是要操作的對象。   設計一個類型時,應盡量減少所定義的虛方法的數量。首先,調用虛方法的速度比調用非虛方法慢。其次,JIT編譯器不能內嵌虛方法,這進一步影響性能。第三,虛方法使組件的版本控制變得更脆弱。第四,定義一個基類型時,經常需要提供一組重載的簡便方法。如果希望這些方法是多態的,最好就是是最復雜的方法稱為虛方法,使所有重載的簡便方法成為非虛方法。 例如:
public class Set {
    private Int32 m_length = 0;
    
    //這個重載的簡便方法是非虛的
    public Int32 Find(Object value) {
        return Find(value,0,m_length);
    }
 
    //這個重載的簡便方法是非虛的
    public Int32 Find(Object value, Int32 startIndex) {
        return Find(value, startIndexx, m_length - startIndex);    
    }
 
    //功能最豐富的方法是虛方法,可以被重寫
    public virtual Int32 Find(Object value, Int32 startIndex, Int32 endIndex){
        //可被重寫的實現放在這裡....
    }
    
    //其他方法
}

  合理使用類型的可見性和成員的可訪問性

  首先,在定義一個新的類型時,編譯器應默認是密封類,使它不能作為基類使用。C#編譯器默認是非密封類,但允許開發人員使用關鍵字sealed顯示地將類標記為密封。    使用密封類比非密封類更好,理由有三:   1)版本控制    可以容易的將密封類改成非密封類,但非密封類改成密封類就麻煩許多。   2)性能    如上所說,調用虛方法的性呢過比不上調用非虛方法,因為CLR為了判斷是哪個類型定義了要調用的方法,必須在運行時查找對象的類型。但是,如果JIT編譯器看到的是使用密封類型對虛方法調用,就可以采用非虛的方式調用虛方法。   3)安全性和可預測性    類必須保護它自己的狀態,不允許自己被破壞。當類處於非密封非私有的,派生類就能訪問和更改基類的狀態。   定義類是遵循的一些原則:   1)定義類時,除非確定要將一個類作為基類使用,並允許派生類對它進行特化處理,否則總是顯式地把它指定為sealed類。另外,默認將類指定為internal類,除非希望在程序集外部公開這個類。   2)在類的內部,將數據字段定義為private。   3)在類的內部,將自己的方法、屬性和事件定義為private和非虛。當然,會將某個方法、屬性和事件定義為public,以便公開類的某些功能。virtual是最後會考慮的,因為虛成員會放棄許多控制,喪失獨立性,變得徹底依賴於派生類的正確行為。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved