• 20181114_特性


    一.   特性:

    比如下图中的Obsolete是特性, CustomAttribute是特性(自定义的特性), Serializable也是特性

    二.    特性的作用:

    a)         特性的核心作用:补充; 就像反射的核心作用一样: 动态

    b)         影响编译器的运行; [Obsolete("请不要使用这个了,请使用什么来代替", true)]如果是true则引用的时候, 编译器会报错

    c)         影响程序的运行; 比如[Serializable]表示某个类是否可以序列化和反序列化

          对于上图中标记的特性的说明:

          [Obsolete("请不要使用这个了,请使用什么来代替", true)]//影响编译器的运行 ; 这个特性一般用在api升级的时候; 比如1.0中有一个函数叫getProductinfo(),

          那么2.0中对此函数进行了升级getProductinfo(string shopId); 那么可以在API中对1.0中的getProductinfo()函数上面添加此特性; 在调用的时候就会有这样的提示

               [Serializable]//可以序列化和反序列化  可以影响程序的运行

    d)         特性就是一个类, 继承自attribute, 一般以Attribute结尾, 在标记的时候和声明时候都可以省略掉, 因为特性本身就是一个类, 那么它当然可以包含属性/字段/方法/委托/事件

    e)         自定义的特性一般以Attribute结尾,声明时(使用时) 可以省略掉Attribute

    f)          当使用特性进行修饰元素的时候, 默认只能修饰一次, 如果需要多次修饰的话, 要在定义此特性的类上增加 

          [AttributeUsage(AttributeTargets.All, AllowMultiple = true)]       

          //AllowMultiple = true默认值为false; 通常也不推荐修改为true, 一般使用默认值即可

     

    g)        AttributeUsage是一个专门用来修饰特性的一个特性类, 它用来描述, 某个特性类, 可以用在什么地方(类上-方法上-属性上)/是否可以叠加使用/是否可以被继承等. . .

    h)     [AttributeUsage(AttributeTargets.Class,  AllowMultiple = true)]

        AttributeTargets 是一个枚举; 指定class表示只能修饰类  ; 指定Method则表示修饰方法

        AttributeTargets.All|AttributeTargets.Method

        Inherited = true表示此特性是否可以被继承

     

    i)  特性可以添加到方法上/类上/属性上/返回值上

     [CustomAttribute] //属性添加特性
         public int Id { get; set; }
    
            [Custom()] //给方法添加特性
            [return: Custom()] //给返回值添加一个特性; 很少见
            public string Answer([Custom]string name) //给参数添加特性
            {
                return $"This is {name}";
            }

    j) 特性, 本身是没用的 , 任何一个生效的特性,都是因为其它地方的主动使用; 没有破坏类型封装的前提下,可以加点额外的信息和行为, 但是这是有前提的, 也就是说必须通过反射才行, 因为给一个元素上特性添加后,编译器会在元素内部标记元数据, 可以通过IL(中间语言)查看, 但是元数据我们是没办法直接使用的, 只有通过反射来调用 ; 如果不用调用, 特性相当于就没有用

    三.   创建自定义的特性类, 代码如下:

    /// <summary>
        /// 检查数字是否在指定范围内的特性, 这个特性在使用的时候, 需要传递两个参数, 且都是long类型的
        /// </summary>
        public class LongAttribute : Attribute //自定义特性类, 必须继承Attribute
        {
            private long _Min = 0;
            private long _Max = 0;
            public LongAttribute(long min, long max)
            {
                this._Min = min;
                this._Max = max;
            }
    
            public  bool Validate(object value)//" "
            {
                if (value != null && !string.IsNullOrWhiteSpace(value.ToString()))
                {
                    //检查value是不是long类型, 如果是, 则复制给lResult; 
                    if (long.TryParse(value.ToString(), out long lResult))
                    {
                        if (lResult > this._Min && lResult < this._Max)
                        {
                            return true;
                        }
                    }
                }
                return false;
            }
        }

    四.   特性基本说明:

    a)   简单介绍, Custom是在上面自定义的那个特性类:

      [Custom]

         [Custom()]//调用空的构造函数, 以上两种写法效果一样

         //这种写法就等于在实例化一个类

         [Custom(123), Custom(123, Description = "1234")]

        //这里要注意, 如果是构造函数的参数, 可以直接传递, 如果是字段, 则必须使用 字段名=值 的格式

     [Custom(123, Description = "1234", Remark = "2345")]//方法不行

    五.   使用反射将枚举值转变成字符, 要达到的效果如下图; 

     

    a)         定义特性类RemarkAttribute, 代码如下:

     /// <summary>
        /// 自定义特性类; 特性类必须继承   Attribute
        /// </summary>
        public class RemarkAttribute : Attribute //特性的类的后缀Attribute, 是可以省略的; 但是只有在使用的时候可以省略
        {
            private string _Remark = null;
            public RemarkAttribute(string remark)
            {
                this._Remark = remark;
            } 
            public string GetRemark()
            {
                return this._Remark;
            }
        }
    

    b) 对Enum类型进行反射取值, 直接使用对枚举扩展的方法来操作, 定义枚举扩展类RemarkExtension:

    /// <summary>
        /// 查找特性的类, 这个类仅仅对枚举类型进行了扩展
        /// </summary>
        public static class RemarkExtension
        {
            /// <summary>
            /// 由于添加了this, 那么这是一个扩展方法, 任何枚举都可以调用这个方法
            /// </summary>
            /// <param name="value"></param>
            /// <returns></returns>
            public static string GetRemark(this Enum value) //对枚举进行扩展
            {
                //根据传递的值得到一个枚举类型
                Type type = value.GetType();
                //找出对应的字段; 枚举中定义的都是字段, 而不是属性
                FieldInfo field = type.GetField(value.ToString());
                //判断该字段上是不是打有 Remark特性
                if (field.IsDefined(typeof(RemarkAttribute), true))
                {
                    RemarkAttribute attribute = (RemarkAttribute)field.GetCustomAttribute(typeof(RemarkAttribute), true);
                    return attribute.GetRemark();
                }
                else
                {
                    return value.ToString();
                }
            }
        }
    

    c) UserState的枚举类代码

    /// <summary>
        /// 用户状态; 绑定界面属性; 在webForm时代, 最常见的就是 用户名: textbox   密码: textbox
        /// 那么通过特性, 用户名  这样的字符串都可以是动态的
        /// </summary>
        public enum UserState
        {
            /// <summary>
            /// 正常
            /// </summary>
            [Remark("正常")]  //使用特性; 特性类在下面
            Normal = 0,//左边是字段名称(Normal),右边是数据库值(0),哪里放描述?也就是说要在界面上展示的 正常 字符
            /// <summary>
            /// 冻结
            /// </summary>
            [Remark("冻结")]
            Frozen = 1,
            /// <summary>
            /// 删除
            /// </summary>
            //[Remark("删除")]
            Deleted = 2
        }
    

     d)  调用方法:

    #region 通过特性为字段/属性/成员添加一些信息
    
        UserState userState = UserState.Normal;
        if (userState == UserState.Normal)
        {
            Console.WriteLine("正常状态"); //界面绑定一个中文
        }
        else if (userState == UserState.Frozen)
        { }
        else
        { }
    
       //GetRemark()方法是个扩展了Enum的方法; 任何定义个枚举都可以调用
        Console.WriteLine(userState.GetRemark());
        //常规的调用方法
        Console.WriteLine(RemarkExtension.GetRemark(userState));
        
        #endregion

     六. 通过扩展object类, 检查属性的合法性, 属性的值是否在指定范围之内 

     a) 自定义的抽象特性类:

     /// <summary>
        /// 抽象类, 每一个需要约束的方法都继承此类
        /// </summary>
        public abstract class AbstractValidateAttribute : Attribute
        {
            public abstract bool Validate(object value);
        }
    

     b)  字符长度的校验类, 继承抽象类 AbstractValidateAttribute:

    /// <summary>
        /// 只能让他修饰属性和字段; 检查字符串长度的特性
        /// </summary>
        [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
        public class LengAttribute : AbstractValidateAttribute
        {
            private int _Min = 0;
            private int _Max = 0;
    
            /// <summary>
            /// 特性类的构造函数
            /// </summary>
            /// <param name="min"></param>
            /// <param name="max"></param>
            public LengAttribute(int min, int max)
            {
                this._Min = min;
                this._Max = max;
            }
    
    
            public override bool Validate(object value)//" "
            {
                //return value != null && !string.IsNullOrWhiteSpace(value.ToString()) && value.ToString().Length >  _Min && value.ToString().Length <  _Max;
                if (value != null && !string.IsNullOrWhiteSpace(value.ToString()))
                {
                    int length = value.ToString().Length;
                    if (length > this._Min && length < this._Max)
                    {
                        return true;
                    }
                }
                return false;
            }
        }

     c) 检查数字是否在指定范围内, 继承抽象类AbstractValidateAttribute:

    /// <summary>
        /// 检查数字是否在指定范围内的特性, 这个特性在使用的时候, 需要传递两个参数, 且都是long类型的
        /// </summary>
       [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
        public class LongAttribute : AbstractValidateAttribute
        {
            private long _Min = 0;
            private long _Max = 0;
            public LongAttribute(long min, long max)
            {
                this._Min = min;
                this._Max = max;
            }
    
    
            public override bool Validate(object value)//" "
            { 
                if (value != null && !string.IsNullOrWhiteSpace(value.ToString()))
                {
                    //检查value是不是long类型, 如果是, 则复制给lResult; 
                    if (long.TryParse(value.ToString(), out long lResult))
                    {
                        if (lResult > this._Min && lResult < this._Max)
                        {
                            return true;
                        }
                    }
                }
                return false;
            }
        }
    

    d) 扩展object类, 注意由于是扩展的object类, 那么所有的类型都将具有这个方法:

    /// <summary>
        /// 验证扩展类
        /// </summary>
        public static class ValidateExtension
        {
            /// <summary>
            /// 验证扩展
            /// </summary>
            /// <param name="oObject"></param>
            /// <returns></returns>
            public static bool Validate(this object oObject)
            {
                Type type = oObject.GetType();
                foreach (var prop in type.GetProperties())
                {
                  //检查prop上是不是有这个 抽象 的特性, 这个抽象当前派生了两个类, 如果有需要则可以继续进行派生, 但是此类就无需再修改, 当然如果想要报错更清晰一点, 可以修改此类
                    if (prop.IsDefined(typeof(AbstractValidateAttribute), true))
                    {
                        object[] attributeArray = prop.GetCustomAttributes(typeof(AbstractValidateAttribute), true);
                        foreach (AbstractValidateAttribute attribute in attributeArray)
                        {
                            if (!attribute.Validate(prop.GetValue(oObject)))
                            {
                                return false;
                            }
                        }
                    }
                     
                }
                return true;
            }
        }
    

    e) 需要校验属性的类, 这里使用student类进行模拟校验:  

    public class Student
        { 
            /// <summary>
            /// 相当于调用了Leng这个特性类的构造函数
            /// </summary>
            [Leng(5, 10)] 
            public string Name { get; set; }
    
            [Extension.Leng(20, 50)]
            public string Accont { get; set; }
    
            /// <summary>
            /// 10001~999999999999
            /// </summary>
            [Long(10001, 999999999999)]
            public long QQ { get; set; } 
        }

    f)    调用方式; 还是那一句→所有的特性都要主动去调用:

          由于特性是基于object扩展的, 所以任何类都可以主动去使用

     Student student = new Student();
                
    student.Name = "孙悟空";
    student.QQ = 123456;
    student.Accont = "23434234243323";
    student.Validate();
    

      

  • 相关阅读:
    ES6新特性
    浏览器兼容问题
    跨域
    箭头函数与普通函数的区别
    单页面应用
    vue试题
    Git 常用命令
    【分享代码】一个笨办法获取容器的剩余内存
    【笔记】thanos receiver的router模式
    【分享】让prometheus支持PUSH模式,可以使用remote write协议推送数据
  • 原文地址:https://www.cnblogs.com/wxylog/p/9986935.html
Copyright © 2020-2023  润新知