C# 特性基础
特性机制可以帮助程序员以申明的方式进行编程,而不再需要考虑实现的细节。
4.1 神马是特性?如何自定义一个特性?
(1)特性是什么?
特性是一种有别于普通命令式编程的编程方式,通常被称为申明式编程方式。所谓申明式编程方式就是指程序员只需要申明某个模块会有怎样的特性,而无需关心如何去实现。下面的代码就是特性在ASP.NET MVC中的基本使用方式:
[HttpPost] public ActionResult Add(UserInfo userInfo) { if (ModelState.IsValid) { // To do fun } return RedirectToAction("Index"); }
当一个特性被添加到某个元素上时,该元素就被认为具有了这个特性所代表的功能或性质,例如上述代码中Add方法在添加了HttpPost特性之后,就被认为只有遇到以POST的方式请求该方法时才会被执行。
Note:特性在被编译器编译时,和传统的命令式代码不同,它会被以二进制数据的方式写入模块文件的元数据之中,而在运行时再被解读使用。特性也是经常被反射机制应用的元素,因为它本身是以元数据的形式存放的。
(2)如何自定义特性
除了直接使用.NET中内建的所有特性之外,我们也可以建立自己的特性来实现业务逻辑。在上面反射工厂的实现中就使用到了自定义特性。具体来说,定义一个特性的本质就是定义一个继承自System.Attribute类的类型,这样的类型就被编译器认为是一个特性类型。
下面我们看看如何自顶一个特性并使用该特性:
①定义一个继承自System.Attribute的类型MyCustomAttribute
/// <summary> /// 一个自定义特性MyCustomAttribute /// </summary> [AttributeUsage(AttributeTargets.Class)] public class MyCustomAttribute : Attribute { private string className; public MyCustomAttribute(string className) { this.className = className; } // 一个只读属性ClassName public string ClassName { get { return className; } } }
一个继承自System.Attribute的类型,就是一个自定义特性,并且可以将其添加到适合的元素之上。特性将会被写入到元数据之中,所以特性的使用基本都是基于反射机制。
②在入口方法中使用MyCustomAttribute
[MyCustom("UseMyCustomAttribute")] class UseMyCustomAttribute { static void Main(string[] args) { Type t = typeof(UseMyCustomAttribute); // 通过GetCustomAttributes方法得到自定义特性 object[] attrs = t.GetCustomAttributes(false); MyCustomAttribute att = attrs[0] as MyCustomAttribute; Console.WriteLine(att.ClassName); Console.ReadKey(); } }
为入口方法所在的类型UseMyCustomAttribute类添加了一个自定义特性,就可以在该类的方法中通过调用该类型的GetCustomAttributes方法获取所有添加到该类型的自定义特性数组,也就可以方便使用该自定义特性所具备的性质和能力(例如代码中的属性成员可以方便获取)。
关于自定义特性,有几点需要注意:
- 虽然没有强制规定,但按照约定最好特性类型的名字都以Attribute结尾;
- 在C#中为了方便起见,使用特性时都可以省略特性名字后的Attribute,例如上述代码中的[MyCustom("UseMyCustomAttribute")]代替了[MyCustomAttribute("UseMyCustomAttribute")];
- 特性类型自身也可以添加其他的特性;
4.2 .NET中特性可以在哪些元素上使用?
特性可以被用来使用到某个元素之上,这个元素可以是字段,也可以是类型。对于类、结构等元素,特性的使用可以添加在其定义的上方,而对于程序集、模块等元素的特性来说,则需要显式地告诉编译器这些特性的作用目标。例如,在C#中,通过目标关键字加冒号来告诉编译器的使用目标:
// 应用在程序集 [assembly:MyCustomAttribute] // 应用在模块 [module: MyCustomAttribute] // 应用在类型 [type: MyCustomAttribute]
我们在设计自定义特性时,往往都具有明确的针对性,例如该特性只针对类型、接口或者程序集,限制特性的使用目标可以有效地传递设计者的意图,并且可以避免不必要的错误使用特性而导致的元数据膨胀。AttributeUsage特性就是用来限制特性使用目标元素的,它接受一个AttributeTargets的枚举对象作为输入来告诉AttributeUsage西望望对特性做何种限定。例如上面展示的一个自定义特性,使用了限制范围:
[AttributeUsage(AttributeTargets.Class)] public class MyCustomAttribute : Attribute { ..... }
Note:一般情况下,自定义特性都会被限制适用范围,我们也应该养成这样的习惯,为自己设计的特性加上AttributeUsage特性,很少会出现使用在所有元素上的特性。即便是可以使用在所有元素上,也应该显式地申明[AttributeUsage(AttributesTargets.All)]来提高代码的可读性。
4.3 如何获知一个元素是否申明了某个特性?
在.NET中提供了很多的方法来查询一个元素是否申明了某个特性,每个方法都有不同的使用场合,但是万变不离其宗,都是基于反射机制来实现的。
首先,还是以上面的MyCustomAttribute特性为例,新建一个入口方法类Program:
/// <summary> /// 一个自定义特性MyCustomAttribute /// </summary> [AttributeUsage(AttributeTargets.Class)] public class MyCustomAttribute : Attribute { private string className; public MyCustomAttribute(string className) { this.className = className; } // 一个只读属性ClassName public string ClassName { get { return className; } } } [MyCustom("Program")] class Program { static void Main(string[] args) { Type attributeType = typeof(MyCustomAttribute); Type thisClass = typeof(Program); } }
(1)System.Attribute.IsDefined方法
// 使用IsDefined方法 bool isDefined = Attribute.IsDefined(thisClass, attributeType); Console.WriteLine("Program类是否申明了MyCustomAttribute特性:{0}", isDefined);
(2)System.Attribute.GetCustomerAttribute方法
// 使用Attribute.GetCustomAttribute方法 Attribute att = Attribute.GetCustomAttribute(thisClass, attributeType); if (att != null) { Console.WriteLine("Program类申明了MyCustomAttribute特性,特性的成员为:{0}", (att as MyCustomAttribute).ClassName); }
(3)System.Attribute.GetCustomerAttributes方法
// 使用Attribute.GetCustomAttributes方法 Attribute[] atts = Attribute.GetCustomAttributes(thisClass, attributeType); if (atts.Length > 0) { Console.WriteLine("Program类申明了MyCustomAttribute特性,特性名称为:{0}", ((MyCustomAttribute)atts[0]).ClassName); }
(4)System.Reflection.CustomAttributeData类型
// 使用CustomAttributeData.GetCustomAttributes方法 IList<CustomAttributeData> attList = CustomAttributeData.GetCustomAttributes(thisClass); if (attList.Count > 0) { Console.WriteLine("Program类申明了MyCustomAttribute特性"); // 注意:这里可以对特性进行分析,但无法得到其实例 CustomAttributeData attData = attList[0]; Console.WriteLine("该特性的名字是:{0}", attData.Constructor.DeclaringType.Name); Console.WriteLine("该特性的构造方法有{0}个参数", attData.ConstructorArguments.Count); }
下图是四种方式的执行结果:
这四种方法各有其特点,但都可以实现查询某个元素是否申明了某个特性的这一功能。其中,可以看到第(4)种方式,可以对特性进行分析,但无法得到其实例。另外,自定义特性被申明为sealed表示不可继承,这是因为在特性被检查时,无法分别制定特性和其派生特性,这一点需要我们注意。
4.4 一个元素是否可以重复申明同一个特性?
对于有些业务逻辑来说,一个特性反复地申明在同一个元素上市没有必要的,但同时对于另一些逻辑来说,又非常有必要对同一元素多次申明同一特性。很幸运,.NET的特性机制完美支持了这一类业务需求。
当一个特性申明了AttributeUsage特性并且显式地将AllowMultiple属性设置为true时,该特性就可以在同一元素上多次申明,否则的话编译器将报错。
例如下面一段代码,类型Program多次申明了MyCustomAttribute特性:
[MyCustom("Class1")] [MyCustom("Class2")] [MyCustom("Class3")] public class Program { public static void Main(string[] args) { } } /// <summary> /// 一个自定义特性MyCustomAttribute /// </summary> [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class MyCustomAttribute : Attribute { private string className; public MyCustomAttribute(string className) { this.className = className; } // 一个只读属性ClassName public string ClassName { get { return className; } } }
通常情况下,重复申明同一特性往往会传入不同的参数。此外,如果不显式地设置AllowMultiple属性时多次申明同一特性会如何呢?在这种情况下,编译器将会认为自定义特性不能多次申明在同一元素上,会出现以下的编译错误:
出处:http://edisonchou.cnblogs.com