C# 自定義特性(Attribute)詳解


什么是特性


特性的定義:公共語言運行時允許添加類似關鍵字的描述聲明,叫做attribute,它對程序中的元素進行標注,如類型、字段、方法、和屬性等。attribute和.NetFramework文件的元數據保存在一起,可以用來在運行時描述你的代碼,或者在程序運行的時候影響應用程序的行為。

如何編寫自定義特性


為了幫助大家理解自定義的特性,首先帶大家了解一下編譯器遇到代碼中某個應用了自定義特性時,是如何處理的,以檢驗Model為例,假如聲明一個C#屬性,如下

    public class User
    {
        /// <summary>
        /// 名稱
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 郵箱
        /// </summary>
        [EmailAttribute]
        public string Email { get; set; }

        /// <summary>
        /// 薪水
        /// </summary>
        [LenghtAttribute(10000, 100000)]
        public decimal Salary { get; set; }
    }

    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = = false, Inherited = false)]
    public class LenghtAttribute
    {
        private int _minLenght = 0;
        private int _maxLenght = 0;
        public LenghtAttribute(int minLenght, int maxLenght)
        {
            _minLenght = minLenght;
            _maxLenght = maxLenght;
        }
    }

當編譯器發現這個屬性應用了一個FiedlName特性時,先會將字符串Attribute追加到這個名稱上,形成一個組合名稱FieldNameAttribute,然后搜索路徑的所有名稱空間中搜索有指定該名稱的類,但是注意,如果編譯器發現了有某個特性標記了數據項,並且該名稱為Attribute結尾,編譯器不會將該字符加到組合名稱中,所以這就是我們用特性標注字段是編譯器會默認將Attribute字符以淺藍色字體顯示的原因
之后編譯器會找到含有該名稱的類,且這個類直接或間接派生自System.Attribute。編譯器還認為這個類包含控制特性用法的信息。特別是

  1. 特性可以應用到那些類型的程序元素上
  2. 它是否可以多次應用到同一個程序元素上
  3. 特性在應用到類或接口上時,是否由派生類類和接口繼承
  4. 這個特性有哪些必選參數和可選參數

如果找不到指定的特性類,或者找到一個特性類,但使用特性的方式和特性類中的信息不匹配,編譯器就會產生一個錯誤,比如特性類指定該特性只能應用於結構上,但我們應用在字段或者類上,就會產生編譯錯誤


一、指定AttributeUsage類

首先,特性類本身是需要用一個特性--system.AttributeUsage特性來標記的,AttributeUsage用來表示我們的特性可以應用在那些類型的程序元素上,這個參數在AttributeUsage是必選參數,類型是枚舉類型 AttributeTargets。具體如下


1.Assembly = 0x1,
2.Module = 0x2,
3.Class = 0x4,
4.Struct = 0x8,
5.Enum = 0x10,
6.Constructor = 0x20,
7.Method = 0x40,
8.Property = 0x80,
9.Field = 0x100,
10.Event = 0x200,
11.Interface = 0x400,
12.Parameter = 0x800,
13.Delegate = 0x1000,
14.ReturnValue = 0x2000,
15.GenericParameter = 0x4000,
16.All = 0x7FFF


如果大家想了解枚舉對應的類型可以去System.AttributeTargets下看到詳細介紹,這里就不給大家一一介紹了
但是大家需要注意,在上面的枚舉類型中有兩個枚舉值不對應任何程序元素, Assembly和Module
特性可以應用到整個程序集或者模塊中,而不是應用在代碼中的某一個元素上,在這種情況下,這個特性可以放在源代碼的任何地方,但需要使用Assembly和Module,寫法如下

    public class User
    {
        [Assembly: FieldNameAttribute(Parameters)]
        [Module: FieldNameAttribute(Parameters)]
    }

另外還有兩個屬性AllowMultiple和Inherited
AllowMultiple表示該特性是否可以多次運用到同一項上,
Inherited表示應用到類或者接口上的特性可以自動應用到所有派生的類和接口上,如果應用到方法和屬性上,也可以自動應用到該方法和屬性的重寫版本上,

二、指定特性參數

接下來給大家介紹如何自定義特性接受的參數。
編譯器會檢查傳遞給特性的參數,並查找該特性中帶這些參數類型的構造函數,如果編譯器找到了這樣的構造函數,編譯器就會將指定的元數據傳遞給程序集,反之就會生成一個編譯錯誤。

三、指定特性的可選參數

在AttributeUsaege特性中,可以使用另外一種語法,把可選參數添加到特性中,這種語法指定可選參數的名稱和值,它通過特性類中的公共屬性和字段起作用。

四、完整代碼

  public abstract class BaseAttribute : Attribute
    {
        /// <summary>
        /// 驗證
        /// </summary>
        /// <param name="oValue"></param>
        /// <returns></returns>
        public abstract bool Validate(object oValue);
    }
    /// <summary>
    /// 特性擴展
    /// </summary>
    public static class AttributeExtend
    {
        /// <summary>
        /// 驗證
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="t"></param>
        /// <returns></returns>
        public static bool Validate<T>(T t)
        {
            Type type = t.GetType();
            foreach (var prop in type.GetProperties())
            {
                if (prop.IsDefined(typeof(BaseAttribute), true))
                {
                    var oValue = prop.GetValue(t, null);

                    foreach (BaseAttribute item in prop.GetCustomAttributes(typeof(BaseAttribute), true))//獲取字段所有的特性
                    {
                        if (!item.Validate(oValue))
                        {
                            return false;
                        }
                    }
                }
            }
            return true;
        }
    }

    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = = false, Inherited = false)]
    public class LenghtAttribute : BaseAttribute
    {
        private int _minLenght = 0;
        private int _maxLenght = 0;
        public LenghtAttribute(int minLenght, int maxLenght)
        {
            _minLenght = minLenght;
            _maxLenght = maxLenght;
        }
        public override bool Validate(object oValue)
        {
            decimal.TryParse(oValue.ToString(), out var minParam);
            decimal.TryParse(oValue.ToString(), out var maxParam);
            if (oValue != null)
            {
                return minParam >= _minLenght && maxParam <= _maxLenght;
            }
            return true;
        }
    }
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = = false, Inherited = false)]
    public class EmailAttribute : BaseAttribute
    {
        public override bool Validate(object oValue)
        {
            if (oValue != null)
            {
                Regex r = new Regex(@"^[A-Za-z0-9\u4e00-\u9fa5]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$");
                return r.IsMatch(oValue.ToString());
            }
            return true;

        }
    }

     static void Main(string[] args)
        {
            var user = new User { Name = "張三", Email = "1300000@qq.com", Salary = 60000.1M };
            var result = AttributeExtend.Validate<User>(user);
            Console.WriteLine("Hello World!");
            Console.ReadKey();
        }

如有哪里講得不是很明白或是有錯誤,歡迎指正
如您喜歡的話不妨點個贊收藏一下吧


免責聲明!

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



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