C# 編譯器所解譯的其他屬性

屬性 ConditionalObsoleteAttributeUsageAsyncMethodBuilderInterpolatedStringHandlerModuleInitializerExperimental 可以套用至您程式碼中的元素。 它們會將語意意義新增至那些元素。 編譯器會使用這些語意意義來改變其輸出,而且開發人員會回報使用您的程式碼可能發生的錯誤。

Conditional 屬性

Conditional 屬性會根據前置處理識別碼來執行方法。 Conditional 屬性是 ConditionalAttribute 的別名,而且可以套用至方法或屬性類別。

在下列範例中,Conditional 會套用至方法,以啟用或停用程式特定診斷資訊的顯示:

#define TRACE_ON
using System.Diagnostics;

namespace AttributeExamples;

public class Trace
{
    [Conditional("TRACE_ON")]
    public static void Msg(string msg)
    {
        Console.WriteLine(msg);
    }
}

public class TraceExample
{
    public static void Main()
    {
        Trace.Msg("Now in Main...");
        Console.WriteLine("Done.");
    }
}

如果未定義 TRACE_ON 識別碼,則不會顯示追蹤輸出。 在互動式視窗中自行探索。

Conditional 屬性通常與 DEBUG 識別碼搭配使用,以啟用偵錯組建 (而非發行組建) 的追蹤和記錄功能,如下列範例所示:

[Conditional("DEBUG")]
static void DebugMethod()
{
}

呼叫標記為條件式的方法時,所指定前置處理符號的存在與否會決定編譯器包括還是省略方法呼叫。 如果定義符號,則會包括呼叫;否則會省略呼叫。 條件式方法必須是類別或結構宣告中的方法,而且必須具有 void 傳回值。 使用 Conditional 比在 #if…#endif 區塊內封入方法更為乾淨、更明確且不容易出錯。

如果方法具有多個 Conditional 屬性,則編譯器會在定義一或多個條件式符號時包括方法呼叫 (符號會使用 OR 運算子以邏輯方式連結在一起)。 在下列範例中,AB 的存在會導致方法呼叫:

[Conditional("A"), Conditional("B")]
static void DoIfAorB()
{
    // ...
}

搭配使用 Conditional 與屬性類別

Conditional 屬性也可以套用至屬性類別定義。 在下列範例中,如果定義 DEBUG,則自訂屬性 Documentation 會將資訊新增至中繼資料。

[Conditional("DEBUG")]
public class DocumentationAttribute : System.Attribute
{
    string text;

    public DocumentationAttribute(string text)
    {
        this.text = text;
    }
}

class SampleClass
{
    // This attribute will only be included if DEBUG is defined.
    [Documentation("This method displays an integer.")]
    static void DoWork(int i)
    {
        System.Console.WriteLine(i.ToString());
    }
}

Obsolete 屬性

Obsolete 屬性會將程式碼元素標記為不再建議使用。 使用標記為過時的實體會產生警告或錯誤。 Obsolete 屬性是單次使用屬性,並且可以套用至任何允許屬性的實體。 ObsoleteObsoleteAttribute 的別名。

在下列範例中,Obsolete 屬性會套用至 A 類別和 B.OldMethod 方法。 因為套用至 B.OldMethod 的屬性建構函式的第二個引數設為 true,所以這個方法會造成編譯器錯誤,而使用 A 類別則會產生警告。 不過,呼叫 B.NewMethod 不會產生任何警告或錯誤。 例如,當您將它與先前的定義搭配使用時,下列程式碼會產生兩個警告和一個錯誤︰


namespace AttributeExamples
{
    [Obsolete("use class B")]
    public class A
    {
        public void Method() { }
    }

    public class B
    {
        [Obsolete("use NewMethod", true)]
        public void OldMethod() { }

        public void NewMethod() { }
    }

    public static class ObsoleteProgram
    {
        public static void Main()
        {
            // Generates 2 warnings:
            A a = new A();

            // Generate no errors or warnings:
            B b = new B();
            b.NewMethod();

            // Generates an error, compilation fails.
            // b.OldMethod();
        }
    }
}

提供為屬性建構函式第一個引數的字串會顯示為警告或錯誤的一部分。 會產生 A 類別的兩個警告︰一個用於宣告類別參考,一個則用於類別建構函式。 您可以使用沒有引數的 Obsolete 屬性,但建議包括改用項目的說明。

在 C# 10 中,您可以使用常數字串內插補點和 nameof 運算子來確保名稱相符:

public class B
{
    [Obsolete($"use {nameof(NewMethod)} instead", true)]
    public void OldMethod() { }

    public void NewMethod() { }
}

Experimental 屬性

從 C# 12 開始,型別、方法和組件可以標示為 System.Diagnostics.CodeAnalysis.ExperimentalAttribute 以表示實驗性功能。 如果您存取以 ExperimentalAttribute 標註的方法或型別,編譯器會發出警告。 以 Experimental 屬性標示組件或模組中宣告的所有型別都是實驗性的。 如果您存取其中任何一項,編譯器就會發出警告。 您可以停用這些警告,以試驗實驗性功能。

警告

實驗性功能可能會有所變更。 API 可能會變更,或在未來的更新中可能會移除它們。 包含實驗性功能是程式庫作者針對未來開發取得想法和概念意見反應的一種方式。 使用標示為實驗性的任何功能時,請特別小心。

您可以在功能規格中深入了解 Experimental 屬性的詳細資料。

SetsRequiredMembers 屬性

SetsRequiredMembers 屬性會通知編譯器:建構函式會設定該類別或結構中的所有 required 成員。 編譯器假設任何具有 System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute 屬性的建構函式都會初始化所有 required 成員。 任何叫用這類建構函式的程式碼都不需要物件初始設定式來設定必要成員。 新增 SetsRequiredMembers 屬性主要適用於位置記錄和主要建構函式。

AttributeUsage 屬性

AttributeUsage 屬性決定如何使用自訂屬性類別。 AttributeUsageAttribute 是您套用至自訂屬性定義的屬性。 AttributeUsage 屬性可讓您控制:

  • 屬性可以套用至哪些程式元素。 除非您限制其使用方式,否則屬性可能會套用至下列任一程式元素:
    • 組件
    • 模組
    • 欄位
    • Event
    • 方法
    • Param
    • 屬性
    • 傳回
    • 類型
  • 屬性是否可以套用至單一程式元素多次。
  • 衍生類別是否繼承屬性。

明確套用時,預設設定看起來像下列範例:

[AttributeUsage(AttributeTargets.All,
                   AllowMultiple = false,
                   Inherited = true)]
class NewAttribute : Attribute { }

在此範例中,NewAttribute 類別可套用至任何支援的程式元素。 但是,它只能套用至每個實體一次。 衍生類別會繼承套用至基底類別的屬性。

AllowMultipleInherited 是選擇性引數,因此下列程式碼具有相同的效果:

[AttributeUsage(AttributeTargets.All)]
class NewAttribute : Attribute { }

第一個 AttributeUsageAttribute 引數必須是 AttributeTargets 列舉的一或多個元素。 您可以使用 OR 運算子來連結多個目標類型,如下列範例所示:

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
class NewPropertyOrFieldAttribute : Attribute { }

屬性 (Attribute) 可以套用至屬性 (Property) 或自動實作屬性 (Property) 的支援欄位。 除非您在屬性 (attribute) 上指定 field 指定名稱,否則屬性 (attribute) 會套用至屬性 (property)。 下列範例會顯示這兩者:

class MyClass
{
    // Attribute attached to property:
    [NewPropertyOrField]
    public string Name { get; set; } = string.Empty;

    // Attribute attached to backing field:
    [field: NewPropertyOrField]
    public string Description { get; set; } = string.Empty;
}

如果 AllowMultiple 引數為 true,則可以將產生的屬性多次套用至單一實體,如下列範例所示:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
class MultiUse : Attribute { }

[MultiUse]
[MultiUse]
class Class1 { }

[MultiUse, MultiUse]
class Class2 { }

在此情況下,因為 AllowMultiple 設為 true,所以可以重複套用 MultiUseAttribute。 套用多個屬性所顯示的兩種格式都有效。

如果 Inheritedfalse,則衍生類別不會從屬性基底類別繼承屬性。 例如:

[AttributeUsage(AttributeTargets.Class, Inherited = false)]
class NonInheritedAttribute : Attribute { }

[NonInherited]
class BClass { }

class DClass : BClass { }

在此情況下,不會透過繼承將 NonInheritedAttribute 套用至 DClass

您也可以使用這些關鍵字來指定應該套用屬性的位置。 例如,您可以使用 field: 規範以將屬性新增至自動實作屬性的支援欄位。 或者,您可以使用 field:property:param: 規範,以將屬性套用至從位置記錄所產生的任何元素。 如需範例,請參閱屬性定義的位置語法

AsyncMethodBuilder 屬性

您可以將 System.Runtime.CompilerServices.AsyncMethodBuilderAttribute 屬性新增至可為非同步傳回類型的類型。 此屬性指定從非同步方法傳回所指定的類型時,可建置非同步方法實作的類型。 AsyncMethodBuilder 屬性可以套用至類型,而此類型:

AsyncMethodBuilder 屬性的建構函式會指定相關聯建立器的類型。 建立器必須實作下列可存取的成員:

  • 傳回建立器類型的靜態 Create() 方法。

  • 傳回非同步傳回類型的可讀取 Task 屬性。

  • 可在工作錯誤時設定例外狀況的 void SetException(Exception) 方法。

  • 可將工作標記為已完成並選擇性地設定工作結果的 void SetResult()void SetResult(T result) 方法

  • 具有下列 API 簽章的 Start 方法:

    void Start<TStateMachine>(ref TStateMachine stateMachine)
              where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
    
  • 具有下列簽章的 AwaitOnCompleted 方法:

    public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : System.Runtime.CompilerServices.INotifyCompletion
        where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
    
  • 具有下列簽章的 AwaitUnsafeOnCompleted 方法:

          public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
              where TAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion
              where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
    

您可以閱讀 .NET 所提供的下列建立器,以了解非同步方法建立器:

在 C# 10 和更新版本中,AsyncMethodBuilder 屬性可以套用至非同步方法,以覆寫該類型的建立器。

InterpolatedStringHandlerInterpolatedStringHandlerArguments 屬性

從 C# 10 開始,您可以使用這些屬性來指定類型是「差補字串處理常式」。 .NET 6 程式庫已包括適用於下列情節的 System.Runtime.CompilerServices.DefaultInterpolatedStringHandler:您使用差補字串作為 string 參數的引數。 您可能有其他想要控制差補字串處理方式的執行個體。 您可以將 System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute 套用至可實作處理常式的類型。 您可以將 System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute 套用至該類型建構函式的參數。

您可以深入了解在 C# 10 功能規格中建置差補字串處理常式,以進行差補字串改善

ModuleInitializer 屬性

ModuleInitializer 屬性會標記執行階段在載入組件時所呼叫的方法。 ModuleInitializerModuleInitializerAttribute 的別名。

ModuleInitializer 屬性只能套用至方法,而此方法:

  • 為靜態。
  • 為無參數。
  • 傳回 void
  • 可以從包含模組 (即 internalpublic) 進行存取。
  • 不是泛型方法。
  • 未包含在泛型類別中。
  • 不是區域函數。

ModuleInitializer 屬性可以套用至多個方法。 在此情況下,執行階段呼叫它們的順序具決定性,但未指定。

下列範例說明如何使用多個模組初始設定式方法。 Init1Init2 方法會在 Main 前面執行,而且每個方法都會將字串新增至 Text 屬性。 因此,Main 執行時,Text 屬性已經有來自這兩個初始設定式方法的字串。

using System;

internal class ModuleInitializerExampleMain
{
    public static void Main()
    {
        Console.WriteLine(ModuleInitializerExampleModule.Text);
        //output: Hello from Init1! Hello from Init2!
    }
}
using System.Runtime.CompilerServices;

internal class ModuleInitializerExampleModule
{
    public static string? Text { get; set; }

    [ModuleInitializer]
    public static void Init1()
    {
        Text += "Hello from Init1! ";
    }

    [ModuleInitializer]
    public static void Init2()
    {
        Text += "Hello from Init2! ";
    }
}

原始程式碼產生器有時需要產生初始化程式碼。 模組初始設定式提供該程式碼的標準位置。 在其他大部分情況下,您應該撰寫靜態建構函式,而不是模組初始設定式。

SkipLocalsInit 屬性

SkipLocalsInit 屬性可防止編譯器在發出至中繼資料時設定 .locals init 旗標。 SkipLocalsInit 屬性是單次使用屬性,而且可以套用至方法、屬性、類別、結構、介面或模組,但不能套用至組件。 SkipLocalsInitSkipLocalsInitAttribute 的別名。

.locals init 旗標可讓 CLR 將方法中所宣告的所有區域變數初始化為其預設值。 因為編譯器也確定您永遠不會在指派變數的值之前使用變數,所以一般不需要 .locals init。 不過,在某些情節中,額外零初始化可能會有可測量的效能影響,例如,當您使用 stackalloc 以在堆疊上配置陣列時。 在這些情況下,您可以新增 SkipLocalsInit 屬性。 如果直接套用至方法,則此屬性會影響該方法和其所有巢狀函數 (包括 Lambda 和區域函數)。 如果套用至類型或模組,則會影響所有巢狀在其內的方法。 此屬性不會影響抽象方法,但會影響針對實作所產生的程式碼。

此屬性需要 AllowUnsafeBlocks 編譯器選項。 此需求發出訊號,指出在某些情況下,程式碼可以檢視未指派的記憶體 (例如,從未初始化的堆疊配置記憶體中讀取)。

下列範例說明可使用 stackalloc 之方法上的 SkipLocalsInit 屬性效果。 此方法顯示在已配置整數陣列時,記憶體中的任何內容。

[SkipLocalsInit]
static void ReadUninitializedMemory()
{
    Span<int> numbers = stackalloc int[120];
    for (int i = 0; i < 120; i++)
    {
        Console.WriteLine(numbers[i]);
    }
}
// output depends on initial contents of memory, for example:
//0
//0
//0
//168
//0
//-1271631451
//32767
//38
//0
//0
//0
//38
// Remaining rows omitted for brevity.

若要自行嘗試此程式碼,請在 .csproj 檔案中設定 AllowUnsafeBlocks 編譯器選項:

<PropertyGroup>
  ...
  <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

另請參閱