特性的学习

特性

在C#的编程过程之中,我们可以发现特性是无处不在的,那么什么是特性那?

什么是特性?

  1. 特性其实就是一个类(class),声明的时候,默认以Attribute结尾,直接或者间接的继承Attribute抽象类。
  2. 当我们应用特性的时候,是把这个特性以[]包裹标记在类或者类内部成员上。

特性和注释的区别?

  1. 注释只是一个描述;在编译后,是不存在的。
  2. 特性

如何自定义特性,特性的多种标记

  1. 标记的是,如果是以Attribute结尾,Attribute可以省略掉,直接或间接继承Attribute。
  2. 可以标记类的内部的任何成员上。
  3. 特性在标记的时候,其实就是调用构造函数。
  4. 在标记的时候也可以对公开的属性或字段赋值
  5. 默认是不能重复标记的
  6. AttributeUsage:也是一个特性,是用来修饰特性的特性
  7. AttributeTargets指定当前特性只能标记到某个地方:建议大家在自己定义特性的时候,最好能够明确指定AttributeTargets--明确告诉别人,我这个特性是专门用来标记在哪里的。
  8. AllowMultiple:是否可以重复标记。
  9. Inherited:是否可以继承特性
    定义一个特性:
[AttributeUsage(AttributeTargets.All,AllowMultiple =true,Inherited =true)]
public class CustomAttribute : Attribute
{ 
    private int _Id { get; set; } 
    public string _Name { get; set; } 

    public int _Age;

    //public CustomAttribute()
    //{
    //}

    public CustomAttribute(int id)
    {
        this._Id = id;
    }

    public CustomAttribute(string name)
    {
        this._Name = name;
    }

    public void Do()
    {
        Console.WriteLine("this is  CustomAttribute");
    }
}

如何调用到特性内部的成员--自定义的这个特性,如何才能使用它那?

  1. 进入反编译以后,在标记有特性的类的内部,生成了custom的成员,但是我们不能直接调用;--要通过反射调用。要让特性生效,实际上来说就是要去执行我们这个特性?--就需要构造特性的实例。
  2. 在使用反射获取特性的时候,可以把标记在任何地方的特性都可以获取到
  3. 既然标记的特性可以通过反射来获取到实例,就可以加以应用
    code:
public class InvokeAttributeManager
{
    /// <summary>
    /// 通过反射来调用特性---确实是可以调用到特性的实例的
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="t"></param>
    public static void Show(Student student)
    {
        //Type type = typeof(Student);
        Type type = student.GetType(); 
        //1.先判断是否有特性
        if (type.IsDefined(typeof(CustomAttribute), true))
        { 
            //2.获取--先判断再获取--为了提高性能
            foreach (CustomAttribute attribute in type.GetCustomAttributes(true))
            {
                Console.WriteLine($"attribute._Name:{attribute._Name}");
                Console.WriteLine($"attribute._Age:{attribute._Age}"); 
                attribute.Do();
            }
        }

        //获取当前Type下所有的属性上标记的特性
        foreach (PropertyInfo prop in type.GetProperties())
        {
            if (prop.IsDefined(typeof(CustomAttribute), true))
            {
                //2.获取--先判断再获取--为了提高性能
                foreach (CustomAttribute attribute in prop.GetCustomAttributes(true))
                {
                    Console.WriteLine($"attribute._Name:{attribute._Name}");
                    Console.WriteLine($"attribute._Age:{attribute._Age}");
                    attribute.Do();
                }
            }
        }

        //获取当前Type下所有的字段上标记的特性
        foreach (FieldInfo field in type.GetFields())
        {
            if (field.IsDefined(typeof(CustomAttribute), true))
            {
                //2.获取--先判断再获取--为了提高性能
                foreach (CustomAttribute attribute in field.GetCustomAttributes(true))
                {
                    Console.WriteLine($"attribute._Name:{attribute._Name}");
                    Console.WriteLine($"attribute._Age:{attribute._Age}");
                    attribute.Do();
                }
            }
        }

        //获取当前Type下所有的方法上标记的特性
        foreach (MethodInfo method in type.GetMethods())
        {
            foreach (ParameterInfo para in method.GetParameters())
            {
                if (para.IsDefined(typeof(CustomAttribute), true))
                {
                    //2.获取--先判断再获取--为了提高性能
                    foreach (CustomAttribute attribute in para.GetCustomAttributes(true))
                    {
                        Console.WriteLine($"attribute._Name:{attribute._Name}");
                        Console.WriteLine($"attribute._Age:{attribute._Age}");
                        attribute.Do();
                    }
                }
            }


            if (method.IsDefined(typeof(CustomAttribute), true))
            {
                //2.获取--先判断再获取--为了提高性能
                foreach (CustomAttribute attribute in method.GetCustomAttributes(true))
                {
                    Console.WriteLine($"attribute._Name:{attribute._Name}");
                    Console.WriteLine($"attribute._Age:{attribute._Age}");
                    attribute.Do();
                }
            }
        }



    }
}

特性获取额外信息

一、传统方式获取枚举
我们会实现这样的一个需求,比方有一个用户信息,用户信息中有一个状态字段;在数据库中保存数据的时候,保存的是1,2,3 对应的就是这个枚举;数据库中保存的是数字,但是我们在查询到数据以后,展示给界面的时候,就可以通过特性来实现。

    public enum UserStateEnum
    {
        Normal = 1,
        Frozen = 2,
        Deleted = 3
    }

面对上述的情况,我们往往需要通过分支判断来判断状态,在面对描述信息改了?我们就需要每一个使用此枚举的地方,导致工作量的增加。
二、通过特性来获取特性信息---额外信息---特性获取额外信息
定义一个特性类

    [AttributeUsage(AttributeTargets.Field)]
    public class RemarkAttribute:Attribute
    {
        private string _Desc;
        public RemarkAttribute(string desc)
        {
            _Desc = desc;
        }
        public string GetRemark()
        {
            return this._Desc;
           
        }
    }
}

调用特性方法

public  class RemarkAttributeExtension
{
    public static string GetRemark(UserStateEnum userState)
    {
        Type type = userState.GetType();
        string fieldName = userState.ToString();
        FieldInfo? fieldInfo = type.GetField(fieldName);
        if (fieldInfo.IsDefined(typeof(RemarkAttribute), true))
        {

            RemarkAttribute attribute =fieldInfo.GetCustomAttribute<RemarkAttribute>();
            return attribute.GetRemark();
        }

        return userState.ToString();
    }
     
}

好处:

  1. 如果增加了新的字段,就可以直接获取不用其他改动
  2. 描述修改后,获取描述信息的方法不用修改
    这样我们还可以更改我们的RemarkAttributeExtension类变成扩展方法
 public static  class RemarkAttributeExtension
 {
     public static string GetRemark(this Enum @enum)
     {
         Type type = @enum.GetType();
         string fieldName = @enum.ToString();
         FieldInfo? fieldInfo = type.GetField(fieldName);
         if (fieldInfo.IsDefined(typeof(RemarkAttribute), true))
         {

             RemarkAttribute attribute =fieldInfo.GetCustomAttribute<RemarkAttribute>();
             return attribute.GetRemark();
         }

         return @enum.ToString();
     }
      
 }

更改我们的代码为;

    UserStateEnum Normal = UserStateEnum.Normal;
    UserStateEnum Frozen = UserStateEnum.Frozen;
    UserStateEnum Deleted = UserStateEnum.Deleted;

    Console.WriteLine(RemarkAttributeExtension.GetRemark(Normal));
    Console.WriteLine(RemarkAttributeExtension.GetRemark(Frozen));
    Console.WriteLine(RemarkAttributeExtension.GetRemark(Deleted));

    Console.WriteLine(Normal.GetRemark());
    Console.WriteLine(Frozen.GetRemark());
    Console.WriteLine(Deleted.GetRemark());

这样我们通过反射+特性+扩展方法,可以封装一个获取额外新的公共方法。

特性获取额外功能

添加一个功能

  1. 如果要保存一条数据到数据库中去
  2. 从前端提交过来的数据格式为:
    {
    "id":0,
    "name":"string",
    "age":0
    "state":1
    }
    3.包含了很多字;
    4.如果数据库总Name的值要求存储的长度为40个字符---如果保存的数据超过40个字符---肯定会报错
  3. 肯定要在保存之前就是需要验证这行数据。
    我们先验证我们的用户名不能为空的一个验证:

 [AttributeUsage(AttributeTargets.Property)]
 public class Required:Attribute
 {
     public string _ErrorMessage;
     public Required(string message) {
         this._ErrorMessage = message;   
     }
     public bool Validate(object value)
     {

         bool bResult = value != null && !string.IsNullOrWhiteSpace(value.ToString());
         return bResult;
     }
 }

之后,就是我们调用特性的代码

public class ValidateInvokeManager
{
    public static bool Validate<T>(T t) where T : class
    {
        Type type = typeof(T);
        foreach (var prop in type.GetProperties())
        {
            if (prop.IsDefined(typeof(Required),true))
            {
                Required required = prop.GetCustomAttribute<Required>();
                if (required.Validate(prop.GetValue(t))==false)
                {
                    return false;
                }
            }
        }
        return true;
    }
}

在这里,只是添加一个用户名非空的一个验证,下面是我们的用户类

     public int Id { get; set; }
     [Required("不能为空")]
     public string Name { get; set; }
     public int Age { get; set; }
     public long QQ { get; set; }

     public string Mobile { get; set; }
     public UserStateEnum UserState { get; set; }

这里我们再添加一个长度的限制

public class LengthAttribute : Attribute
{
    public string _ErrorMessage;
    private int _Min;
    private int _Max;


    public LengthAttribute(int min, int max)
    {
        this._Min = min;
        this._Max = max;
    }
    public bool Validate(object value)
    {
        if (value != null)
        {
            if (value.ToString().Length < _Min || value.ToString().Length > _Max)
            {
                return false;
            }
            else
            {
                if (value.ToString().Length == 0)
                {
                    return false;
                }
                else
                {
                    return true;
                }

            }
        }
        return false;

    }
}

对应的用户类:

public class UserInfo
{
    public int Id { get; set; }

    [Required("Name的值不能为空")]
    public string Name { get; set; }

    public int Age { get; set; }

    public long QQ { get; set; }

    
    [Required("Mobile的值不能为空")]
    [Length(11, 11, _ErrorMessage = "手机号必须为11位数")]
    public string Mobile { get; set; }

    public UserStateEnum State { get; set; }

    public string UserStateDescription
    {
        get
        {
            return this.State.GetRemark();
        }
    }
}

这样其实并不是很通用,我们再去更改一个通用的版本。
定义一个抽象的类


    public abstract class AbstractAttribute : Attribute
    {
        public abstract bool Validate(object oValue);
    }
}

改造一下我们对应的验证的类

  [AttributeUsage(AttributeTargets.Property)]
  public class Required: AbstractAttribute
  {
      public string _ErrorMessage;
      public Required(string message) {
          this._ErrorMessage = message;   
      }
      public override bool Validate(object value)
      {

          bool bResult = value != null && !string.IsNullOrWhiteSpace(value.ToString());
          return bResult;
      }
  }
 public class LengthAttribute : AbstractAttribute
 {
     public string _ErrorMessage;
     private int _Min;
     private int _Max;


     public LengthAttribute(int min, int max)
     {
         this._Min = min;
         this._Max = max;
     }
     public override bool Validate(object value)
     {
         if (value != null)
         {
             if (value.ToString().Length < _Min || value.ToString().Length > _Max)
             {
                 return false;
             }
             else
             {
                 if (value.ToString().Length == 0)
                 {
                     return false;
                 }
                 else
                 {
                     return true;
                 }

             }
         }
         return false;

     }
 }

对应的验证类

  public static bool Validate<T>(T t) where T : class
  {
      Type type = typeof(T);
      foreach (var prop in type.GetProperties())
      {
          if (prop.IsDefined(typeof(AbstractAttribute), true))
          {
              object oValue = prop.GetValue(t);
              foreach (AbstractAttribute attribute in prop.GetCustomAttributes())
              {
                  bool apiResult = attribute.Validate(oValue);
                  if (apiResult == false)
                  {
                      return apiResult;
                  }
              }
          }
      }

      return true;
  }

这样我们又遇到了一个问题,什么问题?我们仅仅是知道了我们验证失败了,但是因为什么才导致我们的验证失败的信息还不清楚。
我们开始引入ApiResult这个类,来解决这个问题.

    public class ApiResult
{
    public bool ret { get; set; }
    public string _ErrorMessage { get; set; }
}

对应的验证类:

   public class LengthAttribute : AbstractAttribute
   {
       public string _ErrorMessage;
       private int _Min;
       private int _Max;


       public LengthAttribute(int min, int max)
       {
           this._Min = min;
           this._Max = max;
       }
       public override ApiResult Validate(object value)
       {
           if (value != null)
           {
               if (value.ToString().Length < _Min || value.ToString().Length > _Max)
               {

                   return new ApiResult() { ret = false, _ErrorMessage = this._ErrorMessage };
               }
               else
               {
                   if (value.ToString().Length == 0)
                   {

                       return new ApiResult() { ret = false, _ErrorMessage = this._ErrorMessage };
                   }
                   else
                   {

                       return new ApiResult() { ret = true, _ErrorMessage =null};
                   }

               }
           }
           return new ApiResult() { ret = true, _ErrorMessage = null }; 

       }
   }
 public class RequiredAttribute : AbstractAttribute
 {
     public string _ErrorMessage;
     public RequiredAttribute(string message) {
         this._ErrorMessage = message;   
     }
     public override ApiResult Validate(object value)
     {

         bool bResult = value != null && !string.IsNullOrWhiteSpace(value.ToString());
         if (bResult)
         {
             return new ApiResult()
             {
                 ret = bResult
             };
         }
         else
         {
             return new ApiResult()
             {
                 ret = bResult,
                 _ErrorMessage = _ErrorMessage
             };
         }

     }
 }

调用特性的类


 public static ApiResult Validate<T>(T t) where T : class
 {
     Type type = typeof(T);
     foreach (var prop in type.GetProperties())
     {
         if (prop.IsDefined(typeof(AbstractAttribute), true))
         {
             object oValue = prop.GetValue(t);
             foreach (AbstractAttribute attribute in prop.GetCustomAttributes())
             {
                 ApiResult apiResult = attribute.Validate(oValue);
                 if (apiResult.ret == false)
                 {
                     return apiResult;
                 }
             }
         }
     }

     return new ApiResult { ret = true, _ErrorMessage = null };
 }

运行的结果:

好处:
1.只要是把验证的规则特性定义好,就可以重新使用
2.如果需要验证哪个属性,就把特性标记在哪个属性上就可以了;
3.只是标记了一个特性,就可以获取了一个验证的逻辑。

这样就完成了特性添加额外信息和特性添加额外功能的作用。

posted @ 2023-12-15 16:02  飘雨的河  阅读(23)  评论(0编辑  收藏  举报