22 個屬性
22.1 一般
大部分的 C# 語言可讓程式設計人員指定程式中所定義實體的宣告式資訊。 例如,在類別中,方法的存取範圍是使用 method_modifier、public
、 protected
internal
和 private
來裝飾。
C# 可讓程式設計人員發明新的宣告式資訊,稱為 屬性。 程序設計人員接著可以將屬性附加至各種程序實體,並在運行時間環境中擷取屬性資訊。
注意:例如,架構可能會定義
HelpAttribute
可以放在特定程式項目的屬性(例如類別和方法),以提供這些程式元素與其文件之間的對應。 end note
屬性是透過屬性類別({22.2)的宣告來定義,這些類別可以具有位置參數和具名參數({22.2.3)。 屬性會使用屬性規格將屬性附加至 C# 程式中的實體(~22.3),而且可以在運行時間擷取為屬性實例(~22.4)。
22.2 屬性類別
22.2.1 一般
衍生自抽象類的類別 System.Attribute
,無論是直接或間接,都是 屬性類別。 屬性類別的宣告會定義可在程序實體上放置的新屬性類型。 依照慣例,屬性類別會以的後綴 Attribute
命名。 屬性的使用可能會包含或省略此後綴。
泛型類別宣告不得做 System.Attribute
為直接或間接基類。
範例:
public class B : Attribute {} public class C<T> : B {} // Error – generic cannot be an attribute
end 範例
22.2.2 屬性使用方式
屬性 AttributeUsage
(~22.5.2) 用來描述屬性類別的使用方式。
AttributeUsage
具有位置參數 (~22.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
具有名為 的具名參數 (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
有另一個具名參數 (~22.2.3),稱為 Inherited
,指出屬性在基類上指定時,也會由衍生自該基類的類別繼承。 如果 Inherited
屬性類別為 true,則會繼承該屬性。 如果 Inherited
屬性類別為 false,則不會繼承該屬性。 如果未指定,則其預設值為 true。
屬性類別 X
沒有 AttributeUsage
附加屬性,如 中所示
class X : Attribute { ... }
相當於下列專案:
[AttributeUsage(
AttributeTargets.All,
AllowMultiple = false,
Inherited = true)
]
class X : Attribute { ... }
22.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 範例
22.2.4 屬性參數類型
屬性類別的位置和具名參數類型僅限於 屬性參數類型,也就是:
- 下列其中一種類型:
bool
、byte
、、char
、double
float
int
、、long
sbyte
short
string
、uint
、 。ulong
ushort
-
object
類型。 -
System.Type
類型。 - 列舉類型。
- 上述類型的單維陣列。
- 沒有其中一種類型的建構函式自變數或公用欄位,不得做為屬性規格中的位置或具名參數。
22.3 屬性規格
屬性規格 是將先前定義的屬性套用至程序實體。 屬性是針對程式實體指定的其他宣告式資訊片段。 可以在全域範圍指定屬性(指定包含元件或模組上的屬性),以及針對 type_declarations (14.7)、class_member_declaration s (~15.3)、interface_member_declarations (s)。指定屬性 18.4)、struct_member_declaration秒(~16.3)、enum_member_declaration秒(~19.2)、accessor_declaration秒(~15.7.3)、event_accessor_宣告s ({15.8)、parameter_lists 的元素({15.6.2),以及 type_parameter_lists ({15.2.3) 的元素。
屬性會在屬性區段中指定。 屬性區段是由一對方括弧所組成,以逗號分隔的清單括住一或多個屬性。 在這類清單中指定屬性的順序,以及附加至相同程序實體之區段的順序並不重要。 例如,屬性規格 [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 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_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)。 end note
attribute_name會識別屬性類別。
當屬性置於全域層級時, 需要global_attribute_target_specifier 。 當global_attribute_target等於:
-
assembly
— 目標為包含元件 -
module
— 目標為包含模組
不允許其他global_attribute_target值。
標準化attribute_target名稱為 event
、field
、method
、、param
property
、、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
別是屬性參數類型(~22.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 範例
型別參數的屬性會以相同方式結合。
22.4 屬性實例
22.4.1 一般
屬性實例是一個實例,表示運行時間的屬性。 屬性是使用屬性類別、位置自變數和具名自變數來定義。 屬性實例是使用位置和具名自變數初始化之屬性類別的實例。
擷取屬性實例牽涉到編譯時間和運行時間處理,如下列子集所述。
22.4.2 屬性的編譯
使用屬性類別、positional_argument_list、E
A
- 請遵循編譯時間處理步驟,以編譯
T(P)
。 這些步驟會導致編譯時期錯誤,或判斷可在運行時間叫用的實例建構C
T
函式。 - 如果沒有
C
公用輔助功能,則會發生編譯時期錯誤。 -
Arg
N
:- 讓我們
Name
成為Arg
。 -
Name
應該識別 上的T
非靜態讀寫公用字段或屬性。 如果沒有T
這類欄位或屬性,則會發生編譯時期錯誤。
- 讓我們
- 如果positional_argument_list
System.String
注意:例如,包含高 Surrogate UTF-16 程式代碼單元的字串,不會緊接著低 Surrogate 程式代碼單位的格式不正確。 end note
- 將下列資訊(針對屬性的運行時間具現化)儲存在編譯程式所產生的元件輸出中,因為編譯包含 屬性的程式:屬性類別
T
、上的實例建構C
T
函式、P
N
和相關聯的程序實體E
,並在編譯階段完全解析值。
22.4.3 屬性實例的運行時間擷取
使用在 \22.4.2 中A
實例:
- 請遵循運行時間處理步驟,以使用編譯時期所決定的實例建構函式和值,執行
C
。 這些步驟會導致例外狀況,或產生的O
實例T
。 - 針對中的每個
Arg
N
,依序排列:- 讓我們
Name
成為Arg
。 如果Name
無法識別 上的O
非靜態公用讀寫字段或屬性,則會擲回例外狀況。 - 讓我們
Value
評估Arg
結果。 - 如果
Name
識別上的O
欄位,請將此欄位設定為Value
。 - 否則,Name 會識別 上的
O
屬性。 將此屬性設定為 Value。 - 結果是
O
,屬性類別T
的實例,已使用 positional_argument_listP
和 named_argument_listN
初始化。
- 讓我們
注意:中儲存
T
、C
、、P
N
、(和與其建立E
關聯)A
的格式,以及用來指定E
和擷取T
、C
、P
、、N
fromA
(因此,在運行時間取得屬性實例的方式)的格式超出此規格的範圍。 end note
範例:在 CLI 的實作中,
Help
編譯在 \22.2.3 中編譯範例程式所建立元件中的屬性實例,可以使用下列程式來擷取:public sealed class InterrogateHelpUrls { public static void Main(string[] args) { Type helpType = typeof(HelpAttribute); string assemblyName = args[0]; foreach (Type t in Assembly.Load(assemblyName).GetTypes()) { Console.WriteLine($"Type : {t}"); var attributes = t.GetCustomAttributes(helpType, false); var helpers = (HelpAttribute[]) attributes; foreach (var helper in helpers) { Console.WriteLine($"\tUrl : {helper.Url}"); } } } }
end 範例
22.5 保留的屬性
22.5.1 一般
許多屬性會以某種方式影響語言。 這些屬性可以包括:
-
System.AttributeUsageAttribute
(~22.5.2),用來描述可以使用屬性類別的方式。 -
System.Diagnostics.ConditionalAttribute
(~22.5.3),是一種多用途屬性類別,可用來定義條件方法和條件屬性類別。 這個屬性會藉由測試條件式編譯符號來指出條件。 -
System.ObsoleteAttribute
(~22.5.4),用來將成員標示為過時。 -
System.Runtime.CompilerServices.AsyncMethodBuilderAttribute
(~22.5.5),用來建立異步方法的工作產生器。 -
System.Runtime.CompilerServices.CallerLineNumberAttribute
(~22.5.6.2)、System.Runtime.CompilerServices.CallerFilePathAttribute
(~22.5.6.3)和System.Runtime.CompilerServices.CallerMemberNameAttribute
(22.5.6.4),用來提供呼叫內容的相關信息給選擇性參數。
可為 Null 的靜態分析屬性 (~22.5.7) 可以改善針對 Null 能力和 Null 狀態所產生的警告正確性(~8.9.5)。
執行環境可能會提供影響 C# 程式執行的其他實作定義屬性。
22.5.2 AttributeUsage 屬性
屬性 AttributeUsage
可用來描述可以使用屬性類別的方式。
以 屬性裝飾的 AttributeUsage
類別應該直接或間接衍生自 System.Attribute
。 否則,會發生編譯時期錯誤。
注意:如需使用此屬性的範例,請參閱 <22.2.2。2>。 end note
22.5.3 條件屬性
22.5.3.1 一般
屬性Conditional
會啟用條件式方法和條件屬性類別的定義。
22.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
。 - 條件式方法不得為介面方法的實作。 否則,會發生編譯時期錯誤。
- 條件式方法的參數不得為輸出參數。
此外,如果從條件式方法建立委派,就會發生編譯時期錯誤。
範例:範例
#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 範例
22.5.3.3 條件屬性類別
以一或多個屬性裝飾的屬性類別 (Conditional
) 是條件屬性類別。 因此,條件屬性類別會與其屬性中 Conditional
宣告的條件式編譯符號相關聯。
範例:
[Conditional("ALPHA")] [Conditional("BETA")] public class TestAttribute : Attribute {}
TestAttribute
宣告為與條件式編譯符號ALPHA
和BETA
相關聯的條件屬性類別。end 範例
如果在規格點定義了一或多個相關聯的條件式編譯符號,則會包含條件式屬性的屬性規格(~22.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 範例
22.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 範例
22.5.5 AsyncMethodBuilder 屬性
此屬性描述於 •15.15.1。
22.5.6 話叫端資訊屬性
22.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
、、 CallerFilePath
CallerMemberName
。 請考慮下列參數宣告:
[CallerMemberName, CallerFilePath, CallerLineNumber] object p = ...
CallerLineNumber
會優先,而其他兩個屬性則會忽略。 如果 CallerLineNumber
省略, CallerFilePath
則會優先,而且 CallerMemberName
會被忽略。 這些屬性的語彙順序無關緊要。
22.5.6.2 CallerLineNumber 屬性
當從常數值System.Runtime.CompilerServices.CallerLineNumberAttribute
到參數類型的標準隱含轉換(~10.4.2)時,選擇性參數允許屬性int.MaxValue
。 這可確保任何不負數的行號可以傳遞至該值,而不會發生錯誤。
如果原始碼中某個位置的函式調用會省略具有 CallerLineNumberAttribute
的選擇性參數,則表示該位置行號的數值常值會當做調用的自變數,而不是預設參數值。
如果調用跨越多行,選擇的行是實作相依的。
行號可能會受到 #line
指示詞的影響(~6.5.8)。
22.5.6.3 CallerFilePath 屬性
當從標準隱含轉換 (~10.4.2string
如果原始碼中某個位置的函式調用會省略具有 CallerFilePathAttribute
的選擇性參數,則表示該位置檔案路徑的字串常值會當做調用的自變數,而不是預設參數值。
檔案路徑的格式與實作相依。
檔案路徑可能會受到 #line
指示詞的影響(~6.5.8)。
22.5.6.4 CallerMemberName 屬性
當從標準隱含轉換 (~10.4.2string
如果函式調用從函式成員主體內的位置,或套用至函式成員本身或其傳回型別的屬性內的位置,則原始程式碼中的參數或類型參數會省略選擇性參數, CallerMemberNameAttribute
則代表該成員名稱的字元串常值會用來做為調用的自變數,而不是預設參數值。
對於在泛型方法內發生的調用,只會使用方法名稱本身,而不使用類型參數清單。
對於在明確介面成員實作內發生的調用,只會使用方法名稱本身,而沒有上述介面限定性。
對於在屬性或事件存取子內發生的調用,所使用的成員名稱是屬性或事件本身的成員名稱。
如果是在索引器存取子內發生的調用,使用的成員名稱是索引IndexerNameAttribute
器成員上提供的成員名稱,如果存在,則為 ,否則為默認名稱Item
。
針對在欄位或事件初始化表達式內發生的調用,所使用的成員名稱是正在初始化的欄位或事件名稱。
對於實例建構函式宣告內發生的調用,靜態建構函式、完成項和運算符所使用的成員名稱是實作相依的。
22.5.7 程式代碼分析屬性
22.5.7.1 一般
本節中的屬性可用來提供其他資訊,以支援提供可為 Null 和 Null 狀態診斷的編譯程式(~8.9.5)。 編譯程式不需要執行任何 Null 狀態診斷。 這些屬性的存在或不存在不會影響語言或程序的行為。 未提供 Null 狀態診斷的編譯程式應該讀取並忽略這些屬性的存在。 提供 Null 狀態診斷的編譯程式,應該使用本節中針對其用來通知其診斷的任何屬性所定義的意義。
程式代碼分析屬性會在命名空間 System.Diagnostics.CodeAnalysis
中宣告。
屬性 | 意義 |
---|---|
AllowNull (*22.5.7.2) |
不可為 Null 的自變數可能是 Null。 |
DisallowNull (~22.5.7.3) |
可為 Null 的自變數絕不應為 Null。 |
MaybeNull (*22.5.7.6) |
不可為 Null 的傳回值可能是 Null。 |
NotNull (~22.5.7.8) |
可為 Null 的傳回值永遠不會是 Null。 |
MaybeNullWhen (~22.5.7.7) |
當方法傳回指定的 bool 值時,不可為 Null 的引數可能是 null。 |
NotNullWhen (*22.5.7.10) |
當方法傳回指定的 bool 值時,可為 Null 的自變數不會是 Null。 |
NotNullIfNotNull (~22.5.7.9) |
如果指定參數的自變數不是 Null,則傳回值不是 Null。 |
DoesNotReturn (~22.5.7.4) |
這個方法永遠不會傳回。 |
DoesNotReturnIf (~22.5.7.5) |
如果關聯的 bool 參數具有指定的值,這個方法永遠不會傳回 。 |
下列在 22.5.7.1 中的各節是有條件的規範。
22.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 範例
22.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 範例
22.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 範例
22.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 範例
22.5.7.6 可能Null 屬性
指定不可為 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 範例
22.5.7.7 可能NullWhen 屬性
指定當方法傳回指定的null
值時,可能是bool
不可為 Null 的自變數。 這類似於 MaybeNull
屬性 (~22.5.7.6),但包含指定傳回值的參數。
22.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); }
啟用 Null 參考型別時,方法
ThrowWhenNull
會編譯而不出現警告。 當該方法傳回時,value
自變數保證不是null
。 不過,使用 Null 參考進行呼叫ThrowWhenNull
是可以接受的。 end 範例
22.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 範例
22.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 範例
22.6 互操作的屬性
若要與其他語言互操作,您可以使用索引屬性來實作索引器。 如果索引器沒有 IndexerName
屬性存在,則預設會使用名稱 Item
。 屬性 IndexerName
可讓開發人員覆寫此預設值,並指定不同的名稱。
範例:根據預設,索引器的名稱為
Item
。 這可以覆寫,如下所示:[System.Runtime.CompilerServices.IndexerName("TheItem")] public int this[int index] { get { ... } set { ... } }
現在,索引器的名稱是
TheItem
。end 範例