DAY 27 特性

特性

屬性提供以宣告方式將資訊與程式碼相關聯的方法。
它們也可提供可重複使用的元素,以套用至各種不同的目標。
屬性是繼承自 Attribute 基底類別的類別。
繼承自 Attribute 的任何類別都能在其他程式碼片段上做為一種「標記」。

Attribute 有人叫做屬性,也有人叫做特性,為了與 Property 區隔,本系列文就叫特性,雖然本人習慣上是叫屬性。

特性是將額外內容透過反射的方式給予目標,根據官方的解釋,目標有以下這些

  • Assembly
  • 類別
  • 建構函式
  • Delegate - 委派
  • 列舉
  • Event - 事件
  • 欄位
  • GenericParameter
  • 介面
  • 方法
  • Module
  • 參數
  • 屬性
  • ReturnValue
  • 結構

簡單的示範一下特性的用法,並套用在我們的類別

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Member {
public int ID { get; set; }

[My (12)]
public string Name { get; set; }
public void Print () {
Console.WriteLine ($"ID = {ID}, Name = {Name}");
}
}
class MyAttribute : Attribute {
public int Length { get; set; }
public MyAttribute (int length) {
Length = length;
}
}

可以看到屬性 Name 上面多了 [My (12)],特性以 [] 括弧內容表示使用哪一個特性,並標住在目標上方。

在建立特性時,只要繼承 Attribute 即可當作特性,建議是加入 Attribute 當作名詞後綴,當你用這種方式取名,可以看到雖然特性名稱是 MyAttribute,但使用時可以省略 Attribute 字詞,當然也方便別人一眼就能認出你寫的就是特性。

如果只是單純的加特性,可以發現程式碼其實沒什麼變動,假如我們想要有一個來驗證屬性不大於 Length 的方法,如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyAttribute : Attribute {
public int Length { get; set; }
public MyAttribute (int length) {
Length = length;
}
public static bool IsValid (object obj, string prop) {
var property = obj.GetType ().GetProperty (prop);
if (property is null) return false;
var value = (object) property.GetValue (obj, null);
if (value is null) return false;
var attr = (MyAttribute) property.GetCustomAttribute (typeof (MyAttribute), false);
if (attr is null) return false;
var length = attr.Length;
return ((string) value).Length <= length;
}
}

請注意範例可能寫得不是很好,或者真實用法不是長這樣。

IsValid 方法就是

  1. 將參數的 obj 轉換成 Type 類型並且獲得與 prop 名稱一樣的屬性資訊 property,若無則傳回 false。

  2. 將 property 的值給予 value,若 value 為空則傳回 false。

  3. 在 property 的特性中取得名稱 MyAttribute 的特性 attr,若找不到符合特性則傳回 false。

  4. 比較特性 attr 中輸入的值,也就是 My (12) 的 12 是不是大於物件 obj 的屬性 prop 長度。

第三點中,因為特性不是只能有一個,但同特性只能有一個,所以在取特性時,也可以一次取出所有特性,GetCustomAttributes () 傳回的是陣列。

若要多個特性可以寫成

1
2
3
[My (12)]
[My2 (20)]
public string Name { get; set; }

或者是

1
2
[My (12), My2 (20)]
public string Name { get; set; }

再來就是使用這個驗證方法,如

1
2
3
4
5
6
7
8
9
10
11
var members = new List<Member> () {
new Member () { ID = 0, Name = "Mio" },
new Member () { ID = 1, Name = "Miffy" },
new Member () { ID = 2, Name = "Lulu" },
new Member () { ID = 3, Name = "NekoSan" },
new Member () { ID = 3, Name = "MooDoooooooooooooooooo" }
};

foreach (var member in members) {
Console.WriteLine ($"Name = {member.Name}, IsValid = {MyAttribute.IsValid (member, "Name")}");
}

特性的特性

有類別的類別,當然也有特性的特性囉,特性的特性可以限定特性使用在哪一個目標上,如

1
2
3
4
[AttributeUsage (AttributeTargets.Class)]
class MyAttribute : Attribute {
//...
}

可以看到瞬間出現紅字,只要多加AttributeTargets.Property就可以在我們的屬性上使用了。

一些小結論

特性的使用可以把一些額外的資訊附加在目標上,例如我忘記在哪邊看到的程式碼維護程度,把特性當作一個 TAG,讓維護的人知道這裡有多大的坑,並無實際操作特性內容。

感謝閱讀,今天的內容可能有誤,閱讀後請再三求證喔。

參考資料
屬性 - C# | Microsoft Docs
C#屬性(Attribute)用法實例解析 - 掃文資訊