定制特性

定制特性

  • 定制特性 可宣告式的为自己的代码构造添加注解来实现特殊功能。

  • 定制特性允许为每一个元数据记录项定义和应用信息。

  • 这种可扩展的元数据信息能在运行时查询,从而动态改变代码的执行方式。

先看一个应用了特性的例子:

[Custom("这里是学生", Description = "123456", Remark = "123456")]
public class Student
{
  [Custom] //使用特性时可以省略Attribute
   public Student()
  {
  Console.WriteLine("这里是student");
  }
?
  [Custom]
   public int Id { get; set; }
}
?
   

这里是定义特性的类,它继承Attribute。

[AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)] //参数AttributeTargets -- 目标,AllowMultiple -- 允许多重修饰, Inherited -- 允许继承
public class CustomAttribute : Attribute
{
   public CustomAttribute()
  {
  Console.WriteLine("这里是CustomAttribute");
  }
   public CustomAttribute(string remark)
  {
  Console.WriteLine("这里是CustomAttribute带参数");
  }
?
public string Remark { get; set; }
?
public string Description { get; set; }
?
   public void Show()
  {
       Console.WriteLine($"This is {this.GetType().Name}");
  }
?
}
?
public class CustomChildAttribut : CustomAttribute
{
}

上述例子中 对于自己定义的特性也应用了一个特性,也为特性本身就是类。AttributeUsage 特性是一个简单的类,可利用它告诉编译器定制特性的合法应用范围。编译器都内建了对该特性的支持,并会在定制特性应用于无效目标时报错。

The System.AttributeTargets 枚举类型在FCL 中是像下面这样定义的。

[Flags, Serializable]
public enum AttributeTargets {
   Assembly = 0x0001,
   Module = 0x0002,
   Class = 0x0004,
   Struct = 0x0008,
   Enum = 0x0010,
   Constructor = 0x0020,
   Method = 0x0040,
   Property = 0x0080,
   Field = 0x0100,
   Event = 0x0200,
   Interface = 0x0400,
   Parameter = 0x0800,
   Delegate = 0x1000,
   ReturnValue = 0x2000,
   GenericParameter = 0x4000,
   All = Assembly | Module | Class | Struct | Enum |
       Constructor | Method | Property | Field | Event | Interface | Parameter | Delegate | ReturnValue | GenericParameter
}

特性到底是什么?

  • 定制特性其实是一个类型的实例。为符合CLS ,定制特性类必须直接或间接从公共抽象类 system.Attribute 派生。

  • 特性是类的实例,类必须有公共构造器才能创建它的实例。所以,将特性应用于目标元素时,语法类似于调用类的某个实例构造器。

 

特性构造器和字段/属性数据类型*

特性本质是一个类,那它就有自己的构造函数,字段/属性。在定义特性类的实例构造器、字段和属性时,可供选择的数据类型不多,只允许以下类型:

Boolean, Char, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double, String, Type, Object

 

检测定制特性

仅仅定义特性类没有用,将特性应用于类、方法上也只是在程序集生成额外的元数据,也没意义,应用程序的代码不会有任何改变。

类、方法在调用的时候让特性起到作用需要在运行时检查。检查程序执行的类实例或方法是否关联了特性元数据。代码利用一种反射的技术检测特性的存在。下面结合上述的代码举个例子:

public static void Manage(Student student)
{
Type type = student.GetType();
?
if (type.IsDefined(typeof(CustomAttribute), true))//检测有没有这个特性
{
       object item = type.GetCustomAttributes(typeof(CustomAttribute), true)[0];
       foreach (var item in type.GetCustomAttributes(typeof(CustomAttribute), true))
      {
       CustomAttribute attribute = item as CustomAttribute;
       attribute.Show();
      }
  }
}

上述代码中 从student实例中得到类型信息,用IsDefined 方法检测student 实例有没有关联CustomAttribute 特性。如果只想判断目标是否应用了特性,有IsDefined就够了。如果要构造特性对象,必须调用GetCustomAttributes 方法和 GetCustomAttribute 方法。调用这两个方法会构造指定特性类型的新实例。

 分享图片

 

调用表中任何一种方法,内部都必须扫描托管模块的元数据,执行字符串比较来定位指定的定制特性类。这肯定会消耗一定时间。假如对性能的要求比较高,可考虑缓存这些方法的调用结果。

 

两个特性实例的相互匹配

除了判断是否像目标应用了一个特性的实例,可能还需要检查特性的字段来确定它们的值。system.Attribute 重写了obejct 的 Equals方法,会在内部比较两个对象的类型。 可以在自己的定制特性类中重写Equals 来移除反射的使用,从而提升性能。system.Attribute 还公开了虚方法 Match,可重写它来提供更丰富的语义。

 

检测定制特性时不创建从Attribute派生的对象。

如何用另一种技术检测应用于元数据记录项的特性。这适用于比较安全的场合,这个技术保证不会调用从attribute派生的类中的代码。原因是在调用GetCustomAttribute 方法时要调用特性类的构造器。 可用system.Reflection.CustiomAttributeData类在查找特性的同时禁止执行特性类中的代码。1 先用Assembly 的静态方法 ReflectionOnlyLoad加载程序集,再调用CustomAttributeData 类分析这个程序集中的元数据中的特性。

 

条件特性

应用了System.Diagnostics.ConditionalAttribute 的特性类称为条件特性类。这个类可以只在符合条件的情况下编译器才生成这个特性。

//#define TEST
#define VERIFY
using System;
using System.Diagnostics;
[Conditional("TEST")][Conditional("VERIFY")]
public sealed class CondAttribute : Attribute {
}
[Cond]
public sealed class Program {
   public static void Main() {
       Console.WriteLine("CondAttribute is {0}applied to Program type.",
   Attribute.IsDefined(typeof(Program),typeof(CondAttribute)) ? "" : "not ");
  }
}

编译器如果发现向目标元素应用了 CondAttribute 的实例,那么当含有目标元素的代码编译时,只有在定义 TEST 或VERIFY 符号的前提下,编译器才会在元数据中生成特性信息。不过特性类的定义元数据和实现任存在于程序集中。

相关文章
相关标签/搜索