定義和讀取自訂屬性
屬性提供以宣告方式將資訊與程式碼相關聯的方法。 這些屬性也可以提供可重複使用的元素,可以套用至各種目標。 以 ObsoleteAttribute 為例。 它可以套用至類別、結構、方法及建構函式等。 它宣告元素已過時。 然後是由 C# 編譯器來尋找此屬性,並執行一些動作做為回應。
在本教學課程中,您了解如何將屬性新增至您的程式碼、如何建立並使用您自己的屬性,以及如何使用一些 .NET 內建的屬性。
必要條件
您將必須設定您的機器才能執行 .NET。 您可以在 .NET 下載頁面上找到安裝指示。 您可以在 Windows、Ubuntu Linux、macOS 或在 Docker 容器中執行此應用程式。 您必須安裝您的慣用程式碼編輯器。 以下說明使用 Visual Studio Code,這是開放原始碼的跨平台編輯器。 不過,您可以使用您熟悉的任何工具。
建立 應用程式
現在您已安裝完所有工具,請建立新的 .NET 主控台應用程式。 若要使用命令列產生器,請在您慣用的殼層中執行下列命令︰
dotnet new console
此命令會建立相當基本的 .NET 專案檔。 您執行 dotnet restore
可以還原編譯此專案所需的相依性。
您不必執行 dotnet restore
,因其是以隱含方式由需要還原的所有命令執行,例如 dotnet new
、dotnet build
、dotnet run
、dotnet test
、dotnet publish
和 dotnet pack
。 若要停用隱含還原,請使用 --no-restore
選項。
dotnet restore
命令在適合進行明確還原的特定案例中仍可派上用場,例如 Azure DevOps Services 中的持續整合組建,或在需要明確控制何時進行還原的組建系統中。
如需如何管理 NuGet 摘要的詳細資訊,請參閱 dotnet restore
文件。
若要執行這個程式,請使用 dotnet run
。 您應該會在主控台看到 "Hello, World" 輸出。
將屬性新增至程式碼
在 C# 中,屬性是繼承自 Attribute
基底類別的類別。 繼承自 Attribute
的任何類別都能在其他程式碼片段上做為一種「標記」。 舉例來說,有一個名為 ObsoleteAttribute
的屬性。 這個屬性發出訊號表示程式碼已過時,而且不應該再使用。 您將此屬性放置在類別上,例如透過使用方括弧。
[Obsolete]
public class MyClass
{
}
雖然此類別名為 ObsoleteAttribute
,但在程式碼中只需要使用 [Obsolete]
。 大部分的 C# 程式碼都遵循此慣例。 您也可以選擇使用完整名稱 [ObsoleteAttribute]
。
將類別標示為過時的時候,最好提供一些像是它為什麼過時及/或應改用什麼的資訊。 您將字串參數包含在 Obsolete 屬性中可以提供這類的說明。
[Obsolete("ThisClass is obsolete. Use ThisClass2 instead.")]
public class ThisClass
{
}
字串會以引數的方式傳遞至 ObsoleteAttribute
建構函式,如同您撰寫 var attr = new ObsoleteAttribute("some string")
一般。
傳遞到屬性建構函式的參數僅限於簡單型別/常值︰bool, int, double, string, Type, enums, etc
和這些型別的陣列。
您不能使用運算式或變數。 您可以自由使用位置或具名參數。
建立您自己的屬性
您透過定義繼承自 Attribute
基底類別的新類別來建立屬性。
public class MySpecialAttribute : Attribute
{
}
使用上述程式碼時,您可以在程式碼基底的其他位置使用 [MySpecial]
(或 [MySpecialAttribute]
) 做為屬性。
[MySpecial]
public class SomeOtherClass
{
}
.NET 基底類別庫中的屬性 (如 ObsoleteAttribute
) 會觸發編譯器中的特定行為。 不過,您所建立的任何屬性僅做為中繼資料,不會在執行的屬性類別內產生任何程式碼。 您可以選擇在程式碼的其他位置對中繼資料採取動作。
此處有「陷阱」要注意。 如上所述,使用屬性時,只有特定型別可做為引數傳遞。 不過,在建立屬性型別時,C# 編譯器不會阻止您建立這些參數。 在下列範例中,您使用正確編譯的建構函式建立屬性。
public class GotchaAttribute : Attribute
{
public GotchaAttribute(Foo myClass, string str)
{
}
}
不過,您無法將此建構函式與屬性語法搭配使用。
[Gotcha(new Foo(), "test")] // does not compile
public class AttributeFail
{
}
上述程式碼會導致編譯器錯誤,如 Attribute constructor parameter 'myClass' has type 'Foo', which is not a valid attribute parameter type
如何限制屬性使用方式
屬性可用於下列「目標」。 上述範例示範屬性用於類別,但屬性也可以用於:
- 組件
- 類別
- 建構函式
- Delegate
- 列舉
- Event
- 欄位
- GenericParameter
- 介面
- 方法
- 模組
- 參數
- 屬性
- ReturnValue
- 結構
當您建立屬性類別時,根據預設,C# 允許您將該屬性用於任何可能的屬性目標。 如果您想要將您的屬性限制在特定目標,您可以在屬性類別中使用 AttributeUsageAttribute
來完成。 沒錯,屬性中的屬性!
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class MyAttributeForClassAndStructOnly : Attribute
{
}
如果您嘗試將上述屬性放在類別或結構以外的項目,您會收到如下的編譯器錯誤 Attribute 'MyAttributeForClassAndStructOnly' is not valid on this declaration type. It is only valid on 'class, struct' declarations
public class Foo
{
// if the below attribute was uncommented, it would cause a compiler error
// [MyAttributeForClassAndStructOnly]
public Foo()
{ }
}
如何使用附加至程式碼元素的屬性
屬性是做為中繼資料。 在無外力介入的情況下,屬性不會實際執行任何動作。
若要尋找屬性並對屬性採取動作,您需要反映。 反映可讓您以 C# 撰寫程式碼來檢查其他程式碼。 比方說,您可以使用 Reflection 取得類別的相關資訊 (在程式碼的前端新增 using System.Reflection;
):
TypeInfo typeInfo = typeof(MyClass).GetTypeInfo();
Console.WriteLine("The assembly qualified name of MyClass is " + typeInfo.AssemblyQualifiedName);
這會列印如下的內容:The assembly qualified name of MyClass is ConsoleApplication.MyClass, attributes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
一旦您擁有 TypeInfo
物件 (或是 MemberInfo
、FieldInfo
或其他物件),您就可以使用 GetCustomAttributes
方法。 這個方法會傳回 Attribute
物件的集合。 您也可以使用 GetCustomAttribute
並指定一個「屬性」型別。
以下是在 MyClass
的 MemberInfo
執行個體上使用 GetCustomAttributes
的範例 (我們之前已看到其中有一個 [Obsolete]
屬性)。
var attrs = typeInfo.GetCustomAttributes();
foreach(var attr in attrs)
Console.WriteLine("Attribute on MyClass: " + attr.GetType().Name);
這會列印至主控台:Attribute on MyClass: ObsoleteAttribute
。 嘗試將其他屬性加入 MyClass
。
務必注意的是,這些 Attribute
物件會延遲具現化。 也就是說,在您使用 GetCustomAttribute
或 GetCustomAttributes
之前,這些物件不會具現化。 這些物件也會每次都具現化。 在一個資料列中呼叫 GetCustomAttributes
兩次,會傳回兩個不同的 ObsoleteAttribute
執行個體。
執行階段中的常見屬性
許多工具和架構都會使用屬性。 NUnit 使用 NUnit 測試執行器使用的屬性 (例如 [Test]
和 [TestFixture]
)。 ASP.NET MVC 使用 [Authorize]
之類的屬性,並提供動作篩選架構來執行關於 MVC 動作的交叉關注。 PostSharp 使用屬性語法以允許 C# 中的層面導向程式設計。
以下是幾個值得注意的屬性,內建於 .NET Core 基底類別庫中︰
[Obsolete]
. 此屬性使用於上述範例中,且存在於System
命名空間。 若提供有關變更程式碼基底的宣告式文件會很有用。 訊息可以字串的形式提供,使用另一個布林值參數可以從編譯器警告提升至編譯器錯誤。[Conditional]
. 此屬性位於System.Diagnostics
命名空間。 這個屬性可以套用至方法 (或屬性類別)。 您必須傳遞字串給建構函式。 如果該字串不符合#define
指示詞,則 C# 編譯器會移除對該方法的任何呼叫 (但非方法本身)。 這項技巧通常用於偵錯 (診斷) 用途。[CallerMemberName]
. 這個屬性可以用於參數,並存在System.Runtime.CompilerServices
命名空間中。CallerMemberName
屬性可以用來插入呼叫其他方法的方法名稱。 在各種 UI 架構中實作 INotifyPropertyChanged 時,這是一個消除 'magic strings' 的方法。 例如:
public class MyUIClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public void RaisePropertyChanged([CallerMemberName] string propertyName = default!)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string? _name;
public string? Name
{
get { return _name;}
set
{
if (value != _name)
{
_name = value;
RaisePropertyChanged(); // notice that "Name" is not needed here explicitly
}
}
}
}
在上述程式碼中,您不必使用常值 "Name"
字串。 使用 CallerMemberName
有助於避免打字錯誤相關的問題,也可使重構/重新命名更順暢。 屬性為 C# 帶來宣告式能力,但屬性是程式碼的中繼資料形式,無法自行運作。
意見反應
https://aka.ms/ContentUserFeedback。
即將登場:在 2024 年,我們將逐步淘汰 GitHub 問題作為內容的意見反應機制,並將它取代為新的意見反應系統。 如需詳細資訊,請參閱:提交並檢視相關的意見反應