定制特性主要用来定义一些信息,并将这些信息应用于几乎所有的元数据表项上,然后在运行时通过查询这些可扩展的元数据信息来动态的改变代码的执行方式。
关于定制特性,有几点内容需要先认识清楚:
1)定制特性仅仅是为目标元素提供关联附加信息的一种方式;
2)编译器的工作只是将这些附加的信息存放在托管模块的元数据中而已,即编译器只负责检测代码中的定制特性,然后产生相应的元数据。
C#编译器允许我们将定制特性应用于以下条目:
程序集,
模块,
类型,
字段,
方法,
方法参数,
方法返回值,
方法返回值,
属性,
事件
如何定制特性
首先,要与CLS兼容,定制特性的类型必须直接或间接继承自System.Attribute。C#只允许使用与CLS兼容的定制特性。
其次,一个特性就是一个类型的实例,该类型必须有一个公有构造器来创建他的实例。因此,当我们在一个目标元素上应用一个特性的时候,其语法类似于调用该类型的实例构造器。
第三,所有的非抽象特性都必须具有public的访问权限。
第四,根据约定,所有特性类型的名称都应该有一个”Attribute”后缀,
假设我们需要定制一个特性,用于标识页面是否需要登录,我们可以如下定义:
using System; namespace ThornBird.Web.Attributes { [AttributeUsage(AttributeTargets.Class|AttributeTargets.Method)] public class LoginRequiredAttribute : Attribute { } }
在上述例子里,LoginRequiredAttribute在定义的时候,同时也使用了另外一个Attribute:AttributeUsageAttribute,AttributeUsageAttribute特性允许我们告诉编译器我们自己定义的Attribute可以使用在那些地方(如Class,Method等等)
Attribute有另外2个可选公有的属性:AllowMultiple和Inherited
AllowMultiple:该Attribute是否可以使用多次,通常意义上,同一个Attribute使用多次是没有意义的,当然,少数情况它还是有意义的,该值默认false
Inherited:表示是否应该用于派生类或派生方法
如果我们在定制特性的时候,忘记在类上应用AttributeUsage,编译器为我们做以下3点假设:
1)应用于所有目标元素>>AttributeTargets.All;
2)一个目标元素只可以应用一次>>AllowMultiple=false;
3)可以被继承>>Inherited=true;
此三点即AttributeUsage类型中字段的默认值。
如何使用特性
使用定制特性的方法非常简单,C#中,我们将特性放置在方括号里面,然后置于目标元素上即可。如果包含多个特性,我们可以将每一个特性放置在一个方括号里面,或者将多个特性用逗号分割,然后放在一个方括号里面。特性名称的后缀Attribute是可选的。如果特性类型的构造器不接受任何的参数,那么小括号也是可选的。
以下几种的是正确的用法:
[LoginRequired()]
[LoginRequired]
[LoginRequired,Flag]
[LoginRequiredAttribute]
如何检测特性
定义一个特性本身没有什么好处,即使将特性应用在目标元素上,也只是将额外的元数据写入托管模块中,应用程序代码的行为不会有任何的改变。
我们需要在运行时检测目标元素上是否应用了某种特性,然后做出相应的操作。
检测特性的方法有:
1)IsDefined:如果至少有一个指定的Attribute应用于目标元素上,则返回true。该方法速度较快,因为它不需要构造任何特性类型的实例。
2) GetCustomerAttributes:返回一个数组,其中的元素是指定的、应用于目标元素上的特性实例。该方法通常用于那些将AllowMultiple设为true的特性。
3) GetCustomerAttribute:返回一个指定的、应用于目标元素上的特性实例。如果没有则返回null;如果目标元素应用了多个指定的特性实例,则抛
System.Reflaction.AmbiguousMatchException异常。该方法通常用于那些将AllowMultiple设为false的特性。
例子:
假设我们要使用上述定义的LoginRequired特性来指定某些页面时需要登录的,如果没有登录则跳转到登录页面,
我们可以这样使用:
//基类处理特性
public class ThornBirdPage:BasePage
{
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
CheckLogin();
}
private void CheckLogin()
{
if (this.GetType().IsDefined(typeof(LoginRequiredAttribute), true))
{
if(NotLogin())
HttpContext.Current.Response.Redirect("/login.aspx");
}
}
}
//客户端调用
[LoginRequired] public partial class Index : ThornBirdPage { protected void Page_Load(object sender, EventArgs e) { } }
也可以这样使用:
public class ThornBirdPage:BasePage { protected override void OnInit(EventArgs e) { base.OnInit(e); CheckLogin(); } private void CheckLogin() { LoginRequiredAttribute attribute = (LoginRequiredAttribute)Attribute.GetCustomAttribute(this.GetType(),typeof(LoginRequiredAttribute),true); if (attribute != null) HttpContext.Current.Response.Redirect("/login.aspx"); } }
客户端的调用是一样的。