1. 程式人生 > >C# 特性詳解(Attribute)

C# 特性詳解(Attribute)

今天整理關於特性的資料,之前都忘了,今天整理一下
參考《C#高階程式設計》第10版

0X01 特性(Attribute)

特性定義

特性不會影響編譯過程,因為編譯器不能識別它們,但這些特性在應用於程式元素時,可以在編譯好的程式集中用作元資料
上面這句話是書上說的,但不太認可,如果通過反射來使用特性呢

這裡假設某個類上使用了特性如下:

    [Test]
    class BBB
    {    }

當編譯器發現這個類BBB應用了Test特性時,首先會把字串Attribute追加到這個名稱後面,形成一個組合名稱
TestAttribute

,然後在其搜尋路徑的所有名稱空間 (即using使用的名稱空間)中搜索有指定名稱的類,如果特性本身已Attribute結尾,編譯器就不會再追加Attritude了,而是不修改該特性名。如下

    [TestAttritude]
    class BBB
    {    }

編譯器會找到含有該名稱的類(TestAttribute),且這個類直接或者間接派生自System.Attribute。編譯器還人為這個類包含控制特性用法的資訊。
現在來看這個類的定義

    public enum Flag
    {
        None,
        Input,
        Output
    }
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.ReturnValue | AttributeTargets.Assembly,AllowMultiple=true,Inherited=false)] class TestAttribute:Attribute { public TestAttribute(Flag flag = Flag.None) { }
}

指定AttributeUsage特性

引申:
特性(Attribute)本身用特性(System.AttributeUsage)來標記,C#編譯器為它提供了特殊支援,其實AttributeUsage不能認為是一個特性了,應該理解為元特性,它只能用來標記特性類,不能標記其它的類。
使用:
AttributeUsage主要用於標識自定義特性可以應用到哪些型別的程式元素上。這些資訊由它的第一個引數給出(AttributeTargets),像上面使用的一樣。

AttributeTargets的定義

    public sealed class AttributeUsageAttribute : Attribute
    {
        // 摘要: 
        //     用指定的 System.AttributeTargets、System.AttributeUsageAttribute.AllowMultiple
        //     值和 System.AttributeUsageAttribute.Inherited 值列表初始化 System.AttributeUsageAttribute
        //     類的新例項。
        //
        // 引數: 
        //   validOn:
        //     使用按位"或"運算子組合的一組值,用於指示哪些程式元素是有效的。
        public AttributeUsageAttribute(AttributeTargets validOn);

        // 摘要: 
        //     獲取或設定一個布林值,該值指示能否為一個程式元素指定多個指示屬性例項。
        //
        // 返回結果: 
        //     如果允許指定多個例項,則為 true;否則為 false。 預設值為 false。
        public bool AllowMultiple { get; set; }
        //
        // 摘要: 
        //     獲取或設定一個布林值,該值指示指示的屬效能否由派生類和重寫成員繼承。
        //
        // 返回結果: 
        //     如果該屬性可由派生類和重寫成員繼承,則為 true,否則為 false。 預設值為 true。
        public bool Inherited { get; set; }
        //
        // 摘要: 
        //     獲取一組值,這組值標識指示的屬性可應用到的程式元素。
        //
        // 返回結果: 
        //     一個或多個 System.AttributeTargets 值。 預設值為 All。
        public AttributeTargets ValidOn { get; }
    }

AttributeTargets的定義

    // 摘要: 
    //     指定可以對它們應用特性的應用程式元素。
    [Serializable]
    [ComVisible(true)]
    [Flags]
    public enum AttributeTargets
    {
        // 摘要: 
        //     可以對程式集應用屬性。
        Assembly = 1,
        //
        // 摘要: 
        //     可以對模組應用屬性。
        Module = 2,
        //
        // 摘要: 
        //     可以對類應用屬性。
        Class = 4,
        //
        // 摘要: 
        //     可以對結構應用屬性,即值型別。
        Struct = 8,
        //
        // 摘要: 
        //     可以對列舉應用屬性。
        Enum = 16,
        //
        // 摘要: 
        //     可以對建構函式應用屬性。
        Constructor = 32,
        //
        // 摘要: 
        //     可以對方法應用屬性。
        Method = 64,
        //
        // 摘要: 
        //     可以對屬性 (Property) 應用屬性 (Attribute)。
        Property = 128,
        //
        // 摘要: 
        //     可以對欄位應用屬性。
        Field = 256,
        //
        // 摘要: 
        //     可以對事件應用屬性。
        Event = 512,
        //
        // 摘要: 
        //     可以對介面應用屬性。
        Interface = 1024,
        //
        // 摘要: 
        //     可以對引數應用屬性。
        Parameter = 2048,
        //
        // 摘要: 
        //     可以對委託應用屬性。
        Delegate = 4096,
        //
        // 摘要: 
        //     可以對返回值應用屬性。
        ReturnValue = 8192,
        //
        // 摘要: 
        //     可以對泛型引數應用屬性。
        GenericParameter = 16384,
        //
        // 摘要: 
        //     可以對任何應用程式元素應用屬性。
        All = 32767,
    }

特性應用於Assembly或者Module時,要這樣寫:
[assembly: someAssemblyAttribute(Parameters)]
[module: someAssemblyAttribute(Parameters)]
在指定自定義特性的有效目標元素時可以用為操作 |或運算子來組合 ,例如

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.ReturnValue | AttributeTargets.Assembly,AllowMultiple=true,Inherited=false)]
    class TestAttribute:Attribute

該類有兩個可選引數AllowMultipleInherited
AllowMultiple 引數表示一個特性是否能夠多次應用在同一項上。
Inherited 引數如果為true,表示應用到類或者介面上的特性也自動應用到所有派生類或者介面上。如果應用到方法或者屬性上,就可以自動應用到該方法或屬性等重寫的版本上

using AttributeTest;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

[assembly: TestAttribute]
[module: TestAttribute]
namespace AttributeTest
{
    
    /// <summary>
    /// AllowMultiple=true 表示同一個元素可以被應用多次該特性
    /// Inherited=true 表示特性可以被繼承
    /// </summary>
    [AttributeUsage(AttributeTargets.All,AllowMultiple=true,Inherited=true)]
    class TestAttribute:Attribute
    {
        /// <summary>
        /// 可選引數
        /// </summary>
        public int flag;
        /// <summary>
        /// 可選引數
        /// </summary>
        public bool isInner;
        /// <summary>
        /// 建構函式的引數是和正常建構函式傳參一樣的
        /// </summary>
        /// <param name="arg"></param>
        public TestAttribute(string arg=null){  }
    }

    [Test("class",flag=3,isInner=true)]
    class AAA
    {
        [Test("constructor",flag=2)]
        public AAA(){       }
        [Test]
        [Test]
        [Test("hello method")]
        public void SSS(){        }
    }

    /// <summary>
    /// 這裡特性的建構函式預設引數就是arg=null,可選引數沒有傳,按照正常預設值處理
    /// </summary>
    [Test]
    class BBB{   }
    class Program
    {
        static void Main(string[] args)
        {


        }
    }
}

指定特性引數

特性引數就是特性建構函式的引數,參考上面例子arg傳參

指定特性可選引數

如上面例子的flag和isInner用法

0X02 特性例子

直接上程式碼

using AttributeTest;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

[assembly: TestAttribute]
[module: TestAttribute]
namespace AttributeTest
{
    
    /// <summary>
    /// AllowMultiple=true 表示同一個元素可以被應用多次該特性
    /// Inherited=true 表示特性可以被繼承
    /// </summary>
    [AttributeUsage(AttributeTargets.All,AllowMultiple=true,Inherited=true)]
    class TestAttribute:Attribute
    {
        /// <summary>
        /// 可選引數
        /// </summary>
        public int flag;
        /// <summary>
        /// 可選引數
        /// </summary>
        public bool isInner;
        /// <summary>
        /// 建構函式的引數是和正常建構函式傳參一樣的
        /// </summary>
        /// <param name="arg"></param>
        public TestAttribute(string arg=null){  }
    }

    [Test("class",flag=3,isInner=true)]
    class AAA
    {
        [Test("constructor",flag=2)]
        public AAA(){       }
        [Test]
        [Test(flag=10)]
        [Test("hello method",flag=9)]
        public void SSS(){        }
    }

    /// <summary>
    /// 這裡特性的建構函式預設引數就是arg=null,可選引數沒有傳,按照正常預設值處理
    /// </summary>
    [Test]
    class BBB{   }
    class Program
    {
        static void Main(string[] args)
        {
            Type t1 = typeof(AAA);
             object[] objs= t1.GetCustomAttributes(typeof(TestAttribute),false);
             foreach (var item in objs)
             {
                 Console.WriteLine("AAA:::::::" + ((TestAttribute)item).flag);
             }
             foreach (var item in t1.GetMethod("SSS").GetCustomAttributes(typeof(TestAttribute), false))
            {
                Console.WriteLine("AAA::Method:::::" + ((TestAttribute)item).flag);
            }


            foreach (var item in typeof(BBB).GetCustomAttributes(typeof(TestAttribute), false))
            {
                Console.WriteLine("BBB:::::::" + ((TestAttribute)item).flag);
            }


        }
    }
}

結果:

在這裡插入圖片描述