← 筆記

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接口,就會被認為是集合,如果還重寫了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