共用方式為


定義和讀取自定義屬性

屬性提供以宣告方式將資訊與程式代碼產生關聯的方式。 它們也可以提供可重複使用的元素,適用於各種目標。 請考慮ObsoleteAttribute。 它可以套用至類別、結構、方法、建構函式等等。 它會 宣告 元素已過時。 接著,C# 編譯程式會尋找此屬性,並執行一些回應動作。

在本教學課程中,您將瞭解如何將屬性新增至程序代碼、如何建立和使用您自己的屬性,以及如何使用 .NET 內建的某些屬性。

先決條件

您必須設定電腦以執行 .NET。 您可以在 .NET 下載 頁面上找到安裝指示。 您可以在 Windows、Ubuntu Linux、macOS 或 Docker 容器中執行此應用程式。 您必須安裝您慣用的程式碼編輯器。 下列描述使用 Visual StudioCode,這是開放原始碼的跨平台編輯器。 不過,您可以使用任何您熟悉的工具。

建立應用程式

現在您已安裝所有工具,請建立新的 .NET 控制台應用程式。 若要使用指令生成器,請在您最喜愛的終端中執行下列命令:

dotnet new console

此命令會建立簡易的 .NET 專案檔案。 您可以執行 dotnet restore 來還原編譯此專案所需的相依性。

您不必執行 dotnet restore,因為其會由需要進行還原的所有命令隱含執行,例如 dotnet newdotnet builddotnet rundotnet testdotnet publishdotnet 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("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

如何限制屬性使用方式

屬性可用於下列「目標」。 在前面的範例中,它們是用於類別,但也可以用於:

  • 集會
  • 班級
  • 建構函式
  • 代表
  • 列舉
  • 事件
  • 領域
  • 泛型參數
  • 介面
  • 方法
  • 模組
  • 參數
  • 房產
  • 返回值
  • 結構體

當您建立屬性類別時,根據預設,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 物件(或 MemberInfoFieldInfo或其他 物件),就可以使用 GetCustomAttributes 方法。 這個方法會傳回 物件的集合 Attribute 。 您也可以使用 GetCustomAttribute 並指定屬性類型。

以下是一個使用GetCustomAttributesMemberInfo實例的範例,針對MyClass(我們稍早看到它有[Obsolete]屬性)。

var attrs = typeInfo.GetCustomAttributes();
foreach(var attr in attrs)
    Console.WriteLine("Attribute on MyClass: " + attr.GetType().Name);

它會列印到主控台: Attribute on MyClass: ObsoleteAttribute。 請嘗試將其他屬性新增至 MyClass

請務必注意,這些 Attribute 物件會延遲具現化。 也就是說,除非您使用 GetCustomAttributeGetCustomAttributes,否則不會具現化它們。 每次也會具現化它們。 連續呼叫兩次 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.CompilerServicesCallerMemberName 是用來插入呼叫另一個方法之方法名稱的屬性。 在各種UI架構中實作INotifyPropertyChanged時,這是消除「魔術字串」的方法。 例如:
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 可防止錯字相關的 Bug,也可讓您更順暢地重構/重新命名。 屬性為 C# 帶來宣告式功能,但它們是程式代碼的元數據形式,而且不會自行採取行動。