在 C# language specification 的 17.1.3 提到 Attribute parameter types 有以下的限制:
- One of the following types: bool, byte, char, double, float, int, long, sbyte, short, string, uint, ulong, ushort.
- The type object.
- The type System.Type.
- An enum type, provided it has public accessibility and the types in which it is nested (if any) also have public accessibility (§17.2)
- Single-dimensional arrays of the above types.
其中 1,2,3 大概沒甚麼問題,很明顯就是屬於這三項規範的型別是可以使用的,5 應該也還好,就是指以上四項規範的一維陣列可以使用;比較有趣的在第四點,翻成中文的意義就是『列舉型別,這個型別的存取範圍必須是公用的(也就是以 public 存取修飾詞宣告),若這個列舉型別是位在某個型別內部的巢狀型別,則該型別也必須是公用存取範圍。』
使用以下的程式碼,我刻意將 enum 與 attribute 都宣告為 internal,這程式碼會通過考驗嗎?
namespace ConsoleApp8
{
class Program
{
static void Main(string[] args)
{
var attribute = typeof(MyClass).GetCustomAttribute<SomeAttribute>();
Console.WriteLine(attribute.Current);
Console.ReadLine();
}
}
internal enum Status
{
On, Off
}
internal class SomeAttribute : Attribute
{
internal Status Current { get; set; }
internal SomeAttribute(Status current)
{
Current = current;
}
}
[Some(Status.Off)]
public class MyClass
{
}
}
各位可以嘗試看看,這個程式碼不僅可以通過編譯,還能夠正確執行。難道 C# 語言規範寫錯了嗎?
那問題出在哪裡?明明第四點寫的就是 public accessibility ,而程式碼明明就是 internal enum,這個巧妙就在於這個章節的名稱 『Attribute parameter types』。咱們先來宣告一個 custom attribute,如下:(為了避免誤會,所以直接顯式宣告其無參數建構式)。
internal class OtherAttribute : Attribute
{
public OtherAttribute()
{ }
public string Name { get; set; }
public int X;
}
建構式沒有任何參數宣告,那該如何在其他元素套用 OtherAttribute 時賦予 Name property 和 X field的值呢?我們可以用以下幾個方式套用這個 Attribute:
[Other]
public class MyClass1
{ }
[Other()]
public class MyClass2
{ }
[Other(Name = "國寶")]
public class MyClass3
{ }
[Other(X =1)]
public class MyClass4
{ }
[Other(Name="國寶",X = 1)]
public class MyClass5
{ }
類似 [Other(Name="國寶",X = 1)] 這樣的寫法在一般的類別建構式上是辦不到的,我指的是單純只有在類別內部設定該屬性,而建構式維持無參數的狀態,如果在建構式使用具名參數就是另外一回事了。事實上,在[Other(Name = "國寶", X = 1)] 中小括弧內部的那些也不全然只能放建構式的參數,這也就是為什麼會稱為 attribute parameter types 而不是叫做 attribute constructor parameter types。
Attribute parameters 其實分成兩種:positional parameter 和 named parameter (參考 C# language specification 17.1.2),簡單這麼解釋這兩樣的差別:
Positional parameter 顧名思義就是靠著位置辨識的參數,說白話一點就是宣告在建構式上的參數,因為建構式上的參數式靠位置決定的。例如以下 attribute 建構式上的 current 就是 positional parameter:
internal class SomeAttribute : Attribute
{
internal Status Current { get; set; }
internal SomeAttribute(Status current)
{
Current = current;
}
}
Named parameter 就是靠名稱來辨識的參數,例如 OtherAttribute 中的 Name 與 X:
internal class OtherAttribute : Attribute
{
public OtherAttribute()
{ }
public string Name { get; set; }
public int X;
}
這裡產生了一個問題,要能夠成為 named parameter 的成員 -- 執行個體屬性 (property) 或欄位 (field) ,它的存取範圍宣告必須是 public。如果我們將先前的範例改成以下,在編譯時期就會產生編譯錯誤 -- CS0617 'Name' 不是有效的具名屬性引數。
internal class OtherAttribute : Attribute
{
public OtherAttribute()
{ }
internal string Name { get; set; }
public int X;
}
[Other]
public class MyClass1
{ }
[Other()]
public class MyClass2
{ }
[Other(Name = "國寶")]
public class MyClass3
{ }
[Other(X =1)]
public class MyClass4
{ }
[Other(Name = "國寶", X = 1)]
public class MyClass5
{ }
當一個型別不是 public 的狀態,它是不能用於宣告為某個 public property 或 field的型別,也因此,若這個 enum 本身不是 public ,或是包裝著它的巢狀型別不是 public 時,這個 enum 無法用來宣告 public property 或 field,於是乎它就無法成為該 attribute 的 named parameter。這也就是 『An enum type, provided it has public accessibility and the types in which it is nested (if any) also have public accessibility 』這句話所要闡述的意義。