23.1 一般規定
大部分的 C# 語言可讓程式設計人員指定程式中所定義實體的宣告式資訊。 例如,在類別中,方法的存取範圍是使用 method_modifier、public、 protectedinternal和 private來裝飾。
C# 可讓程式設計師發明新的宣告式資訊,稱為 屬性s。 程序設計人員接著可以將屬性附加至各種程序實體,並在運行時間環境中擷取屬性資訊。
注意:例如,架構可能會定義
HelpAttribute可以放在特定程式項目的屬性(例如類別和方法),以提供這些程式元素與其文件之間的對應。 結尾註釋
屬性是透過屬性類別的宣告來定義 (§23.2),屬性類別可以有位置和具名參數 (§23.2.3)。 屬性會使用屬性規格附加至 C# 程式中的實體 (§23.3),而且可以在執行階段擷取為屬性實例 (§23.4) 。
23.2 屬性類別
23.2.1 一般規定
衍生自抽象類的類別 System.Attribute,無論是直接或間接,都是 屬性類別。 屬性類別的宣告會定義可在程序實體上放置的新屬性類型。 依照慣例,屬性類別會以的後綴 Attribute命名。 屬性的使用可能會包含或省略此後綴。
泛型類別宣告不得做 System.Attribute 為直接或間接基類。
範例:
public class B : Attribute {} public class C<T> : B {} // Error – generic cannot be an attributeend 範例
23.2.2 屬性用法
屬性 AttributeUsage (§23.5.2) 可用來描述如何使用屬性類別。
AttributeUsage 具有位置參數 (§23.2.3) ,可讓屬性類別指定可以使用它的程式實體類型。
範例:下列範例會定義名為
SimpleAttribute的屬性類別,該類別只能放在 class_declaration 和 interface_declarations 上,並顯示屬性的Simple數個用法。[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)] public class SimpleAttribute : Attribute { ... } [Simple] class Class1 {...} [Simple] interface Interface1 {...}雖然這個屬性是以名稱
SimpleAttribute定義,但使用這個屬性時,Attribute可能會省略後綴,導致簡短名稱Simple。 因此,上述範例在語意上相當於下列內容[SimpleAttribute] class Class1 {...} [SimpleAttribute] interface Interface1 {...}end 範例
AttributeUsage 具有具名參數 (§23.2.3),稱為 AllowMultiple,指出是否可以針對指定的實體多次指定屬性。 如果 AllowMultiple 屬性類別為 true,則該屬性類別是 多用途屬性類別,而且可以在實體上指定多次。 如果 AllowMultiple 屬性類別為 false 或未指定,則該屬性類別為單一 使用屬性類別,而且最多可以在實體上指定一次。
範例:下列範例會定義名為
AuthorAttribute的多用途屬性類別,並顯示具有兩個 屬性用法的Author類別宣告:[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class AuthorAttribute : Attribute { public string Name { get; } public AuthorAttribute(string name) => Name = name; } [Author("Brian Kernighan"), Author("Dennis Ritchie")] class Class1 { ... }end 範例
AttributeUsage 有另一個具名參數 (§23.2.3),稱為 Inherited,指出在基底類別上指定屬性時,是否也會由衍生自該基底類別的類別繼承。 如果 Inherited 屬性類別為 true,則會繼承該屬性。 如果 Inherited 屬性類別為 false,則不會繼承該屬性。 如果未指定,則其預設值為 true。
屬性類別 X 沒有 AttributeUsage 附加屬性,如 中所示
class X : Attribute { ... }
相當於下列專案:
[AttributeUsage(
AttributeTargets.All,
AllowMultiple = false,
Inherited = true)
]
class X : Attribute { ... }
23.2.3 位置和命名參數
屬性類別可以有 位置參數s和 具名參數s。 屬性類別的每個公用實例建構函式都會定義該屬性類別的有效位置參數序列。 屬性類別的每個非靜態公用讀寫字段和屬性都會定義屬性類別的具名參數。 若要讓屬性定義具名參數,該屬性應同時具有公用 get 存取子和公用集合存取子。
範例:下列範例會定義名為
HelpAttribute的屬性類別,其具有一個位置參數,url以及一個具名參數Topic。 雖然它是非靜態和公用的,但屬性Url不會定義具名參數,因為它不是讀寫。 也會顯示此屬性的兩種用法:[AttributeUsage(AttributeTargets.Class)] public class HelpAttribute : Attribute { public HelpAttribute(string url) // url is a positional parameter { ... } // Topic is a named parameter public string Topic { get; set; } public string Url { get; } } [Help("http://www.mycompany.com/xxx/Class1.htm")] class Class1 { } [Help("http://www.mycompany.com/xxx/Misc.htm", Topic ="Class2")] class Class2 { }end 範例
23.2.4 屬性參數類型
屬性類別的位置參數和具名參數類型僅限於 屬性參數類型s,它們為:
- 下列其中一種類型:
bool、byte、、char、doublefloatint、、longsbyteshortstring、uint、 。ulongushort -
object類型。 -
System.Type類型。 - 列舉類型。
- 上述類型的單維陣列。
- 沒有其中一種類型的建構函式自變數或公用欄位,不得做為屬性規格中的位置或具名參數。
23.3 屬性規範
將先前定義的屬性套用至程式實體稱為 屬性規格。 屬性是針對程式實體指定的其他宣告式資訊片段。 屬性可以在全域範圍 (指定包含元件或模組上的屬性) ,以及針對 type_declarations (§14.7)、class_member_declarations (§15.3)、interface_member_declarations (§19.4)、struct_member_declarations (§16.3)、enum_member_declarations (§20.2)、accessor_declarations (§15.7.3)、event_accessor_declarations (§15.8) 指定屬性)、parameter_lists 的要素 (§15.6.2) 和 type_parameter_lists 的要素 (§15.2.3)。
屬性在 屬性區段s 中指定。 屬性區段是由一對方括弧所組成,以逗號分隔的清單括住一或多個屬性。 在這類清單中指定屬性的順序,以及附加至相同程序實體之區段的順序並不重要。 例如,屬性規格 [A][B]、 [B][A]、 [A, B]和 [B, A] 是相等的。
global_attributes
: global_attribute_section+
;
global_attribute_section
: '[' global_attribute_target_specifier attribute_list ']'
;
global_attribute_target_specifier
: global_attribute_target ':'
;
global_attribute_target
: identifier
;
attributes
: attribute_section+
;
attribute_section
: '[' attribute_target_specifier? attribute_list ']'
;
attribute_target_specifier
: attribute_target ':'
;
attribute_target
: identifier
| keyword
;
attribute_list
: attribute (',' attribute)* ','?
;
attribute
: attribute_name attribute_arguments?
;
attribute_name
: type_name
;
attribute_arguments
: '(' ')'
| '(' positional_argument_list (',' named_argument_list)? ')'
| '(' named_argument_list ')'
;
positional_argument_list
: positional_argument (',' positional_argument)*
;
positional_argument
: argument_name? attribute_argument_expression
;
named_argument_list
: named_argument (',' named_argument)*
;
named_argument
: identifier '=' attribute_argument_expression
;
attribute_argument_expression
: non_assignment_expression
;
針對生產global_attribute_target,且在下列文字中,標識符的拼字應該等於 assembly 或 module,其中相等是指在6.4.3 中定義的。 針對生產 attribute_target,且在下列文字中, 標識符 應具有不等於 assembly 或 module的拼字,使用與上述相同的相等定義。
屬性是由 attribute_name 和位置及具名自變數的選擇性清單所組成。 具名自變數之前的位置自變數(如果有的話)。 位置自變數是由 attribute_argument_expression所組成;具名自變數是由名稱所組成,後面接著等號,後面接著 attribute_argument_expression,一起受到與簡單指派相同的規則約束。 具名自變數的順序並不重要。
注意:為了方便起見,global_attribute_section和attribute_section中允許尾端逗號,就像array_initializer中允許的逗號一樣(17.7)。 結尾註釋
attribute_name會識別屬性類別。
當屬性置於全域層級時, 需要global_attribute_target_specifier 。 當global_attribute_target等於:
-
assembly— 目標為包含元件 -
module— 目標為包含模組
不允許其他global_attribute_target值。
標準化attribute_target名稱為 event、field、method、、paramproperty、、return、 type和 typevar。 這些目標名稱只能用於下列內容:
-
event— 事件。 -
field— 欄位。 類似欄位的事件(亦即沒有存取子的一個事件)(15.8.2)和自動實作的屬性(~15.7.4)也可以具有具有這個目標的屬性。 -
method— 建構函式、完成項、方法、運算子、屬性 get 和 set 存取子、索引器取得和設定存取子,以及事件新增和移除存取子。 類似欄位的事件(也就是沒有存取子的一個事件)也可以有具有這個目標的屬性。 -
param— 屬性集存取子、索引器集合存取子、事件加入和移除存取子,以及建構函式、方法和運算符中的參數。 -
property— 屬性和索引器。 -
return— 委派、方法、運算符、屬性 get 存取子,以及索引器 get 存取子。 -
type— 委派、類別、結構、列舉和介面。 -
typevar— 類型參數。
某些內容允許在多個目標上指定屬性。 程式可以藉由包含 attribute_target_specifier來明確指定目標。 如果沒有attribute_target_specifier會套用預設值,但attribute_target_specifier可用來確認或覆寫預設值。 內容會解析如下:
- 對於委派宣告上的屬性,默認目標為委派。 否則, 當attribute_target 等於:
-
type— 目標為委派 -
return— 目標為傳回值
-
- 對於方法宣告上的屬性,默認目標為方法。 否則, 當attribute_target 等於:
-
method— 目標為 方法 -
return— 目標為傳回值
-
- 對於運算符宣告上的屬性,默認目標為運算符。 否則, 當attribute_target 等於:
-
method— 目標為 運算子 -
return— 目標為傳回值
-
- 對於屬性或索引器宣告之 get 存取子宣告上的屬性,預設目標為相關聯的方法。 否則, 當attribute_target 等於:
-
method— 目標為相關聯的方法 -
return— 目標為傳回值
-
- 針對屬性或索引器宣告之 set 存取子上指定的屬性,預設目標為相關聯的方法。 否則, 當attribute_target 等於:
-
method— 目標為相關聯的方法 -
param— 目標為單一隱含參數
-
- 對於自動實作屬性宣告上的屬性,默認目標為 屬性。 否則, 當attribute_target 等於:
-
field— 目標為屬性的編譯程式產生的支援欄位
-
- 對於在省略 event_accessor_declarations 預設目標的事件宣告上指定的屬性為事件宣告。 否則, 當attribute_target 等於:
-
event— 目標為事件宣告 -
field— 目標為欄位 -
method— 目標為方法
-
- 如果事件宣告未省略 event_accessor_declarations 預設目標就是 方法。
-
method— 目標為相關聯的方法 -
param— 目標為單一參數
-
在所有其他內容中,允許包含 attribute_target_specifier ,但不需要。
範例:類別宣告可以包含或省略規範
type:[type: Author("Brian Kernighan")] class Class1 {} [Author("Dennis Ritchie")] class Class2 {}end 範例。
實作可以接受其他 attribute_target,其用途是定義實作。 無法辨識這類 attribute_target 的實作應該發出警告,並忽略包含 attribute_section。
依照慣例,屬性類別會以的後綴 Attribute命名。
attribute_name可以包含或省略此後綴。 具體來說, attribute_name 解析方式如下:
- 如果attribute_name最右邊的標識符是逐字標識碼(~6.4.3),則attribute_name會解析為type_name(~7.8)。 如果結果不是衍生自
System.Attribute的類型,就會發生編譯時期錯誤。 - 否則為
如果上述兩個步驟中只有一個步驟會產生衍生自 System.Attribute的類型,則該類型就是attribute_name的結果。 否則會發生編譯時期錯誤。
範例:如果同時找到屬性類別,且沒有這個後綴,則存在模棱兩可,併產生編譯時期錯誤結果。 如果attribute_name拼字,使其最右邊的標識碼是逐字標識碼(~6.4.3),則只會比對沒有後綴的屬性,因此能夠解析這類模棱兩可。 範例
[AttributeUsage(AttributeTargets.All)] public class Example : Attribute {} [AttributeUsage(AttributeTargets.All)] public class ExampleAttribute : Attribute {} [Example] // Error: ambiguity class Class1 {} [ExampleAttribute] // Refers to ExampleAttribute class Class2 {} [@Example] // Refers to Example class Class3 {} [@ExampleAttribute] // Refers to ExampleAttribute class Class4 {}顯示兩個名為
Example和ExampleAttribute的屬性類別。 屬性[Example]模棱兩可,因為它可以參考Example或ExampleAttribute。 使用逐字標識碼可讓在這類罕見情況下指定確切意圖。 屬性[ExampleAttribute]並不模棱兩可(不過,如果有名為ExampleAttributeAttribute的屬性名稱的話,它就會模棱兩可!)。 如果移除類別Example的宣告,則這兩個屬性都會參考名為ExampleAttribute的屬性類別,如下所示:[AttributeUsage(AttributeTargets.All)] public class ExampleAttribute : Attribute {} [Example] // Refers to ExampleAttribute class Class1 {} [ExampleAttribute] // Refers to ExampleAttribute class Class2 {} [@Example] // Error: no attribute named “Example” class Class3 {}end 範例
在同一個實體上多次使用單一使用屬性類別是編譯時期錯誤。
範例:範例
[AttributeUsage(AttributeTargets.Class)] public class HelpStringAttribute : Attribute { public HelpStringAttribute(string value) { Value = value; } public string Value { get; } } [HelpString("Description of Class1")] [HelpString("Another description of Class1")] // multiple uses not allowed public class Class1 {}會導致編譯時期錯誤,因為它嘗試使用
HelpString,這是單一使用屬性類別,在的Class1宣告上多次。end 範例
如果下列所有語句都成立,表達式 E 就是 attribute_argument_expression :
- 的
E類型是屬性參數類型 (§23.2.4)。 - 在編譯階段,的值
E可以解析為下列其中一項:- 常數值。
-
System.Type使用 typeof_expression指定非泛型型別、封閉式建構型別(~8.4.3)或未系結泛型型別(~8.4.4),但不是開放式型別(~8.4.3)取得的物件。 - attribute_argument_expression s 的單維陣列。
範例:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field)] public class TestAttribute : Attribute { public int P1 { get; set; } public Type P2 { get; set; } public object P3 { get; set; } } [Test(P1 = 1234, P3 = new int[]{1, 3, 5}, P2 = typeof(float))] class MyClass {} class C<T> { [Test(P2 = typeof(T))] // Error – T not a closed type. int x1; [Test(P2 = typeof(C<T>))] // Error – C<;T>; not a closed type. int x2; [Test(P2 = typeof(C<int>))] // Ok int x3; [Test(P2 = typeof(C<>))] // Ok int x4; }end 範例
在多個元件中宣告的類型屬性是由以未指定的順序結合其每個元件的屬性來決定。 如果相同的屬性放在多個元件上,它相當於在型別上多次指定該屬性。
範例:這兩個部分:
[Attr1, Attr2("hello")] partial class A {} [Attr3, Attr2("goodbye")] partial class A {}相當於下列單一宣告:
[Attr1, Attr2("hello"), Attr3, Attr2("goodbye")] class A {}end 範例
型別參數的屬性會以相同方式結合。
23.4 屬性實例
23.4.1 一般規定
屬性實例是一個實例,表示運行時間的屬性。 屬性是使用屬性類別、位置自變數和具名自變數來定義。 屬性實例是使用位置和具名自變數初始化之屬性類別的實例。
擷取屬性實例牽涉到編譯時間和運行時間處理,如下列子集所述。
23.4.2 屬性的編譯
使用屬性類別、positional_argument_list、T
- 請遵循編譯時間處理步驟,以編譯。 這些步驟會導致編譯時期錯誤,或判斷可在運行時間叫用的實例建構
CT函式。 - 如果沒有
C公用輔助功能,則會發生編譯時期錯誤。 -
Arg:- 讓我們
Name成為 。 -
Name應該識別 上的T非靜態讀寫公用字段或屬性。 如果沒有T這類欄位或屬性,則會發生編譯時期錯誤。
- 讓我們
- 如果 positional_argument_list
P內的任何值或 named_argument_listN內的其中一個值是類型System.String,且值格式不正確,如 Unicode 標準所定義,則編譯的值是否等於擷取的執行階段值 (§23.4.3) ,則會實作定義。注意:例如,包含高 Surrogate UTF-16 程式代碼單元的字串,不會緊接著低 Surrogate 程式代碼單位的格式不正確。 結尾註釋
- 將下列資訊(針對屬性的運行時間具現化)儲存在編譯程式所產生的元件輸出中,因為編譯包含 屬性的程式:屬性類別
T、上的實例建構CT函式、P和相關聯的程序實體N,並在編譯階段完全解析值。
23.4.3 屬性實例的執行階段擷取
使用 §23.4.2 中定義的術語,可以在執行階段使用下列步驟從元件T擷取以 、 C、 P和 N相關聯E的屬性實例A:
- 請遵循運行時間處理步驟,以使用編譯時期所決定的實例建構函式和值,執行
new T(P)。 這些步驟會導致例外狀況,或產生的O實例T。 - 針對中的每個
Arg,依序排列:- 讓我們
Name成為 。 如果Name無法識別 上的O非靜態公用讀寫字段或屬性,則會擲回例外狀況。 - 讓我們
Value評估結果。 - 如果
Name識別上的O欄位,請將此欄位設定為Value。 - 否則,Name 會識別 上的
O屬性。 將此屬性設定為 Value。 - 結果是
O,屬性類別T的實例,已使用 positional_argument_listP和 named_argument_listN初始化。
- 讓我們
注意:中儲存
T、C、、PN、(和與其建立E關聯)A的格式,以及用來指定E和擷取T、C、P、、NfromA(因此,在運行時間取得屬性實例的方式)的格式超出此規格的範圍。 結尾註釋
23.5 保留屬性
23.5.1 一般規定
許多屬性會以某種方式影響語言。 這些屬性可以包括:
-
System.AttributeUsageAttribute(§23.5.2),用來描述屬性類別的使用方式。 -
System.Diagnostics.ConditionalAttribute(§23.5.3),是一個多用途屬性類,用於定義條件方法和條件屬性類。 這個屬性會藉由測試條件式編譯符號來指出條件。 -
System.ObsoleteAttribute(§23.5.4),用於將成員標記為過時。 -
System.Runtime.CompilerServices.AsyncMethodBuilderAttribute(§23.5.5),用來建立非同步方法的工作產生器。 -
System.Runtime.CompilerServices.CallerLineNumberAttribute(§23.5.6.2)、System.Runtime.CompilerServices.CallerFilePathAttribute(§23.5.6.3) 和System.Runtime.CompilerServices.CallerMemberNameAttribute(§23.5.6.4),可用來提供選擇性參數呼叫內容的相關資訊。 -
System.Runtime.CompilerServices.EnumeratorCancellationAttribute(§23.5.8) ,用來指定非同步反覆運算器中取消權杖的參數。
可為 Null 的靜態分析屬性 (§23.5.7) 可以改善針對 Null 可能性和 Null 狀態所產生的警告正確性 (§8.9.5) 。
執行環境可能會提供影響 C# 程式執行的其他實作定義屬性。
23.5.2 AttributeUsage 屬性
屬性 AttributeUsage 可用來描述可以使用屬性類別的方式。
以 屬性裝飾的 AttributeUsage 類別應該直接或間接衍生自 System.Attribute。 否則,會發生編譯時期錯誤。
附註: 如需使用此屬性的範例,請參閱 §23.2.2。 結尾註釋
23.5.3 條件屬性
23.5.3.1 一般規定
屬性 Conditional 啟用 條件式方法s及 條件式屬性類別es的定義。
23.5.3.2 條件方法
以 Conditional 屬性裝飾的方法是條件式方法。 因此,每個條件式方法都會與其屬性中 Conditional 宣告的條件式編譯符號相關聯。
範例:
class Eg { [Conditional("ALPHA")] [Conditional("BETA")] public static void M() { // ... } }
Eg.M宣告為與兩個條件式編譯符號ALPHA和BETA相關聯的條件式方法。end 範例
如果在呼叫點定義一或多個相關聯的條件式編譯符號,則會包含條件式方法的呼叫,否則會省略呼叫。
條件式方法受限於下列限制:
- 條件式方法應該是class_declaration或struct_declaration中的方法。 如果在介面宣告中的方法上指定屬性,
Conditional就會發生編譯時期錯誤。 - 條件方法不得是屬性、索引器或事件的存取者。
- 條件式方法的傳回型
void別應為 。 - 條件式方法不得以
override修飾詞標示。 不過,條件式方法可以使用 修飾詞來標記virtual。 這類方法的覆寫是隱含的條件式,不得以 屬性明確標示Conditional。 - 條件式方法不得為介面方法的實作。 否則,會發生編譯時期錯誤。
- 條件式方法的參數不得為輸出參數。
注意:包含
AttributeUsage(§23.2.2) 的AttributeTargets.Method屬性通常可套用於屬性、索引器及事件的存取者。 上述限制禁止此屬性的使用Conditional。 結尾註釋
此外,如果從條件式方法建立委派,就會發生編譯時期錯誤。
範例:範例
#define DEBUG using System; using System.Diagnostics; class Class1 { [Conditional("DEBUG")] public static void M() { Console.WriteLine("Executed Class1.M"); } } class Class2 { public static void Test() { Class1.M(); } }
Class1.M宣告為條件式方法。Class2的Test方法會呼叫這個方法。 由於已定義條件式編譯符號DEBUG,如果Class2.Test呼叫 ,則會呼叫M。 如果未定義符號DEBUG,則Class2.Test不會呼叫Class1.M。end 範例
請務必瞭解,條件式方法的包含或排除是由呼叫點的條件式編譯符號所控制。
範例:在下列程式代碼中
// File Class1.cs: using System; using System.Diagnostics; class Class1 { [Conditional("DEBUG")] public static void F() { Console.WriteLine("Executed Class1.F"); } } // File Class2.cs: #define DEBUG class Class2 { public static void G() { Class1.F(); // F is called } } // File Class3.cs: #undef DEBUG class Class3 { public static void H() { Class1.F(); // F is not called } }類別
Class2和Class3每個都包含條件式方法Class1.F的呼叫,這是根據是否DEBUG定義的條件式方法。 由於此符號定義於 的內容Class2中,但不包含Class3中的F呼叫Class2,同時省略 中的F呼叫Class3。end 範例
在繼承鏈結中使用條件式方法可能會造成混淆。 透過 base的形式 base.M呼叫條件式方法,會受限於一般條件式方法呼叫規則。
範例:在下列程式代碼中
// File Class1.cs using System; using System.Diagnostics; class Class1 { [Conditional("DEBUG")] public virtual void M() => Console.WriteLine("Class1.M executed"); } // File Class2.cs class Class2 : Class1 { public override void M() { Console.WriteLine("Class2.M executed"); base.M(); // base.M is not called! } } // File Class3.cs #define DEBUG class Class3 { public static void Main() { Class2 c = new Class2(); c.M(); // M is called } }
Class2包含對其基類中定義的呼叫M。 省略此呼叫,因為基底方法會根據未定義的符號DEBUG存在來設定條件。 因此,方法只會寫入主控台 “ ”Class2.M executed明智地使用 pp_declaration可以消除此類問題。end 範例
23.5.3.3 條件屬性類別
以一或多個屬性裝飾的屬性類別 (Conditional) 是條件式屬性類別。 因此,條件屬性類別會與其屬性中 Conditional 宣告的條件式編譯符號相關聯。
範例:
[Conditional("ALPHA")] [Conditional("BETA")] public class TestAttribute : Attribute {}
TestAttribute宣告為與條件式編譯符號ALPHA和BETA相關聯的條件屬性類別。end 範例
如果在規範點定義了條件屬性的一個或多個關聯的條件編譯符號,則包括條件屬性的屬性規範 (§23.3),否則省略屬性規範。
請務必注意,條件屬性類別的屬性規格包含或排除是由規格點的條件式編譯符號所控制。
範例:在範例中
// File Test.cs: using System; using System.Diagnostics; [Conditional("DEBUG")] public class TestAttribute : Attribute {} // File Class1.cs: #define DEBUG [Test] // TestAttribute is specified class Class1 {} // File Class2.cs: #undef DEBUG [Test] // TestAttribute is not specified class Class2 {}類別
Class1和Class2都是以 屬性Test裝飾,這是根據是否DEBUG定義的條件式。 由於這個符號是在的內容Class1中定義,但不是Class2,因此會包含 上的Test屬性Class1規格,同時省略上的Test屬性規格Class2。end 範例
23.5.4 過時屬性
屬性 Obsolete 是用來標記不應該再使用的型別和型別成員。
如果程式使用以 Obsolete 屬性裝飾的類型或成員,編譯程式應發出警告或錯誤。 具體來說,如果未提供錯誤參數,或提供錯誤參數且具有 值 false,編譯程式應該發出警告。 如果指定 error 參數並且其值為 true,編譯器應該發出錯誤。
範例:在下列程式代碼中
[Obsolete("This class is obsolete; use class B instead")] class A { public void F() {} } class B { public void F() {} } class Test { static void Main() { A a = new A(); // Warning a.F(); } }類別
A是以 屬性裝飾Obsolete。 每次使用A時Main,都會產生包含指定訊息的警告:「這個類別已過時;請改用 類別B」。end 範例
23.5.5 AsyncMethodBuilder 屬性
此屬性描述於 •15.14.1。
23.5.6 來電者資訊屬性
23.5.6.1 一般規定
基於記錄和報告等用途,函式成員有時有助於取得呼叫程式代碼的特定編譯時間資訊。 呼叫端資訊屬性提供透明傳遞這類資訊的方式。
當選擇性參數以其中一個呼叫端資訊屬性加上批注時,省略呼叫中的對應自變數不一定會導致替代預設參數值。 相反地,如果呼叫內容的相關指定資訊可供使用,該資訊將會當做自變數值傳遞。
範例:
public void Log( [CallerLineNumber] int line = -1, [CallerFilePath] string path = null, [CallerMemberName] string name = null ) { Console.WriteLine((line < 0) ? "No line" : "Line "+ line); Console.WriteLine((path == null) ? "No file path" : path); Console.WriteLine((name == null) ? "No member name" : name); }沒有自變數的呼叫
Log()會列印呼叫的行號和檔案路徑,以及呼叫發生所在的成員名稱。end 範例
呼叫端資訊屬性可以發生在任何位置的選擇性參數上,包括委派宣告中。 不過,特定呼叫端資訊屬性會限制他們可以屬性的參數類型,因此一律會有從替代值到參數類型的隱含轉換。
在部分方法宣告的定義和實作部分的參數上具有相同呼叫端資訊屬性是錯誤的。 只會套用定義元件中的呼叫端資訊屬性,而只會忽略實作元件中發生的呼叫端資訊屬性。
呼叫端資訊不會影響多載解析。 由於屬性化選擇性參數仍會從呼叫端的原始碼中省略,多載解析會以忽略其他省略的選擇性參數(~12.6.4)的方式忽略這些參數。
只有在原始碼中明確叫用函式時,才會取代呼叫端資訊。 隱含調用,例如隱含父建構函式呼叫沒有來源位置,而且不會取代呼叫端資訊。 此外,動態系結的呼叫將不會取代呼叫端資訊。 在這種情況下,當省略呼叫端資訊屬性化參數時,會改用參數的指定預設值。
其中一個例外狀況是查詢表達式。 這些會被視為語法擴充,如果呼叫擴充以省略具有呼叫端資訊屬性的選擇性參數,則會取代呼叫端資訊。 使用的位置是從中產生呼叫的查詢子句位置。
如果指定的參數上指定了多個呼叫端資訊屬性,則會依下列順序辨識這些屬性:CallerLineNumber、、 CallerFilePathCallerMemberName。 請考慮下列參數宣告:
[CallerMemberName, CallerFilePath, CallerLineNumber] object p = ...
CallerLineNumber 會優先,而其他兩個屬性則會忽略。 如果 CallerLineNumber 省略, CallerFilePath 則會優先,而且 CallerMemberName 會被忽略。 這些屬性的語彙順序無關緊要。
23.5.6.2 CallerLineNumber 屬性
當從常數值System.Runtime.CompilerServices.CallerLineNumberAttribute到參數類型的標準隱含轉換(~10.4.2)時,選擇性參數允許屬性int.MaxValue。 這可確保任何不負數的行號可以傳遞至該值,而不會發生錯誤。
如果原始碼中某個位置的函式調用會省略具有 CallerLineNumberAttribute的選擇性參數,則表示該位置行號的數值常值會當做調用的自變數,而不是預設參數值。
如果調用跨越多行,選擇的行是實作相依的。
行號可能會受到 #line 指示詞的影響(~6.5.8)。
23.5.6.3 CallerFilePath 屬性
當從標準隱含轉換 (~10.4.2System.Runtime.CompilerServices.CallerFilePathAttribute
如果原始碼中某個位置的函式調用會省略具有 CallerFilePathAttribute的選擇性參數,則表示該位置檔案路徑的字串常值會當做調用的自變數,而不是預設參數值。
檔案路徑的格式與實作相依。
檔案路徑可能會受到 #line 指示詞的影響(~6.5.8)。
23.5.6.4 CallerMemberName 屬性
當從標準隱含轉換 (~10.4.2System.Runtime.CompilerServices.CallerMemberNameAttribute
如果函式調用從函式成員主體內的位置,或套用至函式成員本身或其傳回型別的屬性內的位置,則原始程式碼中的參數或類型參數會省略選擇性參數, CallerMemberNameAttribute則代表該成員名稱的字元串常值會用來做為調用的自變數,而不是預設參數值。
對於在泛型方法內發生的調用,只會使用方法名稱本身,而不使用類型參數清單。
對於在明確介面成員實作內發生的調用,只會使用方法名稱本身,而沒有上述介面限定性。
對於在屬性或事件存取子內發生的調用,所使用的成員名稱是屬性或事件本身的成員名稱。
對於在索引子存取子內發生的調用,使用的成員名稱是由索引子成員 (IndexerNameAttribute) 所提供的成員名稱,如果有的話,或預設名稱Item。
針對在欄位或事件初始化表達式內發生的調用,所使用的成員名稱是正在初始化的欄位或事件名稱。
對於實例建構函式宣告內發生的調用,靜態建構函式、完成項和運算符所使用的成員名稱是實作相依的。
23.5.7 程式碼分析屬性
23.5.7.1 一般規定
此子句中的屬性用於提供額外資訊,以支援能進行可空性和空狀態診斷的編譯器(§8.9.5)。 編譯程式不需要執行任何 Null 狀態診斷。 這些屬性的存在或不存在不會影響語言或程序的行為。 未提供 Null 狀態診斷的編譯程式應該讀取並忽略這些屬性的存在。 提供空狀態診斷的編譯器應針對其用來通知其診斷的任何這些屬性使用本子句中定義的含義。
程式代碼分析屬性會在命名空間 System.Diagnostics.CodeAnalysis中宣告。
| 屬性 | 意義 |
|---|---|
AllowNull (第 23.5.7.2 條) |
不可為 Null 的自變數可能是 Null。 |
DisallowNull (第 23.5.7.3 條) |
可為 Null 的自變數絕不應為 Null。 |
MaybeNull (第 23.5.7.6 條) |
不可為 Null 的傳回值可能是 Null。 |
NotNull (第 23.5.7.8 條) |
可為 Null 的傳回值永遠不會是 Null。 |
MaybeNullWhen (第 23.5.7.7 條) |
當方法傳回指定的 bool 值時,不可為 Null 的引數可能是 null。 |
NotNullWhen (第 23.5.7.10 條) |
當方法傳回指定的 bool 值時,可為 Null 的自變數不會是 Null。 |
NotNullIfNotNull (第 23.5.7.9 條) |
如果指定參數的自變數不是 Null,則傳回值不是 Null。 |
DoesNotReturn (第 23.5.7.4 條) |
這個方法永遠不會傳回。 |
DoesNotReturnIf (第 23.5.7.5 條) |
如果關聯的 bool 參數具有指定的值,這個方法永遠不會傳回 。 |
§23.5.7.1 中的以下子條款具有條件規範性。
23.5.7.2 AllowNull 屬性
指定即使對應的類型不允許 Null 值,還是允許 Null 值做為輸入。
範例:請考慮下列永遠不會傳
null回的讀取/寫入屬性,因為它具有合理的預設值。 不過,用戶可以將 null 提供給 set 存取子,將 屬性設定為該預設值。#nullable enable public class X { [AllowNull] public string ScreenName { get => _screenName; set => _screenName = value ?? GenerateRandomScreenName(); } private string _screenName = GenerateRandomScreenName(); private static string GenerateRandomScreenName() => ...; }假設下列使用該屬性的 set 存取子
var v = new X(); v.ScreenName = null; // may warn without attribute AllowNull如果沒有 屬性,編譯程式可能會產生警告,因為不可為 Null 的型別屬性似乎設定為 Null 值。 屬性的存在會隱藏該警告。 end 範例
23.5.7.3 DisallowNull 屬性
指定即使對應的類型允許 Null 值,也不允許做為輸入。
範例:請考慮下列屬性,其中 null 是預設值,但用戶端只能將它設定為非 Null 值。
#nullable enable public class X { [DisallowNull] public string? ReviewComment { get => _comment; set => _comment = value ?? throw new ArgumentNullException(nameof(value), "Cannot set to null"); } private string? _comment = default; }get 存取子可能會傳回
null的預設值,因此編譯程式可能會警告它必須在存取之前加以檢查。 此外,它會警告來電者,即使它可以是 Null,呼叫者不應該明確地將它設定為 null。 end 範例
23.5.7.4 DoesNotReturn 屬性
指定指定的方法永遠不會傳回。
範例:請考慮下列事項:
public class X { [DoesNotReturn] private void FailFast() => throw new InvalidOperationException(); public void SetState(object? containedField) { if ((!isInitialized) || (containedField == null)) { FailFast(); } // null check not needed. _field = containedField; } private bool isInitialized = false; private object _field; }屬性的存在可透過多種方式協助編譯程式。 首先,如果有可能存在一條方法結束但不擲回例外的路徑,編譯器可能會發出警告。 其次,編譯程式可以在呼叫該方法之後,抑制程式碼中的任何可空警告,直到找到適當的 catch 子句為止。 第三,無法連線的程式代碼不會影響任何 Null 狀態。
屬性不會根據此屬性的存在而變更可觸達性 (~13.2) 或明確的指派 (\9.4) 分析。 它只會用來影響可為 Null 的警告。 end 範例
23.5.7.5 DoesNotReturnIf 屬性
指定當相關聯的 bool 參數具有指定的值時,指定的方法永遠不會傳回 。
範例:請考慮下列事項:
#nullable enable public class X { private void ThrowIfNull([DoesNotReturnIf(true)] bool isNull, string argumentName) { if (!isNull) { throw new ArgumentException(argumentName, $"argument {argumentName} can't be null"); } } public void SetFieldState(object containedField) { ThrowIfNull(containedField == null, nameof(containedField)); // unreachable code when "isInitialized" is false: _field = containedField; } private bool isInitialized = false; private object _field = default!; }end 範例
23.5.7.6 MaybeNull 屬性
指定不可為 Null 的傳回值可以是 Null。
範例:請考慮下列泛型方法:
#nullable enable public T? Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }此程式代碼的概念是,如果
T取代string為 ,T?就會變成可為 Null 的註釋。 不過,此程式代碼不合法,因為T不受限制為參考型別。 不過,新增此屬性可解決問題:#nullable enable [return: MaybeNull] public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }屬性會通知呼叫端合約隱含不可為 Null 的類型,但傳回值實際上可能是
null。 end 範例
23.5.7.7 MaybeNullWhen 屬性
指定當方法傳回指定的null值時,可能是bool不可為 Null 的自變數。 這類似於 MaybeNull 屬性 (§23.5.7.6) ,但包含指定傳回值的參數。
23.5.7.8 NotNull 屬性
指定如果方法傳回 (而不是擲回),則永遠不會 null 有可為 Null 的值。
範例:請考慮下列事項:
#nullable enable public static void ThrowWhenNull([NotNull] object? value, string valueExpression = "") => _ = value ?? throw new ArgumentNullException(valueExpression); public static void LogMessage(string? message) { ThrowWhenNull(message, nameof(message)); Console.WriteLine(message.Length); }當啟用可空引用型別時,方法
ThrowWhenNull會無警告地編譯。 當該方法傳回時,value自變數保證不是null。 不過,使用 Null 參考進行呼叫ThrowWhenNull是可以接受的。 end 範例
23.5.7.9 NotNullIfNotNull 屬性
指定如果指定的參數的自變數不是 ,則傳回值 null 不是 null。
範例:傳回值的 Null 狀態可能取決於一或多個自變數的 Null 狀態。 若某些參數不等於
null且方法始終傳回非 Null 值,可使用NotNullIfNotNull屬性以協助編譯程式進行分析。 請考慮下列 方法:#nullable enable string GetTopLevelDomainFromFullUrl(string url) { ... }
url如果自變數不是null,null則不會傳回 。 啟用可為 Null 的參考時,該簽章會正確運作,前提是 API 永遠不會接受 Null 自變數。 不過,如果自變數可以是 Null,則傳回值也可以是 Null。 若要正確表示該合約,請標註此方法,如下所示:#nullable enable [return: NotNullIfNotNull("url")] string? GetTopLevelDomainFromFullUrl(string? url) { ... }end 範例
23.5.7.10 NotNullWhen 屬性
指定當方法傳回指定的null值時,bool不會有可為 Null 的自變數。
範例:當自變數為
String.IsNullOrEmpty(String)或空字串時,連結庫方法true會null傳回 。 這是 Null 檢查的形式:如果方法傳false回 ,呼叫端就不需要 null 檢查自變數。 若要讓類似這個可為 Null 的方法感知,請將參數類型設為可為 Null 的參考型別,並新增 NotNullWhen 屬性:#nullable enable bool IsNullOrEmpty([NotNullWhen(false)] string? value) { ... }end 範例
23.5.8 EnumeratorCancellation 屬性
指定代表非同步反覆運算器的參數 CancellationToken (§15.15)。 此參數的引數應與傳遞給 IAsyncEnumerable<T>.GetAsyncEnumerator(CancellationToken)的引數結合。 此組合令牌應由 (IAsyncEnumerator<T>.MoveNextAsync()) 輪詢。 這些代幣應合併為一個代幣,就像 和 CancellationToken.CreateLinkedTokenSource 其Token屬性一樣。 如果取消兩個來源權杖中的任何一個,則會取消合併的權杖。 組合權杖會被視為該方法主體中非同步反覆運算器方法 (§15.15) 的引數。
如果屬性套用至多個參數,則 System.Runtime.CompilerServices.EnumeratorCancellation 為錯誤。 在下列情況下,編譯器可能會產生警告:
- 屬性
EnumeratorCancellation會套用至類型以外的參數CancellationToken,而不是 , - 或者,如果
EnumeratorCancellation屬性套用至非非同步反覆專案器 (§15.15) 的方法上的參數, - 或者,如果屬性套用至傳回非同步列舉介面 (
EnumeratorCancellation) 的方法上的參數,而不是非同步列舉器介面 (§15.15.2) 。
當沒有屬性具有此參數時,CancellationToken迭代器將無法存取GetAsyncEnumerator引數。
範例:方法
GetStringsAsync()是一個非同步迭代器。 在執行任何工作以擷取下一個值之前,它會檢查取消權杖,以判斷是否應該取消反覆專案。 如果要求取消,則不會採取進一步的動作。public static async Task ExampleCombination() { var sourceOne = new CancellationTokenSource(); var sourceTwo = new CancellationTokenSource(); await using (IAsyncEnumerator<string> enumerator = GetStringsAsync(sourceOne.Token).GetAsyncEnumerator(sourceTwo.Token)) { while (await enumerator.MoveNextAsync()) { string number = enumerator.Current; if (number == "8") sourceOne.Cancel(); if (number == "5") sourceTwo.Cancel(); Console.WriteLine(number); } } } static async IAsyncEnumerable<string> GetStringsAsync( [EnumeratorCancellation] CancellationToken token) { for (int i = 0; i < 10; i++) { if (token.IsCancellationRequested) yield break; await Task.Delay(1000, token); yield return i.ToString(); } }end 範例
23.6 互通屬性
若要與其他語言互操作,您可以使用索引屬性來實作索引器。 如果索引器沒有 IndexerName 屬性存在,則預設會使用名稱 Item 。 屬性 IndexerName 可讓開發人員覆寫此預設值,並指定不同的名稱。
範例:根據預設,索引器的名稱為
Item。 這可以覆寫,如下所示:[System.Runtime.CompilerServices.IndexerName("TheItem")] public int this[int index] { get { ... } set { ... } }現在,索引器的名稱是
TheItem。end 範例