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();
        }

如有哪里讲得不是很明白或是有错误,欢迎指正
如您喜欢的话不妨点个赞收藏一下吧

C# 自定义特性(Attribute)详解

上一篇:c# 调用CMD命令并获取输出结果


下一篇:windows自动化-脚本案例