特性概述
特性能够将声明性的信息与程序元素(如程序集assemblies、类Class、方法、属性等)关联在一起,它的本质是添加程序的元数据。程序在编译阶段会将特性包含的额外信息写入元数据中,由于特性与程序元素存在关联,后续可以使用反射机制访问与程序元素关联的特性。
- 特性可以往元数据中添加信息。
- 特性可以应用在多种程序元素之上,包括程序集、类、方法、属性、字段、参数、返回值等。
- 一个程序元素上可以有多个特性。
- 特性可以像方法一样具有传入参数。
元数据概述
元数据在程序编译时生成,元数据是描述程序中Class的信息,包括类的名称、成员属性、成员方法、可见性、基类、实现接口、关联的特性等。编译完成时元数据将被写入PE文件中,在程序运行时可以通过反射机制访问元数据。
补充:PE文件指的是Portable Executable/可移植可执行文件,一般是exe、dll等类型的文件。
反射机制概述
反射机制提供了一个类型为"Type"的对象,在此将其称为反射对象。使用反射对象可以动态地创建实例、调用该实例的方法、访问该实例的属性和特性等。(实际是通过访问元数据实现的)
定义特性类
特性的本质是一个类,自定义特性类必须直接或间接派生自 Attribute 类。自定义的特性类也可以有构造方法、属性、方法等成员,其中构造方法中的参数来自使用特性时提供的实参。自定义特性类的过程如下:
1、应用 AttributeUsage特性
自定义特性类需要以 AttributeUsage 特性开头,用来声明特性类的一些特征。例如如其他类是否可以继承你的属性,或者此属性可以应用到哪些元素等。
cs
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
AttributeUsage包含三个参数:
- AttributeTargets:声明自定义的特性可应用于哪些程序元素上。
- Inherited:应用了自定义特性的类,其派生类是否能继承自定义特性。
- AllowMultiple :自定义的特性能否在同一个程序元素上多次应用。
2、补充特性类的成员
以下定义了一个具有成员属性、成员方法、构造方法的作者特性类。
cs
namespace MyAttribute
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
public class AuthorAttribute : Attribute
{
private string AuthorName { get; set; }
public AuthorAttribute(string name)
{
AuthorName = name;
}
public void OutPutAuthorName()
{
Console.WriteLine("作者:"+AuthorName);
}
}
}
定义特性类时需要注意以下几点:
-
特性类必须声明为public。
-
特性类的名称尽量以单词 Attribute 结束增强保证可读性。 应用特性时,可以不否包含 Attribute后缀。
-
特性类必须直接或间接派生自 Attribute 类。
-
特性类中构造方法的参数来自应用特性时提供的实参。
应用自定义特性
将上述定义的作者特性应用到Book类上,实现调用Book类的GetBookName()方法后,显示书本作者的名称。
cs
[Author("曹雪芹")]
public class Book
{
private string BookName { get; set; } = "红楼梦";
public void GetBookName ()
{
Console.WriteLine ("书名:"+BookName);
}
}
检索特性
Author特性应用在Book类上之后,Author特性类就与Book类就形成了关联。后续可以通过Book类的反射对象直接获取Author特性类的实例,并访问特性实例的属性、方法等。
cs
static void Main(string[] args)
{
Book book = new Book();
book.GetBookName();
//创建Book的反射类
Type reflectionObject = book.GetType();
//检索Book类上是否应用了特性
AuthorAttribute? authorAttribute = (AuthorAttribute)reflectionObject.GetCustomAttribute(typeof(AuthorAttribute));
if (authorAttribute != null)
{
authorAttribute.OutPutAuthorName();
}
}
运行结果:
补充微软官方文档:Attributes and reflection - C# | Microsoft Learn
案例
以下是.Net FrameWork项目自定义权限校验特性的过程,此自定义特性用于限制管理员以外的角色调用接口。
1、定义特性类
cs
public class AdminAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext.Session["AdminValidateState"] == null)
{ return false; }
else
{
return httpContext.Session["AdminValidateState"].ToString() == "true";
}
}
}
此特性间接继承了 FilterAttribute,程序会将此特性类当作Filter处理。http请求中携带了"AdminValidateState" :"true" 键值对则放行,否则拦截请求。
2、应用特性
特性应用在Controller中的方法上。
cs
[HttpPost]
[AdminAuthorize]
public JsonResult ChangeAdminPwd(string NewPassword) { }
3、检索特性
不同于窗口程序需要手动编写反射代码来检索特性.Net FrameWork项目运行时会自动创建Controller实例,检索关联的特性并调用。开发者只需要按照框架开放的接口定义特性,MVC框架底层会自动使用反射机制检索特性。