C#筆記 – 屬性

  • 用於以字段風格的語法設置/查詢對象的邏輯狀態,同時保證狀態不被破壞

  • 靜態屬性作用於類型;實例屬性作用於對象

無參屬性
  • private sealed class PropertiesClass
    {
       int score;
       bool isFullCombo;

       public int Score { set { score = value; } }

       public bool IsFullCombo { get { return isFullCombo; } }

       public float FinalResult
      {
           get { return isFullCombo ? score * 1.5f : score; }
           set { return; }
      }          
    }
  • CLR支持靜態、實例、抽象、虛屬性

  • 可用任何訪問修飾符標記

  • 可以在接口定義

  • 屬性都有名稱和定義,但不能重載(不能定義同名,不同類型的兩個屬性)

  • 屬性包括了一到兩個方法

    • 只有get:唯讀

    • 只有set:只寫

    • get+set:讀寫

    • 屬性操縱的字段稱為「支持字段」

  • 取決於屬性的定義,編譯器會在程序集生成

    • get || set的訪問器(根據聲明與否決定)

  • 屬性定義(必然生成)

    • 編譯器在屬性名之前自動附加get_ 或 set_ 前綴生成方法名

      • 當編譯器發現代碼嘗試獲取/設置屬性時,會自動生成對上述方法的調用

    • 屬性定義項則主要是引用了get/set方法,並可被反射(PropertyInfo)取得

AIP(自動實現屬性)
  • AIP:Automatically Implemented Property

  • public string AIP { get; set; }
    • 聲明了屬性,但沒有get/set的實現,C#會自動聲明一個私有字段,並自動實現get_AIP和set_AIP方法

  • AIP的好處主要是為後期的改動節省一些編譯成本

    • 使用AIP就等於創建了一個屬性,以後再實現get/set方法也不需要重新編譯

    • 如果使用字段,後期改做屬性,就需要重新編譯

  • AIP的壞處

    • AIP需在構造器中顯式初始化

    • 有AIP的類型無法對其實例反序列化

    • 手動實現的屬性可以打斷點,AIP不行

有參屬性
  • 屬性的get訪問器接受一個/多個參數;set訪問器接受兩個/多個參數

    • C#對有參屬性稱作「索引器」

    • 可以看成是[]操作符的重載

    • set訪問器本來就有一個隱藏參數(value)

    • 類型可以提供多個重載的索引器(需要簽名不同)

    • C#不支持靜態索引器

  • 對於CLR而言,屬性有參沒參並沒有區別,每個屬性都只是類型中定義的一對方法和元數據

    • public string this[int idx]
      {
         get { return "Hello World"; }
      }    
    • 由於索引器無法指定名稱,因此在C#中,默認為Item

    • 這個默認名稱可以通過IndexerNameAttribute特性來重命名

      • 該特性不會進入元數據,它只告訴編譯器要怎麼對方法和屬性的元數據命名

      • 雖然可以通過這種方法來重命名索引器,但並不能用來做重載
初始化器初始化公共屬性
  • 無參構造器的對象,可以在實例化時,直接為其公共屬性賦值
  • Employee e1 = new Employee() { Name = "Kelvin", Age = 24 };
    //或
    Employee e2 = new Employee();
    e2.Name = "Catherine";
    e2.Age = 24;

    //無參構造器限定
    Employee e3 = new Employee { Name = "Aden", Age = 1000 };
    • 構造對象 => 對公共屬性賦值

    • IL_0000:  nop
      IL_0001: newobj     instance void CLR_Ch10.Program/Employee::.ctor()
      IL_0006: dup
      IL_0007: ldstr     "Kelvin"
      IL_000c: stfld     string CLR_Ch10.Program/Employee::Name
      IL_0011: dup
      IL_0012: ldc.i4.s   24
      IL_0014: stfld     int32 CLR_Ch10.Program/Employee::Age
      IL_0019: stloc.0
      IL_001a: newobj     instance void CLR_Ch10.Program/Employee::.ctor()
      IL_001f: stloc.1
      IL_0020: ldloc.1
      IL_0021: ldstr     "Catherine"
      IL_0026: stfld     string CLR_Ch10.Program/Employee::Name
      IL_002b: ldloc.1
      IL_002c: ldc.i4.s   24
      IL_002e: stfld     int32 CLR_Ch10.Program/Employee::Age
  • 如果屬性實現了IEnumerable/IEnumerable<T>接口,就會被認為是集合,如果還重寫了Add方法,就可以直接用這種方式來初始化

    • Dictionary<string, int> table = new Dictionary<string, int>
      {
        { "Kelvin", 24 },
        { "Catherine", 24 },
        { "Aden", 1000 },
      };
    • 實際上就是由編譯器來調用Add方法(所以如果沒實現Add方法,就不可以這樣初始化)

    • IL_004f:  newobj     instance void class [System.Collections]System.Collections.Generic.Dictionary`2<string,int32>::.ctor()
      IL_0054: dup
      IL_0055: ldstr     "Kelvin"
      IL_005a: ldc.i4.s   24
      IL_005c: callvirt   instance void class [System.Collections]System.Collections.Generic.Dictionary`2<string,int32>::Add(!0,
      !1)
      IL_0061: nop
      IL_0062: dup
      IL_0063: ldstr     "Catherine"
      IL_0068: ldc.i4.s   24
      IL_006a: callvirt   instance void class [System.Collections]System.Collections.Generic.Dictionary`2<string,int32>::Add(!0,
      !1)
      IL_006f: nop
      IL_0070: dup
      IL_0071: ldstr     "Aden"
      IL_0076: ldc.i4     0x3e8
      IL_007b: callvirt   instance void class [System.Collections]System.Collections.Generic.Dictionary`2<string,int32>::Add(!0,
      !1)

性能問題

  • 對於簡單的get/set,JIT會將代碼內聯,避免了性能上的損失

    • 內聯:將方法(get/set)直接編譯到調用它的方法中,避免在運行時發生調用所產生的開銷

      • 會使編譯好的方法變大,但一般訪問器方法代碼不多

      • 可使本機代碼變得輕量,執行更快

可訪問性
  • 屬性本身的可訪問性限制必須小於/等於其訪問器方法(get/set)的可訪問性限制

  • 一般來說,其他變量如果不明確指出訪問權限,就是默認私有的。但對於屬性而言,它的取值/賦值方法就要求明確指定訪問權限,否則就默認與屬性本身整體保持一致的訪問修飾符

屬性與字段
  • 屬性不能作為out/ref參數傳給方法;字段可以

  • 屬性訪問效率低於一般方法;最好是通過定義自己的訪問器方法來訪問字段

  • 屬性返回引用可能並非指向對象狀態一部分/修改作用不到原始對象;字段總是在操控著原始對象

參考書目

  • 《CLR via C#》(第4版) Jeffrey Richter