C# 編譯器所解譯的其他屬性
有數個屬性可以套用至程式碼中的元素,為這些元素新增語意意義:
Conditional
:使方法的執行依存於前置處理器識別碼。Obsolete
:標示未來 (可能) 移除的型別或成員。AttributeUsage
:宣告可以套用屬性的語言元素。AsyncMethodBuilder
:宣告非同步方法建立器型別。InterpolatedStringHandler
:定義已知案例的差補字串建立器。ModuleInitializer
:宣告初始化模組的方法。SkipLocalsInit
:省略將區域變數儲存體初始化為 0 的程式碼。UnscopedRef
:宣告應該將通常解譯為scoped
的ref
變數視為不限範圍。OverloadResolutionPriority
:新增 tiebreaker 屬性,以影響可能不明確多載的多載解析。Experimental
:將型別或成員標示為實驗性。
編譯器會使用這些語意意義來改變其輸出,而且開發人員會回報使用您的程式碼可能發生的錯誤。
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 運算子以邏輯方式連結在一起)。 在下列範例中,A
或 B
的存在會導致方法呼叫:
[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
屬性是單次使用屬性,並且可以套用至任何允許屬性的實體。 Obsolete
是 ObsoleteAttribute 的別名。
在下列範例中,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
- 方法
- 參數
- 屬性
- 傳回
- 類型
- 屬性是否可以套用至單一程式元素多次。
- 衍生類別是否繼承屬性。
明確套用時,預設設定看起來像下列範例:
[AttributeUsage(AttributeTargets.All,
AllowMultiple = false,
Inherited = true)]
class NewAttribute : Attribute { }
在此範例中,NewAttribute
類別可套用至任何支援的程式元素。 但是,它只能套用至每個實體一次。 衍生類別會繼承套用至基底類別的屬性。
AllowMultiple 和 Inherited 是選擇性引數,因此下列程式碼具有相同的效果:
[AttributeUsage(AttributeTargets.All)]
class NewAttribute : Attribute { }
第一個 AttributeUsageAttribute 引數必須是 AttributeTargets 列舉的一或多個元素。 您可以使用 OR 運算子來連結多個目標類型,如下列範例所示:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
class NewPropertyOrFieldAttribute : Attribute { }
屬性可以套用至自動實作屬性的屬性或支援欄位。 除非您在屬性 (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
。 套用多個屬性所顯示的兩種格式都有效。
如果 Inherited 為 false
,則衍生類別不會從屬性基底類別繼承屬性。 例如:
[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
屬性可以套用至類型,而此類型:
- 具有可存取的
GetAwaiter
方法。 GetAwaiter
方法所傳回的物件會實作 System.Runtime.CompilerServices.ICriticalNotifyCompletion 介面。
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 所提供的下列建立器,以了解非同步方法建立器:
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<TResult>
- System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder
- System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder<TResult>
在 C# 10 和更新版本中,AsyncMethodBuilder
屬性可以套用至非同步方法,以覆寫該類型的建立器。
InterpolatedStringHandler
與 InterpolatedStringHandlerArguments
屬性
從 C# 10 開始,您可以使用這些屬性來指定類型是「差補字串處理常式」。 .NET 6 程式庫已包括適用於下列情節的 System.Runtime.CompilerServices.DefaultInterpolatedStringHandler:您使用差補字串作為 string
參數的引數。 您可能有其他想要控制差補字串處理方式的執行個體。 您可以將 System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute 套用至可實作處理常式的類型。 您可以將 System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute 套用至該類型建構函式的參數。
您可以深入了解在 C# 10 功能規格中建置差補字串處理常式,以進行差補字串改善。
ModuleInitializer
屬性
ModuleInitializer
屬性會標記執行階段在載入組件時所呼叫的方法。 ModuleInitializer
是 ModuleInitializerAttribute 的別名。
ModuleInitializer
屬性只能套用至方法,而此方法:
- 為靜態。
- 為無參數。
- 傳回
void
。 - 可以從包含模組 (即
internal
或public
) 進行存取。 - 不是泛型方法。
- 未包含在泛型類別中。
- 不是區域函數。
ModuleInitializer
屬性可以套用至多個方法。 在此情況下,執行階段呼叫它們的順序具決定性,但未指定。
下列範例說明如何使用多個模組初始設定式方法。 Init1
和 Init2
方法會在 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
屬性是單次使用屬性,而且可以套用至方法、屬性、類別、結構、介面或模組,但不能套用至組件。 SkipLocalsInit
是 SkipLocalsInitAttribute 的別名。
.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>
UnscopedRef
屬性
UnscopedRef
屬性會將變數宣告標示為不限範圍,這表示允許逸出參考。
您可以新增此屬性,其中編譯器會將 ref
視為隱含 scoped
:
struct
執行個體方法的this
參數。- 參考
ref struct
型別的ref
參數。 out
參數。
套用 System.Diagnostics.CodeAnalysis.UnscopedRefAttribute 會將元素標示為不限範圍。
OverloadResolutionPriority
屬性
當兩個多載可能不明確時,OverloadResolutionPriorityAttribute 可讓程式庫作者選擇一種多載,而不是另一種多載。 其主要使用案例是程式庫作者撰寫效能較佳的多載,同時仍支援現有的程式碼,而不會中斷。
例如,您可能會新增一個使用 ReadOnlySpan<T> 的新多載,以減少記憶體分配:
[OverloadResolutionPriority(1)]
public void M(params ReadOnlySpan<int> s) => Console.WriteLine("Span");
// Default overload resolution priority of 0
public void M(params int[] a) => Console.WriteLine("Array");
多載解析會認為這兩種方法對於某些參數類型同樣有效。 對於 int[]
的參數,它會更偏好第一個多載。 為了讓編譯器更偏好 ReadOnlySpan
版本,您可以提高該多載的優先順序。 下面範例展示了新增該屬性的效果:
var d = new OverloadExample();
int[] arr = [1, 2, 3];
d.M(1, 2, 3, 4); // Prints "Span"
d.M(arr); // Prints "Span" when PriorityAttribute is applied
d.M([1, 2, 3, 4]); // Prints "Span"
d.M(1, 2, 3, 4); // Prints "Span"
所有優先順序低於最高多載優先順序的多載將從適用方法集中移除。 沒有此屬性之方法的多載優先順序設定為預設值零。 在增加新的、更好的方法多載時,程式庫的作者應該將此屬性作為最後手段。 程式庫的作者應該對多載解析如何影響選擇更佳方法有深入的了解。 否則,可能會產生非預期的錯誤。