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 accessor 和 public set 或初始化的 accessor。
範例:下列範例會定義名為
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.8)、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)來指定)、local_function_declarations(§13.6.4)、parameter_lists 元素(§15.6.2)、type_parameter_lists 元素(§15.2.3)、lambda_expressions(§12.22.1),以及explicit_anonymous_function_parameters與implicit_anonymous_function_parameters元素(§12.22.1)。
屬性在 屬性區段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— 建構子;終結者;方法;操作員;本地函數、屬性取得、設定及初始化存取器;索引器取得、設定及初始化存取器;事件新增與移除存取器;以及 lambda 表達式。 類似欄位的事件(也就是沒有存取子的一個事件)也可以有具有這個目標的屬性。 -
param— 屬性集合與初始化存取器、索引器集合與初始化存取器、事件新增與移除存取者,以及建構子、方法、局部功能與運算子中的參數。 -
property— 屬性和索引器。 -
return— 一個代理、方法、本地函式、運算子、屬性 get accessor、indexer get accessor 以及 lambda 運算式。 -
type— 委派、類別、結構、列舉和介面。 -
typevar— 類型參數。
某些內容允許在多個目標上指定屬性。 程式可以藉由包含 attribute_target_specifier來明確指定目標。 如果沒有attribute_target_specifier會套用預設值,但attribute_target_specifier可用來確認或覆寫預設值。 內容會解析如下:
- 對於委派宣告上的屬性,默認目標為委派。 否則, 當attribute_target 等於:
-
type— 目標為委派 -
return— 目標為傳回值
-
- 對於方法宣告上的屬性,默認目標為方法。 否則, 當attribute_target 等於:
-
method— 目標為 方法 -
return— 目標為傳回值
-
- 對於本地函式宣告的屬性,預設目標是本地函式。 否則, 當attribute_target 等於:
-
method— 目標為局部函數 -
return— 目標為傳回值
-
- 對於運算符宣告上的屬性,默認目標為運算符。 否則, 當attribute_target 等於:
-
method— 目標為 運算子 -
return— 目標為傳回值
-
- 對於屬性或索引器宣告之 get 存取子宣告上的屬性,預設目標為相關聯的方法。 否則, 當attribute_target 等於:
-
method— 目標為相關聯的方法 -
return— 目標為傳回值
-
- 對於屬性或索引器宣告的設定或初始值存取器上的屬性,預設目標是相關的方法。 否則, 當attribute_target 等於:
-
method— 目標為相關聯的方法 -
param— 目標為單一隱含參數
-
- 對於自動實作屬性宣告上的屬性,默認目標為 屬性。 否則, 當attribute_target 等於:
-
field— 目標為屬性的編譯程式產生的支援欄位
-
- 對於在省略 event_accessor_declarations 預設目標的事件宣告上指定的屬性為事件宣告。 否則, 當attribute_target 等於:
-
event— 目標為事件宣告 -
field— 目標為欄位 -
method— 目標為方法
-
- 如果事件宣告未省略 event_accessor_declarations 預設目標就是 方法。
-
method— 目標為相關聯的方法 -
param— 目標為單一參數
-
- 對於 lambda_expression 上的屬性,預設目標是方法。 否則, 當attribute_target 等於:
-
method— 目標為 方法 -
return— 目標為傳回值
-
在所有其他內容中,允許包含 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) ,則會實作定義。注意:舉例來說,包含高代理 UTF-16 碼單元且後面沒有緊接低代碼單元的字串,則不是良構。 結尾註釋
- 將下列資訊(針對屬性的運行時間具現化)儲存在編譯程式所產生的元件輸出中,因為編譯包含 屬性的程式:屬性類別
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.CallerArgumentExpressionAttribute(§23.5.6.5)用於提供呼叫上下文的資訊給可選參數。 -
System.Runtime.CompilerServices.EnumeratorCancellationAttribute(§23.5.9),用於指定非同步迭代器中消去標記的參數。 -
System.Runtime.CompilerServices.ModuleInitializer(§23.5.10),用於標記方法為模組初始化器。 -
System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute以及System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute,分別用於宣告一個自訂插值字串表達式處理器(§23.5.10.1)以及呼叫其其中一個建構子。 - System.Diagnostics.CodeAnalysis.UnscopedRefAttribute(§23.5.8),允許原本隱含作用範圍的參考被視為未被作用範圍限制。
-
System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute(§23.5.11.1)及System.Runtime.CompilerServices.RequiredMemberAttribute(§23.5.11.2),用於必要成員情境(§15.7.1)。 -
System.Runtime.CompilerServices.CollectionBuilderAttribute(§23.5.12),該典禮指定一種集合類型具有收集建立方法。 -
System.Runtime.CompilerServices.InlineArrayAttribute(§23.5.13),將結構型別標記為內聯陣列型別(§16.6)。 -
System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute(§23.5.14),規定了超載解決時成員的優先權。
可為 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、 條件局部函數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 條件局部函數
靜態局部函數可以條件方式設計,與條件方法相同(§23.5.3.2)。
若非靜態的本地函式被設定為條件函式,則會發生編譯時錯誤。
23.5.3.4 條件屬性類別
以一或多個屬性裝飾的屬性類別 (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辨識:, CallerFilePath, CallerMemberNameCallerArgumentExpression 請考慮下列參數宣告:
[CallerMemberName, CallerFilePath, CallerLineNumber] object p = ...
CallerLineNumber 優先,其他三個屬性則被忽略。 若CallerLineNumber省CallerFilePath略,則CallerMemberNameCallerArgumentExpression優先,且將被忽略。 這些屬性的語彙順序無關緊要。
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的可選參數,則會使用代表該位置檔案路徑的 UTF-16 字串字串字面作為調用參數,而非預設參數值。
檔案路徑的格式與實作相依。
檔案路徑可能會受到 #line 指示詞的影響(~6.5.8)。
23.5.6.4 CallerMemberName 屬性
當從標準隱含轉換 (~10.4.2System.Runtime.CompilerServices.CallerMemberNameAttribute
如果從函式成員體內某處或套用到該函式成員本身、其回傳類型、參數或原始碼中屬性參數的函式調用,遺漏了帶有 CallerMemberNameAttribute的可選參數,則會使用代表該成員名稱的 UTF-16 字串字串作為調用參數,而非預設參數值。 (若是從頂層陳述(§7.1.3)調用函式,成員名稱即為實作所產生。)
對於在泛型方法內發生的調用,只會使用方法名稱本身,而不使用類型參數清單。
對於在明確介面成員實作內發生的調用,只會使用方法名稱本身,而沒有上述介面限定性。
對於在屬性或事件存取子內發生的調用,所使用的成員名稱是屬性或事件本身的成員名稱。
對於在索引子存取子內發生的調用,使用的成員名稱是由索引子成員 (IndexerNameAttribute) 所提供的成員名稱,如果有的話,或預設名稱Item。
針對在欄位或事件初始化表達式內發生的調用,所使用的成員名稱是正在初始化的欄位或事件名稱。
對於實例建構函式宣告內發生的調用,靜態建構函式、完成項和運算符所使用的成員名稱是實作相依的。
對於發生在本地函式或匿名函式中的調用,會使用呼叫該函式的成員方法名稱。
範例:請考慮下列事項:
class Program { static void Main() { F1(); Action anonymousFunction = () => F2(); anonymousFunction(); void F1([CallerMemberName] string? name = null) { Console.WriteLine($"F1 MemberName: |{name}|"); F2(); } static void F2([CallerMemberName] string? name = null) { Console.WriteLine($"F2 MemberName: |{name}|"); } } }產生輸出
F1 MemberName: |Main| F2 MemberName: |Main| F2 MemberName: |Main|此屬性提供呼叫函數成員的名稱,對於局部函數
F1而言,該函數為方法Main。 即使F2被 呼叫F1,但局部函數 並非 函數成員,因此該調用F2的報告呼叫者也是Main。 同樣地,當F2被指派給anonymousFunction的匿名函式呼叫時,報告的呼叫者就是Main呼叫該匿名函式的方法。 end 範例
23.5.6.5 CallerArgumentExpression 屬性
屬性 System.Runtime.CompilerServices.CallerArgumentExpressionAttribute 被套用到 目標參數,並可能導致將兄弟參數參數的原始碼文字擷取為字串,此處稱為 捕獲字串。
除非它是擴展方法中的第一個參數,否則目標參數必須有 default_argument。
請考慮以下方法宣告:
using System;
using System.Runtime.CompilerServices;
#nullable enable
class Test
{
public static void M(int val = 0, [CallerArgumentExpression("val")] string? text = null)
{
Console.WriteLine($"val = {val}, text = <{text}>");
}
}
其中目標參數為text,兄弟參數為 val,其對應參數的原始碼文字可在呼叫時擷取textM。
屬性建構子取一個型別 string為 的參數。 那條線
- 應包含兄弟參數的名稱;否則,該屬性會被忽略。
- 應省略帶有該前綴的參數名稱中的前導
@。
一個 parameter_list 可能包含多個目標參數。
目標參數的類型應有標準的轉換。string
註: 這表示不允許從 的
string使用者定義轉換,實務上這類參數的型別必須是string、object,或是由 實作string的介面。 結尾註釋
若針對目標參數傳遞明確參數,則不會擷取任何字串,該參數取用該參數的值。 否則,對應兄弟參數的參數文字會依照以下規則轉換成捕獲的字串:
- 前後的空白部分會在移除最外圍分組括號前後都被移除。
- 所有最外側的分組括號在移除前後的前置與後方空白部分都會被移除。
- 其他所有 input_elements 皆原字保留(包括留白、註解、 Unicode_Escape_Sequences 及
@識別碼前綴)。
捕獲的字串接著作為對應目標參數的參數傳遞。 然而,若省略兄弟參數的參數,目標參數會取其 default_argument 值。
範例:給定上述宣告
M,考慮以下對 的M呼叫:Test.M(); Test.M(123); Test.M(123, null); Test.M(123, "xyz"); Test.M( 1 + 2 ); Test.M(( ( (123) + 0) ) ); int local = 10; Test.M(l\u006fcal /*...*/ + // xxx 5);產出為
val = 0, text = <> val = 123, text = <123> val = 123, text = <> val = 123, text = <xyz> val = 3, text = <1 + 2> val = 123, text = <(123) + 0> val = 15, text = <l\u006fcal /*...*/ + // xxx 5>end 範例
23.5.7 程式碼分析屬性
23.5.7.1 一般規定
此子句中的屬性用於提供額外資訊,以支援能進行可空性和空狀態診斷的編譯器(§8.9.5)。 編譯器不需執行任何空狀態診斷。 這些屬性的存在或不存在不會影響語言或程序的行為。 不提供空狀態診斷的編譯器應讀取並忽略這些屬性的存在。 提供空狀態診斷的編譯器應針對其用來通知其診斷的任何這些屬性使用本子句中定義的含義。
程式代碼分析屬性會在命名空間 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.10 條) |
可為 Null 的傳回值永遠不會是 Null。 |
MaybeNullWhen (第 23.5.7.7 條) |
當方法傳回指定的 bool 值時,不可為 Null 的引數可能是 null。 |
NotNullWhen (§23.5.7.12) |
當方法傳回指定的 bool 值時,可為 Null 的自變數不會是 Null。 |
NotNullIfNotNull (§23.5.7.11) |
如果指定參數的自變數不是 Null,則傳回值不是 Null。 |
MemberNotNull (第 23.5.7.8 條) |
當方法回傳時,列出的成員不會是空的。 |
MemberNotNullWhen (第 23.5.7.9 條) |
當方法回傳指定的 bool 值時,所列成員不會是空的。 |
DoesNotReturn (第 23.5.7.4 條) |
這個方法永遠不會傳回。 |
DoesNotReturnIf (第 23.5.7.5 條) |
如果關聯的 bool 參數具有指定的值,這個方法永遠不會傳回 。 |
§23.5.7 中的以下子條款為有條件規範性。
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的預設值,因此編譯程式可能會警告它必須在存取之前加以檢查。 此外,它警告呼叫者,即使可能是空,呼叫者不應明確設定為空。 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 子句為止。 第三,不可達程式碼不會影響任何空狀態。
屬性不會根據此屬性的存在而變更可觸達性 (~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} cannot 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 [return: MaybeNull] public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }若沒有該屬性,編譯器可能會產生警告,若方法能回傳
null。 屬性的存在會隱藏該警告。 end 範例
23.5.7.7 MaybeNullWhen 屬性
指定當方法傳回指定的null值時,可能是bool不可為 Null 的自變數。 這類似於 MaybeNull 屬性 (§23.5.7.6) ,但包含指定傳回值的參數。
23.5.7.8 MemberNotNull 屬性
指定該成員在方法回傳時不會是 null 。
範例:輔助方法可能包含
MemberNotNull屬性,以列出該方法中指派給非空值的任何欄位。 分析建構子以判斷所有不可空參考欄位是否已被初始化的編譯器,可能會利用此屬性來發現這些輔助方法設定了哪些欄位。 請考慮下列範例:#nullable enable public class Container { private string _uniqueIdentifier; // must be initialized. private string? _optionalMessage; public Container() { Helper(); } public Container(string message) { Helper(); _optionalMessage = message; } [MemberNotNull(nameof(_uniqueIdentifier))] private void Helper() { _uniqueIdentifier = DateTime.Now.Ticks.ToString(); } }多個欄位名稱可作為屬性建構子的參數。 end 範例
23.5.7.9 MemberNotNullWhen 屬性
指定當方法回傳指定null值時,所列成員不會被列入bool。
舉例:這個屬性類似
MemberNotNull於 (§23.5.7.8),但那是一個MemberNotNullWhen參數bool。MemberNotNullWhen設計用於輔助方法回傳 Abool並表示是否初始化欄位的情況。 end 範例
23.5.7.10 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。 然而,使用空參考是可以ThrowWhenNull接受的。 end 範例
23.5.7.11 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.12 NotNullWhen 屬性
指定當方法回傳指定null值時,可空的參數不會是bool。
範例:當自變數為
String.IsNullOrEmpty(String)或空字串時,連結庫方法true會null傳回 。 這是一種空檢查(null-check):呼叫者不需要對參數進行空檢查,若方法回傳false。 若要讓類似這個可為 Null 的方法感知,請將參數類型設為可為 Null 的參考型別,並新增 NotNullWhen 屬性:#nullable enable bool IsNullOrEmpty([NotNullWhen(false)] string? value) { ... }end 範例
23.5.8 UnscopedRef 屬性
有數種情況中,參考文獻被視為隱含範圍(§9.7.3);也就是說,參考不可逃脫方法。 例如:
-
this對於結構體實例方法。 - 參考參數,這些參數指向 ref 結構型別。
- 我們的參數。
此屬性用於裁判應被允許逃脫的情況。
此屬性可套用於任意 ref ,且 ref-safe-context 會比預設寬一級。 例如:
| UnscopedRef 被應用於 | 原始參考安全上下文 | 新 ref-safe-context |
|---|---|---|
| 實例成員 | 函數成員 | 僅能回傳 |
in
/
ref 參數 |
僅能回傳 | 呼叫者上下文 |
out 參數 |
函數成員 | 僅能回傳 |
當將此屬性套用到結構的實例方法時,會修改隱含 this 參數;也就是說,作為 this 同類型的無註解 ref 參數。
一個以 為 [UnscopedRef] 註解的實例方法或屬性,其 的 ref-safe-context this 設為 呼叫者上下文。
標註為 的 [UnscopedRef] 成員不得實作介面。
這是個錯誤,不能用 [UnscopedRef] 在
- 一個未被宣告在
struct上的成員。 -
static一個成員、init成員或構造子。struct - 一個標記為
scoped的參數。 - 一個參數,由值傳遞。
- 一個透過參考傳遞且未隱含作用域的參數。
更多資訊請參見 §9.7.3 。
23.5.9 枚舉者取消屬性
指定代表非同步反覆運算器的參數 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),而非非同步列舉介面(§15.15.3)。
當沒有任何屬性具備此參數時,迭代器將無法存取該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.5.10 ModuleInitializer 屬性
屬性 ModuleInitializer 用來標記方法為 模組初始化器。 此類方法在包含模組初始化時被呼叫。 一個模組可能有多個初始化器,這些初始化器會依實作定義的順序呼叫。
模組初始化器中允許使用哪些程式碼沒有限制。
模組初始化器應具備以下特性:
-
method_modifier
static。 - 沒有 parameter_list。
-
return_type
void的 。 - 沒有 type_parameter_list。
- 不會被 class_declaration 內申報,並擁有 type_parameter_list。
- 可以從包含模組中可達(即具有存取修飾符
internal或public)。 - 不要成為地方性的活動。
23.5.10.1 自訂插值字串表達式處理器
23.5.10.1.1 宣告自訂處理器
請考慮以下程式,它實作了一個簡單的訊息記錄器:
using System;
public class Logger
{
public void LogMessage(string msg)
{
Console.WriteLine(msg);
}
}
public class Program
{
static void Main()
{
var logger = new Logger();
int val = 255;
logger.LogMessage($"val = {{{val,4:X}}}; 2 * val = {2 * val}.");
}
}
產出的產品如下:
val = { FF}; 2 * val = 510.
在呼叫 LogMessage時,插值字串表達式參數的目標是 msg參數 ,其型別 string為 。 因此,根據 第 12.8.3 節,會調用預設插值字串表達式處理器。 以下子條款(§23.5.10.1.1)說明如何使用自訂處理器。
為了為上述程式提供客製化處理,需要 一個客製化插值字串表達式處理器 。 接著是新增自訂處理器的訊息記錄器(雖然它只是像預設處理器一樣運作,但提供了自訂的掛鉤):
using System;
using System.Text;
using System.Runtime.CompilerServices;
[InterpolatedStringHandler]
public ref struct LogInterpolatedStringHandler
{
StringBuilder builder; // Storage for the built-up string
public LogInterpolatedStringHandler(int literalLength, int formattedCount)
{
builder = new StringBuilder(literalLength);
}
public void AppendLiteral(string s)
{
builder.Append(s);
}
public void AppendFormatted<T>(T t)
{
builder.Append(t?.ToString());
}
public void AppendFormatted<T>(T t, string format) where T : IFormattable
{
builder.Append(t?.ToString(format, null));
}
public void AppendFormatted<T>(T t, int alignment, string format)
where T : IFormattable
{
builder.Append(String.Format("{0" + "," + alignment + ":" + format + "}", t));
}
public override string ToString() => builder.ToString();
}
public class Logger
{
public void LogMessage(string msg)
{
Console.WriteLine(msg);
}
public void LogMessage(LogInterpolatedStringHandler builder)
{
Console.WriteLine(builder.ToString());
}
}
public class Program
{
static void Main()
{
var logger = new Logger();
int val = 255;
logger.LogMessage($"val = {{{val,4:X}}}; 2 * val = {2 * val}.");
}
}
產出的產品如下:
val = { FF}; 2 * val = 510.
具有 屬性 System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute 的型別稱為 適用插值字串處理型態。
要符合自訂插值字串表達式處理器的資格,類別或結構型態應具備以下特性:
- 標記為屬性
System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute。 - 有一個可達的建構子,其前兩個參數的型別
int為 。 (其他參數後續可能用於傳遞與處理器的資訊。這些內容詳見 第23.5.10.1.3條。可選擇性最後參數可被宣告,以阻止處理器處理插值字串。此點詳見 第23.5.10.1.2節)。
當編譯器產生的程式碼呼叫建構子時,第一個參數設為插值字串表達式中插值字串表達式段(§12.8.3)長度的總和,第二個參數則設為插值次數。 (對於 ($"val = {{{val,4:X}}}; 2 * val = {2 * val}.",這兩個值分別為 21 和 2。)
- 有一個可存取的方法,簽名為
void AppendLiteral(string s),用於處理單一插值字串表達式的文字段。 - 有一組可存取的超載方法,稱為
AppendFormatted,其中一個會被呼叫來處理單一插值,根據該插值的內容。 他們的簽名如下:-
void AppendFormatted<T>(T t),處理的是沒有明確格式或對齊的插值,如 的情況{2 * val}。 -
void AppendFormatted<T>(T t, string format) where T : System.IFormattable,處理的是具有明確格式但沒有對齊的插值,如 的情況{val:X4}。 -
void AppendFormatted<T>(T t, int alignment, string format) where T : System.IFormattable,處理具有明確格式與對齊的插值,如 的情況{val,4:X}。
-
- 有一個公開方法,簽名為
override string ToString(),回傳建構的字串。
注意:遺漏任何
AppendFormatted超載並非編譯時錯誤,但若處理器要達到最大魯棒性,應支援預設處理器識別的所有格式。 結尾註釋
新的超載 LogMessage 會將自訂處理器取代 string,並依照該處理器格式化的字串取得。 當存在此類過載時,若有對應的處理器存在且插值的字串運算式不是常數(§12.8.3),編譯器會產生程式碼呼叫使用處理器的運算式。 在這種情況下,編譯器會產生
- 呼叫處理者建構子
- 在插值字串表達式中的詞彙順序
- 將每個插值字串表達式段傳遞為
AppendLiteral - 將每個插值傳遞到適當的
AppendFormatted方法。
- 將每個插值字串表達式段傳遞為
- 回傳最後一個字串,作為插值字串表達式的值。
- 執行 的
LogMessage主體。
23.5.10.1.2 抑制自訂處理器
如果一個處理者建構子的最終參數 bool 型別為 是出界參數,當該建構子被呼叫時會測試該參數的值。 如果是真的,行為就像是那個參數被省略了一樣。 然而,若為假,插值字串表達式將不再被進一步處理;也就是說,操作者受到 抑制。 具體來說,插值表達式不會被評估, AppendLiteral 方法和 AppendFormatted 也不會被呼叫。
public LogInterpolatedStringHandler(int literalLength, int formattedCount,
out bool processString)
{
if (some_condition)
{
processString = false;
return;
}
else
{
processString = true;
// continue construction
}
}
注意:插值字串表達式中的插值可能包含副作用(如 ++、 --、 指派及某些方法呼叫的結果)。 若處理程序被抑制,則不會評估插值字串表達式中的副作用。 若處理程序未被抑制,則會評估插值字串表達式中的所有副作用。
結尾註釋
23.5.10.1.3 與自訂處理器之間的資訊傳遞
傳遞其他資訊給自訂處理器,並接收回傳資訊,是很有用的。 這是透過屬性 System.Runtime.CompilerServices.InterpolatedStringHandlerArgument來完成的。 請考慮以下對訊息記錄程式的新超載:
public class Logger
{
// …
public void LogMessage(bool flag, int count,
[InterpolatedStringHandlerArgument("count","flag","")]
LogInterpolatedStringHandler builder)
{
// …
}
}
public ref struct LogInterpolatedStringHandler
{
// …
public LogInterpolatedStringHandler(int literalLength, int formattedCount,
int count, bool flag, Logger logger)
{
// …
}
}
InterpolatedStringHandlerArgument屬性套用到處理器參數,該參數應跟隨要傳遞給處理器的參數宣告。 屬性建構子參數應為一個逗號分隔的清單,包含零個或多個字串,該字串指定要傳遞的參數及其順序。 空字串表示呼叫處理器的實例。 因此,上述屬性 "count","flag","" 建構子呼叫包含,需要一個匹配的處理器建構子。 如果屬性建構子的參數列表為空,行為就等同於該屬性被省略。
若 out bool 同時宣告允許處理者被抑制的參數(§23.5.10.1.2),該參數即為最終參數。
23.5.11 必要成員屬性
23.5.11.1 SetsRequiredMembers 屬性
此屬性表示其裝飾的建構子已設定當前型別的所有必要成員,因此呼叫者無需自行設定任何必要成員。 然而,編譯器並未驗證建構子是否真的初始化了所有所需的成員。
範例:
public class Person { public Person() { } [SetsRequiredMembers] public Person(string firstName, string lastName) => (FirstName, LastName) = (firstName, lastName); public required string FirstName { get; init; } public required string LastName { get; init; } public int? Age { get; set; } } public class Student : Person { public Student() : base() { } [SetsRequiredMembers] public Student(string firstName, string lastName) : base(firstName, lastName) { } public double GPA { get; set; } } public class Test { public static void M() { var p1 = new Student(); // error: doesn't set required members var p2 = new Student("Jane", "Williams"); // OK } }end 範例
注意:由於衍生型建構子
Student(string, string)與具有此屬性的基底建構子Person(string, string)鏈式,衍生型態建構子也必須具備該屬性(§15.11.1)。 結尾註釋
23.5.11.2 RequiredMember 屬性
此屬性表示目前型別有一個或多個必需成員(§15.7.1),或是該型別的特定成員是必需的。 然而,明確使用此屬性是錯誤的。 相反地,修飾符 required 的存在會使該類型或成員被視為裝飾了這個屬性。
23.5.12 CollectionBuilder 屬性
此屬性指定集合類型具有集合建立方法(§15.17.1)。
建構子會取一個建構型別及要調用的方法名稱來建構該集合型別的實例。
屬性可以套用到類別、結構體、ref 結構體或介面上。 該屬性不會被繼承,但它可以套用到基底類別或抽象類別。
建構者類型應為非通用類別或結構體。
23.5.13 InlineArray 屬性
屬性 InlineArray 用來將非記錄結構體識別為內嵌陣列類型。 欲了解更多資訊及使用範例,請參見 第16.6節。
23.5.14 OverloadResolutionPriority 屬性
屬性 OverloadResolutionPriority 用於在超載解析時指定成員的優先順序,作為 int 建構子的參數。 此屬性的缺失等價於其存在且參數為 0。 數字越大,優先順序越高。 所有優先權低於最高超載優先權的超載,都會從適用的配對集合中移除。
函式庫作者可能會使用此屬性,確保優先選擇新的、更好的超載而非現有的,以減少記憶體配置。 此屬性會告知編譯器應優先處理哪種過載。
範例:請考慮下列事項:
class Program { static void Main() { var c = new C(); int[] arr = [1, 2, 3]; c.M(arr); // Prints "Span" } } class C { [OverloadResolutionPriority(1)] public void M(params ReadOnlySpan<int> s) => Console.WriteLine("Span"); public void M(params int[] a) => Console.WriteLine("Array"); }第二種方法隱含優先權為零,若無明確屬性,則會呼叫第二個超載。 然而,當屬性存在時,會被召喚出第一個屬性。
此屬性的某些使用可能會使成員無法被呼叫,具體如下:
class Program { static void Main() { var c = new C(); c.M1(1); // Calls C3.M1(long), not M1(int) c.M2(1); // Calls C3.M2(int, string), not M2(int) c.M3("abc"); // Calls C3.M3(object), not M3(string) } } class C { public void M1(int i) { } [OverloadResolutionPriority(1)] public void M1(long l) { } [Conditional("DEBUG")] public void M2(int i) { } [OverloadResolutionPriority(1), Conditional("DEBUG")] public void M2(int i, [CallerArgumentExpression(nameof(i))] string s = "") { } public void M3(string s) { } [OverloadResolutionPriority(1)] public void M3(object o) { } }end 範例
23.6 互通屬性
若要與其他語言互操作,您可以使用索引屬性來實作索引器。 如果索引器沒有 IndexerName 屬性存在,則預設會使用名稱 Item 。 屬性 IndexerName 可讓開發人員覆寫此預設值,並指定不同的名稱。
範例:根據預設,索引器的名稱為
Item。 這可以覆寫,如下所示:[System.Runtime.CompilerServices.IndexerName("TheItem")] public int this[int index] { get { ... } set { ... } }現在,索引器的名稱是
TheItem。end 範例