一、特性是什么東東


前言

我們初學C#的時候看到類上面一對中括號里面有個高亮了的關鍵字,不知道那是什么有什么用。想問人又不知道它叫什么。糾結的要命。其實,它就是特性。如:

這就是我們今天要分析的主題。

特性是什么?

個人理解,特性就是修飾對象元數據的修飾符。

那么什么是“元數據”?

元數據就是用來描述數據的數據。(挺拗口的)

如:

圖中的1.是特性 2.是訪問修飾符 3.聲明修飾符 4.數據類型 5.變量名 6.變量數據值,其中1、2、3、4、5就是元數據,用來描述數據(6)的數據。

特性到底是什么?

如上面的 Obsolete  ,會不會也是一個如 public  static 這樣類似的修飾符呢,我們且看看反編譯后的中間語言。

意料之外,我們看到了上面的2、3、4、5,而1(特性)怎么跑到里面去了,且是一種看不懂的東東,反正我們知道了不是類似的修飾符。

然后我們接着在vs里面把光標移到 Obsolete  上按F12,如:

原來只是一個繼承了 Attrbute 的一個類(class)。那么上面我們看不懂的部分應該就是這個 ObsoleteAttribute 類的實例化了。

我們來回答上面問題:特性到底是什么?特性只是一個類而已。

我們自定義一個特性玩玩

我們看到上面系統特性 Obsolete 上面還有特性,如:Serializable、AttributeUsage、Camvisible等。像這種特性我們稱之為“元數據的元數據”(元元數據)。

1.我們分別來解釋性上面的三個特性。

Serializable:表示類型支持序列化。

ComVisible:微軟定義“控制程序集中個別托管類型、 成員或所有類型對COM的可訪問性”。

AttributeUsage:這個比較重要了,基本上每個特性定義都用到了它。它就是用來表示當前這個特性可用於哪些對象。如:類、方法、屬性...等等。(只需要用到這個我們就可以自定義特性了)

2.上面有個問題,不知道大家發現沒有。

就是我們特性名明明是 Obsolete  ,為什么我們F12進去后變成了 ObsoleteAttribute 呢?這其實只是一個微軟的約定而已,沒有為什么。

其實我們可以兩種寫法: [ObsoleteAttribute("已過時")] 和 [Obsolete("已過時")] 是等效的,只是我們一般都用后面這種。

3.定義的特性必須繼承於 Attribute 。

4.屬性沒有set方法。只能通過構造函數賦值。這是因為特性語法所致,因為特性的定義只存在單行的中括號中,不能實例化之后在設置屬性,所以全部的設置都在后面的小括號里進行的。如果需要有set屬性,我們就要用到命名參數,下面會繼續講到

好了,我們通過這四點完全可以自己定義個特性來玩玩了。我們來定義一個給機器看的注釋。我們平時的注釋都只是給程序員看的,編譯之后就全沒了。那我們想在代碼運行時,彈出我們的注釋怎么辦,接下來我們用自定義特性來實現,如:

[AttributeUsage(AttributeTargets.All)]//3.設置可用於哪些對象
public class TMessgAttribute : Attribute//1.定義類TMessg加上后綴TMessgAttribute 2.繼承Attribute。
{
    public TMessgAttribute() { }

    /// <param name="createTime">創建時間</param>
    /// <param name="createName">創建人</param>
    public TMessgAttribute(string createTime, string createName, string mess)
    {
        this._createName = createName;
        this._createTime = createTime;
        this._mess = mess;
    }

    private string _createTime;
    public string createTime
    {
        get { return _createTime; }//4.只能有get方法
    }
    private string _createName;
    public string createName
    {
        get { return _createName; }
    }
    private string _mess;
    public string mess { get { return _mess; } }
}

好了,上面就是我們自定義的特性。那我們怎樣使用呢。和系統特性一樣。我們先定義一個測試類TClass,然后在類上面定義特性,如:

[TMessg("2015-12-20", "zhaopei", "我只是測試自定義特性,不要報錯哦,求求你了。")]
public class TClass
{
    //................
}

我們定義了特性,也使用了特性,然我們卻不知道怎么看效果。我們想看到效果怎么辦。可以使用反射(下篇博問繼續分析反射)看看 TClass 類的元數據,如:

static void Main(string[] args)
{
    System.Reflection.MemberInfo info = typeof(TClass); //通過反射得到TClass類的信息
    TMessgAttribute hobbyAttr = (TMessgAttribute)Attribute.GetCustomAttribute(info, typeof(TMessgAttribute));
    Console.WriteLine("類名:{0}", info.Name);
    Console.WriteLine("創建時間:{0}", hobbyAttr.createTime);
    Console.WriteLine("創建人:{0}", hobbyAttr.createName);
    Console.WriteLine("備注消息:{0}", hobbyAttr.mess);
    Console.ReadKey();
}

打印效果如:

什么是命名參數?

上面的自定義特性都是通過構造函數設置字段私有字段,然后通過只提供了get的屬性來訪問。那么可否直接在特性里面定義擁有get和set的屬性嗎?答案是肯定的。那怎么在使用特性的時候設置這個屬性呢?我們接着往下看。

我們接着在自定義特性里面添加一個屬性。

/// <summary>
/// 修改時間
/// </summary>
public string modifyTime { get; set; }

使用自定義特性。

[TMessg("2015-12-20", "zhaopei", "我只是測試自定義特性,不要報錯哦,求求你了。", modifyTime = "2015-12-21")]
public class TClass
{
    //................
}

我們發現,直接在輸入了構造函數之后接着設置屬性就可以。(這就相當於可選參數了,屬性當然可以隨便你是否設置了。不過這里需要注意了,前面的參數一定要按照定義的特性構造函數的參數順序

這種參數,我們成為命名參數。

我們來繼續要看看AttributeUsage(這個描述特性的特性--“元元數據”

我們F12看看AttributeUsage的定義

看上去,同樣也只是普通的特性。實際上也只是個普通的特性。>_<

我們來看看他的這幾個屬性是干嘛的。從最后一個開始看。

1.AttributeTargets,我們在上面其實就已經看到並也已經使用了。

我們設置的是可用於所有對象。AttributeTargets其實是個枚舉,每個值對於一個類型對象。

你可以直接在 AttributeTargets F12進去:

 我們看到了每個值代表可以用於所對於的對象類型。

2.Inherited(是一個布爾值):“如果該屬性可由派生類和重寫成員繼承,則為 true,否則為 false。 默認值為 true

如下,我們設置 Inherited = false 那么繼承TClass的T2Class無法訪問到TClass中設置的特性元數據。

namespace net
{
    [AttributeUsage(AttributeTargets.All, Inherited = false)]//3.設置可用於哪些對象    
    public class TMessgAttribute : Attribute//1.定義類TMessg加上后綴TMessgAttribute 2.繼承Attribute。
    {
        public TMessgAttribute() { }

        /// <param name="createTime">創建時間</param>
        /// <param name="createName">創建人</param>
        public TMessgAttribute(string createTime, string createName, string mess)
        {
            this._createName = createName;
            this._createTime = createTime;
            this._mess = mess;
        }

        private string _createTime;
        public string createTime
        {
            get { return _createTime; }//4.只能有get方法
        }
        private string _createName;
        public string createName
        {
            get { return _createName; }
        }
        private string _mess;
        public string mess { get { return _mess; } }
        /// <summary>
        /// 修改時間
        /// </summary>
        public string modifyTime { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            System.Reflection.MemberInfo info = typeof(T2Class); //通過反射得到TClass類的信息
            TMessgAttribute hobbyAttr = (TMessgAttribute)Attribute.GetCustomAttribute(info, typeof(TMessgAttribute));
            Console.WriteLine("類名:{0}", info.Name);
            if (hobbyAttr != null)
            {
                Console.WriteLine("創建時間:{0}", hobbyAttr.createTime);
                Console.WriteLine("創建人:{0}", hobbyAttr.createName);
                Console.WriteLine("備注消息:{0}", hobbyAttr.mess);
                Console.WriteLine("修改時間:{0}", hobbyAttr.modifyTime);

            }
            Console.ReadKey();
        }
    }

    [TMessg("2015-12-20", "zhaopei", "我只是測試自定義特性,不要報錯哦,求求你了。", modifyTime = "2015-12-21")]
    public class TClass
    {
        //................
    }

    public class T2Class : TClass
    {
        //...........
    }
}
View Code

反之,我們設置 Inherited = true (或者不設置任何,因為默認就是true)打印如下:

3.AllowMultiple(也是一個布爾值):“如果允許指定多個實例,則為 true;否則為 false。 默認值為 false。

我們設置兩個特性試試,如:

如果我們想要這樣設置怎么辦。在AttributeUsage中設置 AllowMultiple = true 如:

那么上面報錯的地方將會打印:

注意:上面的打印地方的代碼需要修改。因為之前是打印一個特性信息,這里是打印一個特性數組集合的信息。

static void Main(string[] args)
{
    System.Reflection.MemberInfo info = typeof(T2Class);
    TMessgAttribute[] hobbyAttr = (TMessgAttribute[])Attribute.GetCustomAttributes(info, typeof(TMessgAttribute));//修改1.這里需要取特性數據的集合了
    Console.WriteLine("類名:{0}", info.Name);
    for (int i = 0; i < hobbyAttr.Count(); i++)//修改2.這里需要循環打印了
    {
        Console.WriteLine("================================================");
        Console.WriteLine("創建人:{0}", hobbyAttr[i].createName);
        Console.WriteLine("創建時間:{0}", hobbyAttr[i].createTime); 
        Console.WriteLine("備注消息:{0}", hobbyAttr[i].mess);
        Console.WriteLine("修改時間:{0}", hobbyAttr[i].modifyTime);
    }

    Console.ReadKey();

全部代碼:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace net
{
    [AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = true)]//3.設置可用於哪些對象    
    public class TMessgAttribute : Attribute//1.定義類TMessg加上后綴TMessgAttribute 2.繼承Attribute。
    {
        public TMessgAttribute() { }

        /// <param name="createTime">創建時間</param>
        /// <param name="createName">創建人</param>
        public TMessgAttribute(string createTime, string createName, string mess)
        {
            this._createName = createName;
            this._createTime = createTime;
            this._mess = mess;
        }

        private string _createTime;
        public string createTime
        {
            get { return _createTime; }//4.只能有get方法
        }
        private string _createName;
        public string createName
        {
            get { return _createName; }
        }
        private string _mess;
        public string mess { get { return _mess; } }
        /// <summary>
        /// 修改時間
        /// </summary>
        public string modifyTime { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            System.Reflection.MemberInfo info = typeof(T2Class);
            TMessgAttribute[] hobbyAttr = (TMessgAttribute[])Attribute.GetCustomAttributes(info, typeof(TMessgAttribute));//修改1.這里需要取特性數據的集合了
            Console.WriteLine("類名:{0}", info.Name);
            for (int i = 0; i < hobbyAttr.Count(); i++)//修改2.這里需要循環打印了
            {
                Console.WriteLine("================================================");
                Console.WriteLine("創建人:{0}", hobbyAttr[i].createName);
                Console.WriteLine("創建時間:{0}", hobbyAttr[i].createTime); 
                Console.WriteLine("備注消息:{0}", hobbyAttr[i].mess);
                Console.WriteLine("修改時間:{0}", hobbyAttr[i].modifyTime);
            }

            Console.ReadKey();
        }
    }

    [TMessg("2015-12-20", "zhaopei", "我只是測試自定義特性,不要報錯哦,求求你了。", modifyTime = "2015-12-21")]
    [TMessg("2015-12-21", "zhaopei", "我再次測試,還能給我面子顯示出來嗎?", modifyTime = "2015-12-22")]
    public class TClass
    {
        //................
    }

    public class T2Class : TClass
    {
        //...........
    }
}
View Code

自定義特性可以干什么?

上面我們通過反編譯,發現自定義特性實際上就是一個對象調用的最前面加了一段實例化的代碼。

那么我們可以做的事可多了,除了像上面一樣為對象設置“注釋”,我們還可以自定義個特性,給某些方法或是某些類做“操作日志記錄”,或者給需要在執行某些方法的時候需要權限,我們可以做個權限認證的特性等等。

這里就需要大家自己去擴展了。

 

本文已同步至《C#基礎知識鞏固系列》 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM