C# Advanced Tutorial 1-5-Attribute


主要内容概要

  1. 特性attribute,和注释有什么区别
  2. 声明和使用attribute,AttributeUsage
  3. 运行中获取attribute:额外信息 额外操作
  4. Remark封装、attribute验证

特性及其语法

MVC-EF-WCF-IOC-AOP-SuperSocket 无处不在在。
特性很厉害,加了特性,可以影响编译器编译,还可以增加额外功能。
[Obsolete(“请不要使用这个了,请使用什么来代替”)]//系统
[Serializable]//可以序列化和反序列化

特性到底是什么
特性其实就是一个类,需要直接/间接继承Attribute父类
声明特性的时候,约定俗称是以Attribute 结尾,标记时就可以省略
下面自己写一个特性类:

[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class CutomAttribute : Attribute
{
  public CutomAttribute()
  {
    Console.WriteLine("无参数构造函数");
  }
  public CutomAttribute(int i)
  {
    Console.WriteLine("Int类型构造函数");
  }

  public CutomAttribute(int i, string j)
  {
    Console.WriteLine("两个参数构造函数");
  }
  public string Remark { get; set; }

  public string Description;

  public void Show()
  {
    Console.WriteLine("通过反射调用特性中的方法");
  }
}

public class CutomAttributeChild : CutomAttribute
{
  public CutomAttributeChild() : base(345)
  {
  }
}

在外面就可以对特性Cutom进行应用:

// [Obsolete("请不要使用这个了,请使用什么来代替")]//系统
//[Serializable]//可以序列化和反序列化  
[Cutom(234, "飞哥")]
[Cutom(123)]
[Cutom(567, Remark = ".Net学习", Description = ".Net正在学习")]
[CutomAttributeChild]
public class Student
{
  public int Id { get; set; }
  public string Name { get; set; }

  public void Study()
  {
    Console.WriteLine($"这里是{this.Name}跟着Eleven老师学习");
  }

  //[return:Cutom]
  public string Answer([Cutom]string name)//修饰参数 返回值
  {
    return $"This is {name}";
  }
}

在标记的时候以中括号包裹,可以标记在元素
[AttributeUsage(AttributeTargets.All,AllowMultiple =true)]
AttributeTargets.Class设置标记的元素 指定修饰对象,建议明确的指定标记的元素

特性的原理与作用

Obsolete,Serializable是系统提供的,我们实现不了,这种是特例。
特性在代码运行的时候,究竟起什么作用?例如像框架中会有很多特性,是怎么工作的?

通过反编译工具可以看到:标记了特性的元素,都会在元素内部生成.custom,但是这个c#元素内部不能调用。
AttributeIL
至此,是不是感觉特性没啥?
那么各种框架集里面是如何使用特性? 反射
反射确实可以调用特性,但是需要一个第三方的InvokeCenter,在这里主动去检测并且使用特性,才能提供功能。

  1. 首先,看一下在vipstudent类中的特性的使用情况:
[Cutom(123, Remark = ".Net学习", Description = "正在学习特性")]
public class StudentVip : Student
{
  [Cutom(123, Remark = ".Vip", Description = "高级会员")]
  public string VipGroup { get; set; }

  [Cutom(123, Remark = ".Vip", Description = "高级会员")]
  public void Homework()
  {
    //.cutom
    Console.WriteLine("Homework");
  }

  [SalaryAttribute(1000000)]
  public long Salary { get; set; }
}
  1. 然后,再看一下具体的特性中是怎么写的?这里主要参考CutomAttribute 这个特性的内容。特性中有方法,字段,还有属性。

  2. 最后,研究一下,是如何发挥使用特性的?怎么用反射来调用特性中的内容。

public static void ManagerSudent<T>(T student) where T : Student
{
  Console.WriteLine(student.Id);
  Console.WriteLine(student.Name);
  student.Answer("YZM");
  Type type = student.GetType();
  if (type.IsDefined(typeof(CutomAttribute), true)) // 先判断
  {
    object[] aAttributeArry = type.GetCustomAttributes(typeof(CutomAttribute), true);
    foreach (CutomAttribute attribute in aAttributeArry)
    {
      attribute.Show();
    }

    foreach (var prop in type.GetProperties())
    {
      if (prop.IsDefined(typeof(CutomAttribute), true)) // 先判断
      {
        object[] aAttributeArryprop = prop.GetCustomAttributes(typeof(CutomAttribute), true);
        foreach (CutomAttribute attribute in aAttributeArryprop)
        {
          attribute.Show();
        }
      } 
    } 
    foreach (var method in type.GetMethods())
    {
      if (method.IsDefined(typeof(CutomAttribute), true)) // 先判断
      {
        object[] aAttributeArryMethod = method.GetCustomAttributes(typeof(CutomAttribute), true);
        foreach (CutomAttribute attribute in aAttributeArryMethod)
        {
          attribute.Show();
        }
      }
    }
  }
}

特性是在编译时确认,因此不能赋一个变量!
所以 MVC filter是不能注入的,在core里面才提供了注入了filter的方式

特性的应用

第一个例子:通过特性提供额外信息。
首先举一个remark的实例,在绑定到界面上的时候,经常用到,例如用户的状态。
这里有一个特性,叫做RemarkAttribute,如下:

    [AttributeUsage(AttributeTargets.Field)]
    public class RemarkAttribute : Attribute
    {
        public string Remark { get; private set; }


        public RemarkAttribute(string remark)
        {
            this.Remark = remark;
        }

    }

然后,在用户状态中可以这么用特性:

public enum UserState
{
  /// <summary>
  /// 正常状态
  /// </summary>
  [RemarkAttribute("正常状态")]
  Normal = 0,

  /// <summary>
  /// 已冻结
  /// </summary>
  [RemarkAttribute("已冻结")] 
  Frozen = 1,

  /// <summary>
  /// 已删除
  /// </summary>
  [RemarkAttribute("已删除")]
  Deleted = 2
}

为了获取特性还需要封装一个方法来获取:

public static class AttributeExtend
{
  public static string GetRemark(this Enum value)
  {
    Type type = value.GetType();//这个type是UserState
    var field = type.GetField(value.ToString());//从UserState这个类中获取字段,更具传进来的名字获取,一般是唯一的,例如这里是Deleted
    if (field.IsDefined(typeof(RemarkAttribute), true))//从字段上判断是有特性 true 支持继承
    {
      RemarkAttribute attribute = (RemarkAttribute)field.GetCustomAttribute(typeof(RemarkAttribute), true);//获取特性RemarkAttribute 
      return attribute.Remark;
    }
    else
    {
      return value.ToString();
    }
  }
}

下面就来通过特性获取额外信息

//特性获取额外的信息
string remark1 = AttributeExtend.GetRemark(UserState.Normal);
string remark2 = AttributeExtend.GetRemark(UserState.Frozen);
string remark3 = UserState.Deleted.GetRemark(); //扩展方法

总结:这个例子当中,其实特性的作用是提供了一些信息,一些额外的数据。,应用的特点是:

  • 数据展示——不想展示属性名字,而是一个中文描述;
  • 想指定那个是主键,哪个是自增;
  • 别名——数据库里面叫A 程序叫B 怎么映射起来

第二个例子:通过特性提供额外行为。
例如QQ号码有个范围,可以通过特性来做验证。
在上面我们已经写了vipStudent这个类,里面有个field叫做QQ,有个属性叫Salary,如下:

[LongAttribute(_Max = 10, _Min = 5)]
public long QQ;

[SalaryAttribute(800000)] // 公司的预算
public long Salary { get; set; }

这里有两个特性LongAttribute和SalaryAttribute ,他们分别继承自AbstratValidateAttribute,这个特性里面有个抽象方法叫做Validate,让前两个特性去实现验证,如下:

public abstract class AbstratValidateAttribute : Attribute
{
  public abstract bool Validate(object oValue);
}
[AttributeUsage(AttributeTargets.Field)]
public class LongAttribute : AbstratValidateAttribute
{
  public int _Max { get; set; }
  public int _Min { get; set; }
  public override bool Validate(object oValue)
  {
    return oValue != null && oValue.ToString().Length >= 5 && oValue.ToString().Length <= 10;
  }
}
public class SalaryAttribute : AbstratValidateAttribute
{
  public long _Salary { get; set; }  // 公司的预算 800000

  public SalaryAttribute(long salary)
  {
    _Salary = salary;
  }

  public override bool Validate(object oValue)
  {
    return oValue != null && long.TryParse(oValue.ToString(), out long lValue) && lValue < _Salary;
  }
}

还需要写个泛型的扩展方法,来调用特性的这个验证方法,如下:

public static bool Validate<T>(this T t) where T : class
{
  Type type = t.GetType();
  foreach (var prop in type.GetProperties())
  {
    if (prop.IsDefined(typeof(AbstratValidateAttribute), true)) // 这里先判断  是为了提高性能
    {
      object ovale = prop.GetValue(t);
      foreach (AbstratValidateAttribute attribute in prop.GetCustomAttributes(typeof(AbstratValidateAttribute), true)) // 获取特性的实例  上面先判断之后,再获取实例
      {
        if (!attribute.Validate(ovale))
        {
          return false;
        }
      }
    }
  } 
  foreach (var field in type.GetFields())
  {
    if (field.IsDefined(typeof(AbstratValidateAttribute), true)) // 这里先判断  是为了提高性能
    {
      object ovale = field.GetValue(t);
      foreach (AbstratValidateAttribute attribute in field.GetCustomAttributes(typeof(AbstratValidateAttribute), true)) // 获取特性的实例  上面先判断之后,再获取实例
      {
        if (!attribute.Validate(ovale))
        {
          return false;
        }
      }
    }
  } 
  return true;
}

这样通过AbstratValidateAttribute可以获取到所有的特性,然后分别去获取属性和字段上的所有特性,依次去执行验证的方法,这就是抽象类的一个应用,分别是实现不同的validate方法。可以通过调用特性student.Validate()来验证。


文章作者: Chaoqiang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Chaoqiang !
评论
 上一篇
C# Advanced Tutorial 1-6-Delegate C# Advanced Tutorial 1-6-Delegate
主要内容概要 委托的声明、实例化和调用 泛型委托–Func Action 委托的意义:解耦 委托的意义:异步多线程 委托的意义:多播委托 事件 观察者模式 委托委托的声明委托在IL 中就是一个类,继承自父类(特殊类)MulticastDe
下一篇 
C# Advanced Tutorial Object-Oriented-Programming C# Advanced Tutorial Object-Oriented-Programming
主要内容概要 封装&继承&多态理解 重写overwrite(new) 覆写override 重载overload 抽象类&接口理解和选择 面向过程和面向对象下面通过一个经典例子来对比一下面向过程和面向对象的区别:
  目录