代码见文件:AttributeStudy.zip  

特性无处不在,各种框架:MVC、IOC、ORM、WebService无处不在,常见的[Serializable]、[Controllers]等等

一、基本概念

       可以理解成”标签“,给被标记的目标(class、property、field、method甚至方法的参数parameter)追加额外的信息。比如【不老男神】刘德华、【流量】xxx,对应到程序里的例子[Required]表示标记的字段非空、[MaxLength(32)]字段最大长度32位等等。

       与注释不同,注释在编译的时候会被编译器(CLR)层丢弃,而特性不但不会被丢弃还会被编译进程序集程序集(Assembly)的元数据(Metadata)里,在程序运行时,从元数据提取出来决策程序进行。

1、它的本质是一个class(类),默认以Attribute结尾,直接或者是间接继承Attribute抽象类

2、应用特性,是把这个特性以[]标记在类或者是类内部成员上

3、标记的特性,如果是以Attribute结果,Attribute可以省略掉

4、可以标记的类内部的任何成员上

5、特性在标记的时候,其实就是去调用构造函数

6、在标记的时候也可以对公开的属性或者字段赋值

7、特性标记默认是不能重复标记的

二、具体应用

自定义一个特性CustomAttribute,继承Attribute。对应的约束:

AttributeTargets.All--可标记在类及类的成员上

AllowMultiple = true 允许重复标记

Inherited = true 允许被继承

   /// <summary>
    /// AttributeUsage--也是一个特性,是用来修饰特性的特性--约束特性的特性
    /// AttributeTargets--当前特性只能标记在某个地方;,我这个特性是专们用来标记在哪里的推荐使用(AttributeTargets.Class、AttributeTargets.Property......)
    /// AllowMultiple--是否允许重复标记
    /// Inherited--是否允许被继承
    /// </summary>
    [AttributeUsage(AttributeTargets.All,AllowMultiple = true,Inherited = true)]
    public class CustomAttribute:Attribute
    {
        private string _TypeDescription;
        public string _Name;
        /// <summary>
        /// 构造函数一
        /// </summary>
        /// <param name="typeDescription"></param>
        public CustomAttribute(string typeDescription)
        {
            _TypeDescription = typeDescription;
            Console.WriteLine($"这是{_TypeDescription}");
        }
        //构造函数二
        public CustomAttribute(string 
            name,string typeDescription)
        {
            _TypeDescription = typeDescription;
            _Name = name;
            Console.WriteLine($"这是{_TypeDescription}");
        }

    }

实际调用

namespace AttributeStudy.Model
{
    [Custom("学生")]
    [Custom("学生")]
    public class Student
    {
        /// <summary>
        /// 标记特性时实际是调用属性的构造方法
        /// </summary>
        [Custom("学生姓名","中文名")]
        public string Name { get; set; }
        [Custom("学生年龄")]
        public int Age { get; set; }
      
        // _Name为publi类型,可在标记时赋值
        
        [Custom("学生电话号码",_Name = "电话号码")]
        public string PhoneNumber { get; set; }

        [Custom("学生学习")]
        public void Study()
        {
            Console.WriteLine("学习");
        }

        public void eat([Custom("食物")]string food)
        {
            Console.WriteLine($"吃{food}");
        }
    }
}

四、如何具体调用特性内部的成员

.class public auto ansi beforefieldinit AttributeStudy.Model.Student
    extends [System.Runtime]System.Object
{
    .custom instance void AttributeStudy.CustomAttribute::.ctor(string) = (
        01 00 06 e5 ad a6 e7 94 9f 00 00
    )
    // Fields
    .field private string '<Name>k__BackingField'
    .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
        01 00 00 00
    )
    .custom instance void [System.Runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggerBrowsableState) = (
        01 00 00 00 00 00 00 00
    )
    .field private int32 '<Age>k__BackingField'
    .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
        01 00 00 00
    )
    .custom instance void [System.Runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggerBrowsableState) = (
        01 00 00 00 00 00 00 00
    )
    .field private string '<PhoneNumber>k__BackingField'
    .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
        01 00 00 00
    )
    .custom instance void [System.Runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.DebuggerBrowsableState) = (
        01 00 00 00 00 00 00 00
    )

    // Methods
    .method public hidebysig specialname 
        instance string get_Name () cil managed 
    {
        .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
            01 00 00 00
        )
        // Method begins at RVA 0x208e
        // Code size 7 (0x7)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: ldfld string AttributeStudy.Model.Student::'<Name>k__BackingField'
        IL_0006: ret
    } // end of method Student::get_Name

    .method public hidebysig specialname 
        instance void set_Name (
            string 'value'
        ) cil managed 
    {
        .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
            01 00 00 00
        )
        // Method begins at RVA 0x2096
        // Code size 8 (0x8)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: ldarg.1
        IL_0002: stfld string AttributeStudy.Model.Student::'<Name>k__BackingField'
        IL_0007: ret
    } // end of method Student::set_Name

    .method public hidebysig specialname 
        instance int32 get_Age () cil managed 
    {
        .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
            01 00 00 00
        )
        // Method begins at RVA 0x209f
        // Code size 7 (0x7)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: ldfld int32 AttributeStudy.Model.Student::'<Age>k__BackingField'
        IL_0006: ret
    } // end of method Student::get_Age

    .method public hidebysig specialname 
        instance void set_Age (
            int32 'value'
        ) cil managed 
    {
        .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
            01 00 00 00
        )
        // Method begins at RVA 0x20a7
        // Code size 8 (0x8)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: ldarg.1
        IL_0002: stfld int32 AttributeStudy.Model.Student::'<Age>k__BackingField'
        IL_0007: ret
    } // end of method Student::set_Age

    .method public hidebysig specialname 
        instance string get_PhoneNumber () cil managed 
    {
        .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
            01 00 00 00
        )
        // Method begins at RVA 0x20b0
        // Code size 7 (0x7)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: ldfld string AttributeStudy.Model.Student::'<PhoneNumber>k__BackingField'
        IL_0006: ret
    } // end of method Student::get_PhoneNumber

    .method public hidebysig specialname 
        instance void set_PhoneNumber (
            string 'value'
        ) cil managed 
    {
        .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
            01 00 00 00
        )
        // Method begins at RVA 0x20b8
        // Code size 8 (0x8)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: ldarg.1
        IL_0002: stfld string AttributeStudy.Model.Student::'<PhoneNumber>k__BackingField'
        IL_0007: ret
    } // end of method Student::set_PhoneNumber

    .method public hidebysig specialname rtspecialname 
        instance void .ctor () cil managed 
    {
        // Method begins at RVA 0x20c1
        // Code size 8 (0x8)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: call instance void [System.Runtime]System.Object::.ctor()
        IL_0006: nop
        IL_0007: ret
    } // end of method Student::.ctor

    // Properties
    .property instance string Name()
    {
        .custom instance void AttributeStudy.CustomAttribute::.ctor(string) = (
            01 00 0c e5 ad a6 e7 94 9f e5 a7 93 e5 90 8d 00 00
        )
        .get instance string AttributeStudy.Model.Student::get_Name()
        .set instance void AttributeStudy.Model.Student::set_Name(string)
    }
    .property instance int32 Age()
    {
        .custom instance void AttributeStudy.CustomAttribute::.ctor(string) = (
            01 00 0c e5 ad a6 e7 94 9f e5 b9 b4 e9 be 84 00 00
        )
        .get instance int32 AttributeStudy.Model.Student::get_Age()
        .set instance void AttributeStudy.Model.Student::set_Age(int32)
    }
    .property instance string PhoneNumber()
    {
        .custom instance void AttributeStudy.CustomAttribute::.ctor(string) = (
            01 00 12 e5 ad a6 e7 94 9f e7 94 b5 e8 af 9d e5
            8f b7 e7 a0 81 00 00
        )
        .get instance string AttributeStudy.Model.Student::get_PhoneNumber()
        .set instance void AttributeStudy.Model.Student::set_PhoneNumber(string)
    }

}

 

五、利用反射去获取、调用特性

在对Student进行反编译后,在内部可看到生成了Custom成员,但是不能直接调用。如果要调用需要用到反射。

思路:通过反射获取类及类成员的类型Type,再查看各个Type上有没有定义特性

定义一个工具类InvokeAttributeManager,访问类及类内部成员(属性、字段、方法)上的特性

GetCustomAttributes-返回使用定义在该成员变量上的特性数组,在这个步骤MyCustomAttribute的构造函数被调用
public class InvokeAttributeManager
    {
        /// <summary>
        /// 查看T实体类否有打上M特性
        /// </summary>
        /// <typeparam name="T">对应的实体类</typeparam>
        /// <typeparam name="M">对应的标记</typeparam>
        /// <param name="t"></param>
        
        public static void ShowClassContainsAttribute<T, M>(T t) where M : Attribute
        {
            //获取实体类的类型
            Type type = typeof(T);
            //1、判断T是否含有特性M
            //IsDefined--是否在该成员变量上定义了一个或多个attributeType实例
            if (type.IsDefined(typeof(M), true))
            {
                Console.WriteLine("类上定义了此特性");
                //先判断是否含有此特性,再获取
                //GetCustomAttributes-返回使用定义在该成员变量上的特性数组
                //在这个步骤MyCustomAttribute的构造函数被调用
                var attributes = type.GetCustomAttributes(true);
                foreach (M attribute in attributes)
                {
                    Console.WriteLine($"包含特性名称:{typeof(M).Name}");
                }
            }
        }
        /// <summary>
        /// 查看T实体类下的属性是否有打上M特性
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="M"></typeparam>
        /// <param name="t"></param>
        public static void ShowPropertyContainsAttribute<T, M>(T t) where M : Attribute
        {
            //获取实体类的类型
            Type type = typeof(T);
            foreach (var propertyInfo in type.GetProperties())
            {
                //1、判断propertyInfo是否含有特性M
                //IsDefined--是否在该成员变量上定义了一个或多个attributeType实例
                if (propertyInfo.IsDefined(typeof(M), true))
                {
                    Console.WriteLine("属性上定义了此特性");
                    //先判断是否含有此特性,再获取
                    //GetCustomAttributes-返回使用定义在该成员变量上的特性数组
                    //在这个步骤MyCustomAttribute的构造函数被调用
                    var attributes = propertyInfo.GetCustomAttributes(true);
                    foreach (M attribute in attributes)
                    {
                        Console.WriteLine($"包含特性名称:{typeof(M).Name}");
                    }
                }
            }
            
        }
        /// <summary>
        /// 查看T实体类下的字段是否有打上M属性
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="M"></typeparam>
        /// <param name="t"></param>
        public static void ShowFieldContainsAttribute<T, M>(T t) where M : Attribute
        {
            //获取实体类的类型
            Type type = typeof(T);
            foreach (var fieldInfo in type.GetFields())
            {
                //1、判断propertyInfo是否含有特性M
                //IsDefined--是否在该成员变量上定义了一个或多个attributeType实例
                if (fieldInfo.IsDefined(typeof(M), true))
                {
                    Console.WriteLine("字段上定义了此特性");
                    //先判断是否含有此特性,再获取
                    //GetCustomAttributes-返回使用定义在该成员变量上的特性数组
                    //在这个步骤MyCustomAttribute的构造函数被调用
                    var attributes = fieldInfo.GetCustomAttributes(true);
                    foreach (M attribute in attributes)
                    {
                        Console.WriteLine($"包含特性名称:{typeof(M).Name}");
                    }
                }
            }

        }
        /// <summary>
        /// 查看获取方法成员上的特性
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="M"></typeparam>
        /// <param name="t"></param>
        public static void ShowMethodsContainsAttribute<T, M>(T t) where M : Attribute
        {
            Type type = typeof(T);
            foreach (var methodInfo in type.GetMethods())
            {
                //判断是否包含
                if (methodInfo.IsDefined(typeof(M), true))
                {
                    var attributes = methodInfo.GetCustomAttributes(true);
                    foreach (var attribute in attributes)
                    {
                        Console.WriteLine(attribute.GetType().Name);
                    }
                }
                
            }
        }
    }

执行结果:

 {
                
                
                 InvokeAttributeManager.ShowClassContainsAttribute<Student,MyCustomAttribute>(student);
                //结果打印:
                //类上定义了此特性
                //这是学生1(在var attributes = type.GetCustomAttributes(true);时打印)
                //这是学生2(在var attributes = type.GetCustomAttributes(true);时打印)
                //包含特性名称:MyCustomAttribute
                //包含特性名称:MyCustomAttribute

                InvokeAttributeManager.ShowPropertyContainsAttribute<Student, MyCustomAttribute>(student);
                //打印:
                //属性上定义了此特性
                //    这是中文名
                //包含特性名称:MyCustomAttribute
                //    属性上定义了此特性
                //这是学生年龄
                //    包含特性名称:MyCustomAttribute
                //    属性上定义了此特性
                //这是学生电话号码
                //    包含特性名称:MyCustomAttribute

                InvokeAttributeManager.ShowFieldContainsAttribute<Student, MyCustomAttribute>(student);
                //字段上定义了此特性
                //    这是所在学校
                //包含特性名称:MyCustomAttribute

                InvokeAttributeManager.ShowMethodsContainsAttribute<Student, MyCustomAttribute>(student);
                //这是学生学习
                //MyCustomAttribute
            }

 

六、特性的应用

1、利用特性去获取枚举的含义

避免每次获取含义时,都要调用如下一个层层if的方法去获取

public string getMean(enum enumA){
  if(enumA == enum.A ){
     return "正常";
  }
if(enumA == enum.B ){
     return "冻结";
  }
。。。。
}

定义一个说明特性RemarkAttribute,去说明enum中的字段含义

 public class RemarkAttribute:Attribute
    {
        private string _remark;

        public RemarkAttribute(string remark)
        {
            _remark = remark;
        }
        /// <summary>
        /// 为外界暴露一个获取说明的方法
        /// </summary>
        /// <returns></returns>
        public string GetRemark()
        {
            return _remark;
        }
    }

定义一个UserStateEnum,打上Remark特性

public enum UserStateEnum
    {
        [Remark("正常状态")]
        Normal = 1,
        [Remark("删除状态")]
        Deleted = -1,
        [Remark("冻结状态")]
        Frozen = 2,
       //没有状态
        Empty = 0
    }

定义一个InvokeRemarkManager工具类,定义一个enum的拓展方法去获取含义

public static class InvokeRemarkManager
    {
        /// <summary>
        /// 获取枚举字段对应的标记含义
        /// </summary>
        /// <param name="eEnum"></param>
        /// <returns></returns>
        public static string getRemark(this Enum eEnum)
        {
            Type type = eEnum.GetType();
            //获取枚举字段的名称
            string fieldName = eEnum.ToString();
            //获取对应的枚举字段
            FieldInfo fieldInfo = type.GetField(fieldName);
            //获取枚举字段对应的解释说明
            //判断该字段上是否定义了RemarkAttribute特性
            if (fieldInfo.IsDefined(typeof(RemarkAttribute),true))
            {
                //获取RemarkAttribute特性,两种写法
                //RemarkAttribute attr = (RemarkAttribute)fieldInfo.GetCustomAttribute(typeof(RemarkAttribute));
                RemarkAttribute attr = fieldInfo.GetCustomAttribute<RemarkAttribute>();

                //获取RemarkAttribute的remark
                return attr.GetRemark();
            }
            return "该枚举字段未创建说明";
        }
    }

测试及结果打印:

 //1、获取枚举变量的实际说明
            {
                var remark1 = InvokeRemarkManager.getRemark(UserStateEnum.Deleted);
                var remark2 = InvokeRemarkManager.getRemark(UserStateEnum.Frozen);
                var remark3 = InvokeRemarkManager.getRemark(UserStateEnum.Empty);
                Console.WriteLine($"UserStateEnum.Deleted———{remark1}");
                Console.WriteLine($"UserStateEnum.Frozen———{remark2}");
                Console.WriteLine($"UserStateEnum.Empty———{remark3}");
                //结果打印:
                //UserStateEnum.Deleted———删除状态
                //UserStateEnum.Frozen———冻结状态
                //UserStateEnum.Empty———该枚举字段未创建说明
            }

 

 2、利用特性去实现数据字段的校验(比如web api提交数据的场景)

模拟一个webapi的返回类型

public class ApiResult
    {
        /// <summary>
        /// 说明信息
        /// </summary>
        public string Message { get; set; }
        /// <summary>
        /// 返回状态码
        /// </summary>
        public int StatusCode { get; set; }

        /// <summary>
        /// 是否成功
        /// </summary>
        public bool IsSuccess { get; set; }

    }

因为数据的校验很多种类:非空校验、字符串长度校验、电话号码校验、身份证号校验等等,这里我们给这些校验的特性定义一个父类AbstractValidateAttribute:Attribute

,暴露一个校验的虚方法,让这些子类特性去继承重写

public abstract class AbstractValidateAttribute:Attribute
    {

        public abstract ApiResult Validate(Object obj);
    }

非空校验RequiredAttribute

public class RequiredAttribute:AbstractValidateAttribute
    {
        public string _ErrorMessage;
        public override ApiResult Validate(object obj)
        {

            if (obj != null && !string.IsNullOrWhiteSpace(obj.ToString()) && obj.ToString() != "0")
            {
                return new ApiResult()
                {
                    IsSuccess = true
                };
            }
            else
            {
                return new ApiResult()
                {
                    Message = _ErrorMessage,
                    IsSuccess = false
                };
            }
        }
    }

长度校验LengthValiDateAttribute

public class LengthValiDateAttribute:AbstractValidateAttribute
    {
        //一般设置一个public字段来指定显示对应的信息
        public string ErrorMessage;

        private readonly int _min;
        private readonly int _max;

        public LengthValiDateAttribute(int min,int max)
        {
            _min = min;
            _max = max;
        }
        public override ApiResult Validate(object obj)
        {
            if (obj.ToString().Length < _min || obj.ToString().Length > _max)
            {
                return new ApiResult()
                {
                    IsSuccess = false,
//如果失败则将失败信息赋值给Message Message
= ErrorMessage }; } return new ApiResult() { IsSuccess = true }; } }

定义一个工具类,和静态方法去做校验

 public class InvokeValiDateManager
    {
        public static ApiResult ValiDate<T>(T t) where T : class
        {
            Type type = typeof(T);
            //遍历其属性
            foreach (var propertyInfo in type.GetProperties())
            {
                if (propertyInfo.IsDefined(typeof(AbstractValidateAttribute),true))
                {
                    //获取属性的值
                    var value = propertyInfo.GetValue(t);

                    var attributes = propertyInfo.GetCustomAttributes(true);
                    foreach (var attribute in attributes)
                    {
                        AbstractValidateAttribute attributeAttribute = (AbstractValidateAttribute)attribute;
                         
                        ApiResult result = attributeAttribute.Validate(value);
                        if (result.IsSuccess == false)
                        {
                            return result;
                        }
                    }

                }

            }
            return new ApiResult()
            {
                IsSuccess = true
            };
        }

    }

定义一个实体类UserInfo,打上特性

   public class UserInfo
    {
        
        public string Name { get; set; }
        public string Email { get; set; }

        [Required(_ErrorMessage = "年龄不能为空")]
        public int Age { get; set; }

        [Required(_ErrorMessage = "姓名不能为空")]
        [LengthValiDate(11, 11, ErrorMessage = "手机号必须为11位数")]
        public string  PhoneNumber { get; set; }

        public UserStateEnum State { get; set; }
    }

测试:

//2、字段的可行性校验
            UserInfo userInfo = new UserInfo()
            {
                Name = "",
                 // Age = 16,
                PhoneNumber = "1323640748822"
            };
            ApiResult result = InvokeValiDateManager.ValiDate<UserInfo>(userInfo);
            Console.WriteLine($"验证结果:{result.IsSuccess},信息:{result.Message}");
            //结果打印:
            //验证结果:False,信息:手机号必须为11位数