特性的學習

特性

在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.只是標記了一個特性,就可以獲取了一個驗證的邏輯。

這樣就完成了特性添加額外信息和特性添加額外功能的作用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章