共用方式為


22 個屬性

22.1 一般

大部分的 C# 語言可讓程式設計人員指定程式中所定義實體的宣告式資訊。 例如,在類別中,方法的存取範圍是使用 method_modifierpublicprotectedinternalprivate來裝飾。

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 屬性參數類型

屬性類別的位置和具名參數類型僅限於 屬性參數類型,也就是:

  • 下列其中一種類型:boolbyte、、chardoublefloatint、、longsbyteshortstringuint、 。 ulongushort
  • 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,且在下列文字中,標識符的拼字應該等於 assemblymodule,其中相等是指在6.4.3定義的。 針對生產 attribute_target,且在下列文字中, 標識符 應具有不等於 assemblymodule的拼字,使用與上述相同的相等定義。

屬性是由 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名稱為 eventfieldmethod、、paramproperty、、returntypetypevar。 這些目標名稱只能用於下列內容:

  • 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的類型,就會發生編譯時期錯誤。
  • 否則為
    • attribute_name會解析為type_name~7.8),但會隱藏任何錯誤。 如果此解析成功,併產生衍生自 System.Attribute 的類型,則類型是此步驟的結果。
    • 字元Attribute會附加至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 {}

顯示兩個名為 ExampleExampleAttribute的屬性類別。 屬性 [Example] 模棱兩可,因為它可以參考 ExampleExampleAttribute。 使用逐字標識碼可讓在這類罕見情況下指定確切意圖。 屬性 [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 可以解析為下列其中一項:

範例:

[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、EA

  • 請遵循編譯時間處理步驟,以編譯T(P)。 這些步驟會導致編譯時期錯誤,或判斷可在運行時間叫用的實例建構CT函式。
  • 如果沒有 C 公用輔助功能,則會發生編譯時期錯誤。
  • Arg N
    • 讓我們Name成為 Arg
    • Name 應該識別 上的 T非靜態讀寫公用字段或屬性。 如果沒有 T 這類欄位或屬性,則會發生編譯時期錯誤。
  • 如果positional_argument_listSystem.String

    注意:例如,包含高 Surrogate UTF-16 程式代碼單元的字串,不會緊接著低 Surrogate 程式代碼單位的格式不正確。 end note

  • 將下列資訊(針對屬性的運行時間具現化)儲存在編譯程式所產生的元件輸出中,因為編譯包含 屬性的程式:屬性類別T、上的實例建構CT函式、PN和相關聯的程序實體E,並在編譯階段完全解析值。

22.4.3 屬性實例的運行時間擷取

使用在 \22.4.2 中A實例:

  • 請遵循運行時間處理步驟,以使用編譯時期所決定的實例建構函式和值,執行C。 這些步驟會導致例外狀況,或產生的O實例T
  • 針對中的每個ArgN,依序排列:
    • 讓我們Name成為 Arg。 如果 Name 無法識別 上的 O非靜態公用讀寫字段或屬性,則會擲回例外狀況。
    • 讓我們Value評估Arg結果。
    • 如果 Name 識別上的 O欄位,請將此欄位設定為 Value
    • 否則,Name 會識別 上的 O屬性。 將此屬性設定為 Value。
    • 結果是 O,屬性類別 T 的實例,已使用 positional_argument_listPnamed_argument_listN 初始化。

注意:中儲存 TC、、 PN 、(和與其建立E關聯)A的格式,以及用來指定E和擷取 TCP、、Nfrom A (因此,在運行時間取得屬性實例的方式)的格式超出此規格的範圍。 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.CallerMemberNameAttribute22.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宣告為與兩個條件式編譯符號ALPHABETA相關聯的條件式方法。

end 範例

如果在呼叫點定義一或多個相關聯的條件式編譯符號,則會包含條件式方法的呼叫,否則會省略呼叫。

條件式方法受限於下列限制:

  • 條件式方法應該是class_declarationstruct_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宣告為條件式方法。 Class2Test 方法會呼叫這個方法。 由於已定義條件式編譯符號 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
    }
}

類別 Class2Class3 每個都包含條件式方法 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宣告為與條件式編譯符號ALPHABETA相關聯的條件屬性類別。

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 {}

類別 Class1Class2 都是以 屬性 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 。 每次使用 AMain ,都會產生包含指定訊息的警告:「這個類別已過時;請改用 類別 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、、 CallerFilePathCallerMemberName。 請考慮下列參數宣告:

[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 的類型,但傳回值實際上可能是 nullend 範例

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如果自變數不是 nullnull則不會傳回 。 啟用可為 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) 或空字串時,連結庫方法truenull傳回 。 這是 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 範例