您现在的位置是:首页 >技术交流 >C#_Attribute网站首页技术交流
C#_Attribute
Attribute
Attribute,一般翻译成 特性 (与Property属性做区分)。
它提供了一种将 元数据(metadata) (声明性信息)与代码(程序集、类型、方法、属性等)关联起来的强大方法。在Attribute与程序实体关联之后,可以在运行时使用 反射(reflection) 技术查询该Attribute。
元数据
元数据是描述数据的数据。
在C#编程环境中,它指的是用于描述程序代码的特性。
机智的小伙伴会问,描述程序代码?那不是注释嘛。
这边有个本质区别,注释通常不会影响程序的执行,编译器在编译过程中会忽略注释;而元数据是数据,可以通过反射等技术在程序运行时查询和操作。
Attribute有以下性质:
- Attribute会将元数据添加至程序。 Metadata(元数据) 是一种关于程序中定义的类型的信息。所有的.NET程序集都包含一组指定的元数据,用于描述程序中定义的类型和类成员。你可以添加自定义的Attribute来指定所需的附加信息。
- 可以将一个或多个Attribute应用于整个程序集、模块或更小的程序元素(类、属性之类的)。
- Attribute可以像方法和属性一样接收参数。
- 可以在程序中使用反射来检查元数据。
反射提供类型为Type的对象来描述程序集、模块和类型。你可以使用反射来动态地创建类的实例,将类绑定到现有的对象,或从现有对象获取类型并调用其方法或访问其字段和属性。若你在代码中使用Attribute,则你可以用反射访问它们。
下面是一个简单的反射示例,使用GetType()方法(所有继承自Object的类都可以用)获取变量的类型:
注意
用之前要在 .cs 文件顶部添加 using system; 和 using System.Reflection;
// using GetType to obtain type information
int i = 42;
Type type = i.GetType();
Console.WriteLine(type);
输出: System.Int32
下面示例使用反射获取加载程序集的全名:
// using Reflection to get information of an Assembly
Assembly info = typeof(int).Assembly;
Console.WriteLine(info);
输出:System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
注意
C#关键字 protected 和 internal 在中间语言(IL)中没有意义,也不会在反射API中起作用。在IL中对应的术语是 Family 和 Assembly。要用反射识别internal方法,使用 IsAssembly 属性。要识别 protected internal 方法,使用 IsFamilyOrAssembly。
使用Attribute
Attribute几乎可以放在任何声明上,尽管特定的Attribute可能会限制其有效的声明类型。在C#中,可以通过将Attribute名称用方括号[]括起来,置于该Attribute所应用的实体声明上来指定一个Attribute。
下面示例中, SerializableAttribute Attribute用于给类指定一个特性(该类的对象将被序列化):
[Serializable]
public class SampleClass
{
// objects of this type can be serialized.
}
下面示例声明了一个带有 DllImportAttribute Attribute的方法:
[System.Runtime.InteropServices.DllImport("user32.dll")]
extern static void SampleMethod();
一个声明中也可以放置多个Attribute,如下:
void MethodA([In][Out] ref double x) { }
void MethodB([Out][In] ref double x) { }
void MethodC([In, Out] ref double x) { }
有些Attribute也可以为给定实体指定多次。下面是一个多重使用的Attribute例子(ConditionalAttribute):
[Conditional("DEBUG"), Conditional("TEST1")]
void TraceMethod()
{
// ...
}
注意
按照惯例,所有Attribute名称都以"Attribute"结尾,以区别于.NET库中的其它项。但是,在代码中使用Attribute时,不需要指定Attribute后缀。例如[DllImport]等价于[DllImportAttribute],但是DllImportAttribute是Attribute在.NET类库中的实际名称。
Attribute参数
许多Attribute都有参数,这些参数可能是位置参数(positional)、未命名参数(unnamed)或命名参数(named)。位置参数必须按一定顺序指定,且不能省略。命名参数是可选的,可以按任意顺序指定。下面三个Attribute是等价的:
[DllImport("user32.dll")]
[DllImport("user32.dll", SetLastError=false, ExactSpelling=false)]
[DllImport("user32.dll", ExactSpelling=false, SetLastError=false)]
其中的第一个参数DLL名称,它是位置参数,总是排在第一位;其它参数都是命名参数。在该情况下,两个默认参数默认为false,因此可以省略。位置参数对应于Attribute构造函数的参数(列表)。命名参数或可选参数对应于Attribute的属性(property)或字段(field)。
Attribute目标
Attribute的目标(target)指该Attribute所应用的实体。Attribute可以应用于类、特定的方法或整个程序集。默认情况下,Attribute应用于它后面的元素。但你也可以显式地标识一个Attribute是应用于方法,还是它的参数或其返回值。
要显式标识Attribute目标,使用以下语法:
[target : attribute-list]
target的可能值如下表所示:
Target值 | 应用目标 |
---|---|
assembly | 整个程序集 |
module | 当前程序模块 |
field | 类或结构中的字段 |
event | 事件 |
method | 方法或get和set属性访问器 |
param | 方法参数或set属性访问器参数 |
property | 属性 |
return | 方法的返回值、属性索引或get属性访问器 |
type | 结构体、类、接口、枚举或委托 |
你可以指定字段目标值,以将Attribute应用到自动实现属性(auto-implemented property)创建的后台字段。
以下示例展示了如何将Attribute应用于程序集和模块。
using System;
using System.Reflection;
[assembly: AssemblyTitleAttribute("Production assembly 4")]
[module: CLSCompliant(true)]
下面示例展示如何在C#中将Attribute应用于方法、方法参数和方法返回值。
// default: applies to method
[ValidatedContract]
int Method1() { return 0; }
// applies to method
[method: ValidatedContract]
int Method2() { return 0; }
// applies to parameter
int Method3([ValidatedContract] string contract) { return 0; }
// applies to return value
[return: ValidatedContract]
int Method4() { return 0; }
注意
无论 ValidatedContract被定义为作用于哪些目标,都必须指定 return 目标,即使 ValidatedContract 被定义为只应用于返回值。换句话说,编译器不会使用 AttributeUsage 信息来解析不明确的 Attribute 目标。
Attribute的常见使用
下面列出了代码中Attribute的常见使用:
- 使用Web服务中的 WebMethod Attribute 来标记方法,以指示该方法可以通过SOAP协议调用。
- 描述与本机代码交互时如何封装方法参数。
- 描述类、方法和接口的COM属性。
- 使用 DllImportAttribute类调用非托管代码。
- 使用标题、版本、描述或商标描述程序集。
- 描述为了持久化而序列化类成员。
- 描述如何在类成员和XML节点之间映射以进行XML序列化。
- 描述方法的安全性要求。
- 指定用于强制安全性的特性。
- 控制JIT的优化,使代码易于调试。
- 获取关于方法调用者的信息。
反射概述
反射在以下情况下很有用:
- 当需要访问程序元数据中的属性时。
- 检查和实例化程序集中的类时。
- 在运行时构建新的类。使用 System.Relection.Emit 中的类。
- 执行延迟绑定,访问在运行时创建的类上的方法。