《CLR via C#》讀書筆記-Attribute

什麼是Attribute,Attribute的作用是什麼

在代碼中,有時需要攜帶一些額外的信息。可以限定代碼的運行範圍,決定代碼是否對外可見;也可以是一些解釋性的信息,保證程序的正常運行。而我們最常見、最常用的Attribute就是public、private、static之類,是不是有點小驚訝。而Attribute的作用就類似public、private一樣,將附加信息與目標元素關聯起來。因此更通俗的說法,Attribute是一種代碼與數據的關聯方式。有點類似加了葡萄乾或其他佐料的曲奇餅乾。

Attribute的代碼定義

MSDN中Attribute的定義如下

[System.AttributeUsage(System.AttributeTargets.All, AllowMultiple=false, Inherited=true)]
[System.Runtime.InteropServices.ClassInterface(System.Runtime.InteropServices.ClassInterfaceType.None)]
[System.Runtime.InteropServices.ComVisible(true)]
public abstract class Attribute : System.Runtime.InteropServices._Attribute

由代碼可知,Attribute是一個類。其實現了_Attribute接口,而該接口派生自:System.Attribute。

Attribute怎麼用

可能在工作中,用的最多的Attribute就是DllImport了,如下所示。

[DllImport(passDllPath, EntryPoint = "MDC_DoSetDrug", CharSet = CharSet.Ansi)]
public static extern int MDC_DoSetDrug(string pcDrugUniqueCode, string pcDrugName);

Attribute可以用在什麼地方

assembly, class, constructor, delegate, enum, event, field, interface, method, portable executable file module, parameter, property, return value, struct, or another attribute

基本上平時見到的都可以用

Attribute使用過程中的注意點

1、Attribute是一個類,因此使用時,需要向其傳遞構造器必須的參數,以及初始化一些屬性或設定。構造器的必須參數,被稱之爲“定位參數”(Positional Parameter),順手初始化的屬性或公共字段的參數,稱之爲“命名參數”(Named Parameter)。前者是必須,後者是選擇項。在VS使用中,智能提醒如下所示。裏面明確的指出,哪些是必填的定位參數,哪些是選填的命名參數。
這裏寫圖片描述
2、可以將多個Attribute用於單個目標元素。Attribute的先後順序無所謂。多個Attribute可以放在一箇中括號內,也可以多個。另外,若Attribute的構造器不需要參數,則Attribute後面的圓括弧也可以刪除。舉例,以下都是相同的:

        [Serializable][Flags]
        [Serializable,Flags]
        [SerializableAttribute,FlagsAttribute]
        [FlagsAttribute(), SerializableAttribute()]

Attribute如何在項目中使用

使用.NET中自定義的Attribute

(一)常用的Attribute有:
1、DllImport應用於方法,是指告訴CLR,方法的實現位於制定路徑的**非託管**DLL中。
2、Flags應用於枚舉類型。告訴CLR,將枚舉類型作爲一個標誌位集合使用
3、Serializable應用於類型。告訴序列格式化器,一個實例的字段可以序列化和反序列化。
….
具體詳細的可以參見:MSDN上Attribute的介紹內容

(二)使用時的注意事項或語法
在Attribute使用前,需要有一些前綴。例如:[method:DllImport(“…..”)],有時,前綴可以省略,編譯器會自動判斷。但有一些不能省略。以下是Attribute的語法。下面代碼的帶有“//前綴必須”代表不能刪除該前綴

using System;

[assembly:SomeAttr] //前綴必須
[module:SomeAttr]   //前綴必須

namespace NETAttribute
{
    [type:SomeAttr]
    class AttributeGrammar<[typevar:SomeAttr] T>
    {
        [field:SomeAttr]
        public int SomeFiled = 0;

        [return: SomeAttr]  //前綴必須
        [method: SomeAttr]
        public int SomeMethod([param:SomeAttr]int someParam)
        {
            return SomeParam
        }

        [property: SomeAttr]
        public string SomeProp
        {
            [method: SomeAttr]
            get { return null; }
        }

        [event: SomeAttr]
        [field: SomeAttr]   //前綴必須
        [method: SomeAttr]  //前綴必須
        public event EventHandler SomeEvent;
    }
}

自定義Attribute

另外,在項目中,需要攜帶額外的信息,但又不想從數據庫或配置文件中讀取,則可以試試自定義Attribute,用自定義Attribute的形式攜帶額外信息。

自定義Attribute的要求

需要滿足兩個條件
1、必須繼承system.Attribute抽象類
2、所有非抽象的Attribute至少包含一個公共構造器
另外,根據“行規”,自定義的Attribute要以“Attribute”字母結尾。
原則
Attribute的本質是一個類,但這個類只是提供一些基本的狀態信息,或用作邏輯狀態的一個容器。不應該在該類中實現複雜的方法、邏輯,更不應該提供方法、事件或者其他成員。若需要這些,請單獨編寫一個邏輯類(class文件)。不要把Attribute搞得複雜。

自定Attribute的使用

當實現自定義的Attribute後,就需要在程序中使用它。最主要的就是,獲取自定義Attribute的值。
如下所示:

using System;
using System.Reflection;
using System.Diagnostics;


[assembly:CLSCompliant(true)]

namespace NETAttribute
{
    [Serializable]
    [DefaultMember("Main")]
    [DebuggerDisplay("Zheng",Name ="Lin",Target =typeof(Program))]
    public class Program
    {
        [Conditional("Debug")]
        [Conditional("Release")]
        public void SomeMethod() { }



        public Program() { }

        [CLSCompliant(true)]
        [STAThread]
        public static void Main(string[] args) //此處Main方法需爲public,否則Main不會暴露,[DefaultMember("Main")]將無法應用到Main上
        {
            ShowAttributes(typeof(Program));

            MemberInfo[] members = typeof(Program).FindMembers(MemberTypes.Constructor | MemberTypes.Method,
                BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.Static,
                Type.FilterName, "*");

            foreach (MemberInfo item in members)
            {
                ShowAttributes(item);
            }

            Console.Read();
        }



        //獲取當前類型中的Attribute設定值
        private static void ShowAttributes(MemberInfo attributeTarget)
        {
            //獲取目標上的Attribute
            Attribute[] attributes = Attribute.GetCustomAttributes(attributeTarget);

            //展示目標信息
            Console.WriteLine("當前目標的名稱是{0},本目標上的Attribute有:{1}", attributeTarget.Name, (attributes.Length == 0 ? "無" : string.Empty));


            //遍歷Attributes並將信息展示
            foreach (Attribute item in attributes)
            {
                //顯示Attribute的類型
                Console.WriteLine("Attribute的名稱是{0}",item.GetType().ToString());

                if (item is DefaultMemberAttribute)
                    Console.WriteLine("MemberName={0}",((DefaultMemberAttribute)item).MemberName);
                if (item is ConditionalAttribute)
                    Console.WriteLine("ConditionString={0}",((ConditionalAttribute)item).ConditionString);
                if (item is CLSCompliantAttribute)
                    Console.WriteLine("IsCLSCompliant={0}",((CLSCompliantAttribute)item).IsCompliant);

                DebuggerDisplayAttribute dda = item as DebuggerDisplayAttribute;
                if (dda != null)
                {
                    Console.WriteLine("Value={0},Name={1},Target={2}", dda.Value, dda.Name, dda.Target);
                }
            }

            Console.WriteLine();
        }
    }
}

輸出結果爲:
輸出的結果

使用反射查詢內容

上面的例子是使用Attribute靜態方法查詢相關方法,另外也可以使用反射實現。

重構ShowAttributes方法

/獲取當前類型中的Attribute設定值
        private static void ShowAttributesBuReflection(MemberInfo attributeTarget)
        {
            //獲取目標上的Attribute
            IList<CustomAttributeData> attributes = CustomAttributeData.GetCustomAttributes(attributeTarget);

            //展示目標信息
            Console.WriteLine("當前目標的名稱是{0},本目標上的Attribute有:{1}", attributeTarget.Name, (attributes.Count == 0 ? "無" : string.Empty));


            //遍歷Attributes並將信息展示
            foreach (CustomAttributeData item in attributes)
            {

                Type t = item.Constructor.DeclaringType;

                Console.WriteLine("{0}", item.ToString());
                Console.WriteLine("構造器={0}", item.Constructor);

                IList<CustomAttributeTypedArgument> posArgs= item.ConstructorArguments;
                Console.WriteLine("是否存在定位參數={0}",(posArgs.Count==0)?"否":string.Empty);
                foreach (CustomAttributeTypedArgument m in posArgs)
                {
                    Console.WriteLine("參數的類型:{0},參數值:{1}",m.ArgumentType,m.Value);
                }

                IList<CustomAttributeNamedArgument> namedArgs = item.NamedArguments;
                Console.WriteLine("是否存在命名參數={0}", (namedArgs.Count == 0) ? "否" : string.Empty);
                foreach (CustomAttributeNamedArgument m in namedArgs)
                {
                    Console.WriteLine("參數名稱:{0},參數的類型:{1},參數值:{2}", m.MemberInfo.Name,
                        m.TypedValue.ArgumentType, m.TypedValue.Value);
                }
            }

            Console.WriteLine();
        }

運行結果如下:
輸出結果

總結

上面的兩個例子,明顯的能夠看到通過反射查詢和自身類靜態方法查詢的區別

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章