天天看點

演練C#特性介紹背景什麼是特性?特性類型限制特性的使用

目錄

介紹

背景

什麼是特性?

關于特性要記住的事情

特性類型

内在特性

常用的内置特性

使用C#通過反射提取内置類型

在運作時讀取特性

自定義特性

建立自定義特性

在類中應用自定義特性

限制特性的使用

介紹

如果您已經使用C#語言已有一段時間了,那麼您正在使用内置特性(例如[Serializable], [Obsolete]),但是您還沒有對此進行深入的考慮?在本文中,我們将探讨特性的基礎知識,常見的特性,如何建立和讀取特性。令人興奮的是,您将看到如何使用System.Reflection來擷取内置特性。好,那就開始吧。

背景

這有點題外話,但可以分享。你知道嗎?當我們放松和閱讀書籍時,我們的頭腦往往會開始懷疑。而且,當我讀一本C#書籍時,這件事發生在我身上,我開始想知道如何通過System.Reflection來擷取那些内置特性。是以,這篇文章變得生動起來。

什麼是特性?

特性很重要,它提供了其他資訊,為開發人員提供了線索。尤其是在期望的情況下,具有類的行為以及應用程式中類的特性和/或方法。

簡而言之,特性就像形容詞,描述類型、程式集、子產品、方法等。

關于特性要記住的事情

  • 特性是派生自System.Attribute的類 
  • 特性可以具有參數
  • 在代碼中使用特性時,特性可以省略特性名稱的Attribute部分。架構将以任何一種方式正确處理特性。

特性類型

内在特性

這些特性也稱為預定義或内置特性。.NET Framework / .NET Core提供了數百甚至數千個内置特性。它們中的大多數是專門的,但是我們将嘗試以程式設計方式提取它們,并讨論一些最常見的方法。

常用的内置特性

特性 描述
[Obsolete]

System.ObsoleteAttribute

幫助您識别代碼應用程式中的過時位。

[Conditional]

System.Diagnostics.ConditionalAttribute

使您能夠執行條件編譯。

[Serializable]

System.SerializableAttribute

表明一個類可以序列化。

[NonSerialized]

System.NonSerializedAttribute

表明不應将可序列化類的字段序列化。

[DLLImport]

System.DllImportAttribute

顯示方法由非托管動态連結庫(DLL)公開為靜态入口點。

使用C#通過反射提取内置類型

如所承諾的,我們将看到如何使用C#提取内置特性。請參見下面的示例代碼:

using System;
using System.Linq;
using System.Reflection;
using Xunit;
using Xunit.Abstractions;

namespace CSharp_Attributes_Walkthrough {
    public class UnitTest_Csharp_Attributes {
        private readonly ITestOutputHelper _output;

        private readonly string assemblyFullName = "System.Private.CoreLib, 
                Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e";

        public UnitTest_Csharp_Attributes (ITestOutputHelper output) {
            this._output = output;
        }

        [Fact]
        public void Test_GetAll_BuiltIn_Attributes () {
            var assembly = Assembly.Load (assemblyFullName);

            var attributes = assembly
                .DefinedTypes
                .Where (type =>
                    type
                    .IsSubclassOf (typeof (Attribute)));

            foreach (var attribute in attributes) {
                
                string attr = attribute
                    .Name
                    .Replace ("Attribute", "");

                this._output
                    .WriteLine ("Attribute: {0} and Usage: [{1}]", attribute.Name, attr);
            }
        }
    }
}
           

請參見下面的輸出:

演練C#特性介紹背景什麼是特性?特性類型限制特性的使用

在運作時讀取特性

現在,我們已經回答了什麼是特性,什麼是常用特性,以及如何通過System.Reflection提取内置特性的方法,讓我們看看如何在運作時讀取這些特性,當然使用System.Reflection。

在運作時檢索特性值時,有兩種方法可以檢索值。

  • 使用GetCustomAttributes()方法,這将傳回一個包含指定類型的所有特性的數組。當您不确定哪些特性适用于特定類型時,可以使用此方法,可以周遊此數組。
  • 使用GetCustomAttribute()方法,這将傳回所需的特定特性的詳細資訊。

好的,接下來讓我們來看一個例子。

讓我們首先嘗試建立一個類,并使用一些随機特性對其進行标記。

using System;
using System.Diagnostics;

namespace CSharp_Attributes_Walkthrough.My_Custom_Attributes
{
    [Serializable]
    public class Product
    {
        public string Name { get; set; }
        public string Code { get; set; }

        [Obsolete("This method is already obselete. Use the ProductFullName instead.")]
        public string GetProductFullName()
        {
            return $"{this.Name} {this.Code}";
        }

        [Conditional("DEBUG")]
        public void RunOnlyOnDebugMode()
        {

        }
    }
}
           

Product-class是很容易了解。在下面的示例中,我們要檢查以下内容:

  • 檢查Product-class是否确實具有[Serializable]特性。
  • 檢查Product-class是否具有兩個方法。
  • 檢查每個方法是否确實具有特性。
  • 檢查方法GetProductFullName是否正在使用[Obsolete]特性。
  • 檢查方法RunOnlyDebugMode是否正在使用[Conditional]特性。
/*
*This test will read the Product-class at runtime to check for attributes. 
*1. Check if [Serializable] has been read. 
*2. Check if the product-class has two methods 
*3. Check if each methods does have attributes. 
*4. Check if the method GetProudctFullName is using the Obsolete attribute. 
*5. Check if the method RunOnlyOnDebugMode is using the Conditional attribute.
*/
[Fact]
public void Test_Read_Attributes()
{
    //get the Product-class
    var type = typeof(Product);

    //Get the attributes of the Product-class and we are expecting the [Serializable]
    var attribute = (SerializableAttribute)type.
                    GetCustomAttributes(typeof(SerializableAttribute), false).FirstOrDefault();

    Assert.NotNull(attribute);

    //Check if [Serializable] has been read.
    //Let's check if the type of the attribute is as expected
    Assert.IsType<SerializableAttribute>(attribute);

    //Let's get only those 2 methods that we have declared 
    //and ignore the special names (these are the auto-generated setter/getter)
    var methods = type.GetMethods(BindingFlags.Instance | 
                                    BindingFlags.Public | 
                                    BindingFlags.DeclaredOnly)
                        .Where(method => !method.IsSpecialName).ToArray();

    //Check if the product-class has two methods 
    //Let's check if the Product-class has two methods.
    Assert.True(methods.Length == 2);

    Assert.True(methods[0].Name == "GetProductFullName");
    Assert.True(methods[1].Name == "RunOnlyOnDebugMode");

    //Check if each methods does have attributes. 
    Assert.True(methods.All( method =>method.GetCustomAttributes(false).Length ==1));

    //Let's get the first method and its attribute. 
    var obsoleteAttribute = methods[0].GetCustomAttribute<ObsoleteAttribute>();

    // Check if the method GetProudctFullName is using the Obsolete attributes. 
    Assert.IsType<ObsoleteAttribute>(obsoleteAttribute);

    //Let's get the second method and its attribute. 
    var conditionalAttribute = methods[1].GetCustomAttribute<ConditionalAttribute>();

    //Check if the method RunOnlyOnDebugMode is using the Conditional attributes.
    Assert.IsType<ConditionalAttribute>(conditionalAttribute);
}
           

希望您喜歡上面的示例。然後讓我們進入自定義特性。

自定義特性

内置特性是有用且重要的,但是在大多數情況下,它們具有特定用途。此外,如果您認為出于某種原因需要特性而不考慮内置特性,則可以建立自己的特性。

建立自定義特性

在本部分中,您将看到我們如何建立自定義特性,以及在建立自定義特性時需要記住的内容。

  • 要建立自定義特性,請定義一個派生自System.Attribute的類。
using System;

namespace CSharp_Attributes_Walkthrough.My_Custom_Attributes
{
    public class AliasAttribute : Attribute
    {
        //This is how to define a custom attributes.
    }
}
           
  • 位置參數——如果您的自定義特性的構造函數中有任何參數,它将成為必需的位置參數。
using System;

namespace CSharp_Attributes_Walkthrough.My_Custom_Attributes
{
    public class AliasAttribute : Attribute
    {
        /// <summary>
        /// These parameters will become mandatory once have you decided to use this attribute.
        /// </summary>
        /// <param name="alias"></param>
        /// <param name="color"></param>
        public AliasAttribute(string alias, ConsoleColor color)
        {
            this.Alias = alias;
            this.Color = color;
        }

        public string  Alias { get; private set; }
        public ConsoleColor Color { get; private set; }
    }
}
           
  • 可選參數 ——這些是從System.Attribute派生的類的public字段和public可寫特性。
using CSharp_Attributes_Walkthrough.My_Custom_Attributes;
using System;

namespace CSharp_Attributes_Walkthrough.My_Custom_Attributes
{
    public class AliasAttribute : Attribute
    {
        //....

        //Added an optional-parameter
        public string AlternativeName { get; set; }
    }
}
           

請參見下面的完整示例代碼:

using System;

namespace CSharp_Attributes_Walkthrough.My_Custom_Attributes
{
    public class AliasAttribute : Attribute
    {
        /// <summary>
        /// These parameters will become mandatory once have you decided to use this attribute.
        /// </summary>
        /// <param name="alias"></param>
        /// <param name="color"></param>
        public AliasAttribute(string alias, ConsoleColor color)
        {
            this.Alias = alias;
            this.Color = color;
        }

        #region Positional-Parameters
        public string Alias { get; private set; }
        public ConsoleColor Color { get; private set; }
        #endregion 

        //Added an optional-parameter
        public string AlternativeName { get; set; }
    }
}
           

請參見下圖,以可視化位置參數和可選參數之間的差異。

演練C#特性介紹背景什麼是特性?特性類型限制特性的使用

現在我們已經建立了一個自定義特性,讓我們嘗試在一個類中使用它。

在類中應用自定義特性

using System;
using System.Linq;

namespace CSharp_Attributes_Walkthrough.My_Custom_Attributes
{
    [Alias("Filipino_Customers", ConsoleColor.Yellow)]
    public class Customer
    {
        [Alias("Fname", ConsoleColor.White, AlternativeName = "Customer_FirstName")]
        public string Firstname { get; set; }

        [Alias("Lname", ConsoleColor.White, AlternativeName = "Customer_LastName")]
        public string LastName { get; set; }

        public override string ToString()
        {
            //get the current running instance.
            Type instanceType = this.GetType(); 

            //get the namespace of the running instance.
            string current_namespace = (instanceType.Namespace) ?? "";

            //get the alias.
            string alias = (this.GetType().GetCustomAttributes(false).FirstOrDefault() 
                            as AliasAttribute)?.Alias;

            return $"{current_namespace}.{alias}";
        }
    }
}
           

該示例的主要内容是ToString()方法,默​​認情況下将傳回該類型的全限定名稱。然而; 我們在這裡所做的是,我們已經覆寫了ToString()傳回特性的全限定名稱和别名的方法。

現在讓我們嘗試調用該ToString()方法,看看它傳回什麼。請參見下面的示例,輸出:

using CSharp_Attributes_Walkthrough.My_Custom_Attributes;
using System;

namespace Implementing_Csharp_Attributes_101
{
    class Program
    {
        static void Main(string[] args)
        {
            var customer = new Customer { Firstname = "Jin Vincent" , LastName = "Necesario" };
          
            var aliasAttributeType = customer.GetType();

            var attribute = 
                aliasAttributeType.GetCustomAttributes(typeof(AliasAttribute), false);

            Console.ForegroundColor = ((AliasAttribute)attribute[0]).Color;

            Console.WriteLine(customer.ToString());

            Console.ReadLine();
        }
    }
}
           
演練C#特性介紹背景什麼是特性?特性類型限制特性的使用

限制特性的使用

預設情況下,您可以将自定義特性應用于應用程式代碼中的任何實體。是以,當您建立自定義特性時,它可以應用于類、方法、私有字段、特性、結構等。但是,如果您希望将自定義特性限制為僅出現在某些類型的實體上。您可以使用AttributeUsage特性來控制可以将其應用于哪些實體。

目标
AttributeTargets.All 可以應用于應用程式中的任何實體
AttributeTargets.Assembly 可以應用于程式集
AttributeTargets.Class 可以應用于類
AttributeTargets.Construtor 可以應用于構造函數
AttributeTargets.Delegate 可以應用于代理
AttributeTargets.Enum 可以應用于枚舉
AttributeTargets.Event 可以應用于事件
AttributeTargets.Field 可以應用于字段
AttributeTargets.Interface 可以應用于接口
AttributeTargets.Method 可以應用于方法
AttributeTargets.Module 可以應用于子產品
AttributeTargets.Parameter 可以應用于參數
AttributeTargets.Property 可以應用于屬性
AttributeTargets.ReturnValue 可以應用于傳回值
AttributeTargets.Struct 可以應用于結構

如果您想知道,我們如何在運作時獲得這些AttributeTargets?請參閱以下示例:

[Fact]
public void Test_GetAll_AttributeTargets()
{
    var targets = Enum.GetNames(typeof(AttributeTargets));

    foreach (var target in targets)
    {
        this._output.WriteLine($"AttributeTargets.{target}");
    }
}
           

繼續閱讀