特性提供了一种将元数据或声明性信息与代码(程序集、类型、方法、属性等)相关联的强大方法。 将属性与程序实体关联后,可以使用称为 反射的技术在运行时查询属性。
属性具有以下属性:
- 属性将元数据添加到程序。 元数据 是有关程序中定义的类型的信息。 所有 .NET 程序集都包含一组指定的元数据,用于描述程序集中定义的类型和类型成员。 可以添加自定义属性以指定任何其他必需信息。
- 特性可以应用于整个程序集、模块或较小的程序元素,例如类和属性。
- 特性可以接受与方法和属性相同的参数。
- 通过属性,程序可以使用反射检查其自己的元数据或其他程序中的元数据。
利用反射进行工作
反射 API 提供的功能是描述程序集、模块和类型。 可以使用反射动态创建类型的实例、将类型绑定到现有对象或从现有对象获取类型,并调用其方法或访问其字段和属性。 在代码中使用属性时,反射使你能够访问它们。 有关详细信息,请参阅 属性。
下面是使用 GetType() 该方法进行反射的简单示例。 基类中的所有 Object
类型都继承此方法,该方法用于获取变量的类型:
注意
请确保在 C# (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=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
IL 的关键字差异
C# 关键字 protected
, internal
在中间语言(IL)中没有意义,在反射 API 中不使用。 IL 中的相应术语是 系列 和 程序集。 可通过以下几种方式使用这些术语:
- 若要通过反射识别
internal
方法,请使用IsAssembly属性。 - 若要标识
protected internal
方法,请使用 IsFamilyOrAssembly。
使用属性
属性几乎可以放置在任何声明中,但特定属性可能会限制其有效声明的类型。 在 C# 中,通过将括在方括号([]
)中的属性的名称放在应用实体的声明上方来指定特性。
在此示例中,使用 SerializableAttribute 特性将特定特征应用于类:
[Serializable]
public class SampleClass
{
// Objects of this type can be serialized.
}
可以通过 DllImportAttribute 特性声明方法:
[System.Runtime.InteropServices.DllImport("user32.dll")]
extern static void SampleMethod();
可以在声明上放置多个属性:
void MethodA([In][Out] ref double x) { }
void MethodB([Out][In] ref double x) { }
void MethodC([In, Out] ref double x) { }
可以为给定实体多次指定某些属性。 以下示例显示了属性 ConditionalAttribute 的多重用法:
[Conditional("DEBUG"), Conditional("TEST1")]
void TraceMethod()
{
// ...
}
注意
按照约定,所有属性名称以后缀“Attribute”结尾,以将它们与 .NET 库中的其他类型的区分开来。 但是,在代码中使用属性时,无需指定属性后缀。 例如,声明 [DllImport]
等效于 [DllImportAttribute]
声明,但 DllImportAttribute
与 .NET 类库中类的实际名称等效。
属性参数
许多属性都有参数,可以是 位置、 未命名或 命名。 下表介绍如何使用命名属性和位置属性:
位置参数
属性构造函数的参数:
命名参数
属性的属性或字段:
- 必须指定,不能省略
- 始终先指定
- 按 特定顺序指定
- 始终可选,如果为 false,则省略
- 在位置参数之后指定
- 按任意顺序指定
例如,以下代码显示了三个等效 DllImport
属性:
[DllImport("user32.dll")]
[DllImport("user32.dll", SetLastError=false, ExactSpelling=false)]
[DllImport("user32.dll", ExactSpelling=false, SetLastError=false)]
第一个参数(DLL 名称)是位置性的,始终为第一个参数。 其他实例是命名参数。 在此方案中,两个命名参数默认为 false,因此可以省略它们。 有关默认参数值的信息,请参阅各个属性的文档。 有关允许的参数类型的详细信息,请参阅 C# 语言规范属性 部分。
特性目标
属性的 目标 是属性适用的实体。 例如,特性可以应用于类、方法或程序集。 默认情况下,属性应用于其后面的元素。 但是,还可以显式标识要关联的元素,例如方法、参数或返回值。
若要显式标识属性目标,请使用以下语法:
[target : attribute-list]
下表显示了可能 target
值的列表。
目标值 | 适用于 |
---|---|
assembly |
整个程序集 |
module |
当前程序集模块 |
field |
类或结构中的字段 |
event |
活动 |
method |
方法或 get 和 set 属性访问器 |
param |
方法参数或 set 属性访问器参数 |
property |
财产 |
return |
方法、属性索引器或 get 属性访问器的返回值 |
type |
结构、类、接口、枚举或委托 |
可以指定 field
目标值,将属性应用于为 自动实现的属性创建的后盾字段。
以下示例演示如何将属性应用于程序集和模块。 有关详细信息,请参阅通用属性(C#)。
using System;
using System.Reflection;
[assembly: AssemblyTitleAttribute("Production assembly 4")]
[module: CLSCompliant(true)]
以下示例演示如何将属性应用于 C# 中的方法、方法参数和方法返回值。
// 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
信息来解析不明确的属性目标。 有关详细信息,请参阅 AttributeUsage。
查看使用属性的方法
下面是在代码中使用属性的一些常见方法:
- 使用
HttpPost
特性标记响应 POST 消息的控制器方法。 有关更多信息,请参见 HttpPostAttribute 类。 - 介绍如何在与本机代码进行交互时封送方法参数。 有关更多信息,请参见 MarshalAsAttribute 类。
- 描述类、方法和接口的组件对象模型 (COM) 属性。
- 使用 DllImportAttribute 类调用非托管代码。
- 从标题、版本、说明或商标方面描述程序集。
- 描述哪个类的成员需要序列化以实现持久性。
- 介绍如何在类成员和 XML 节点之间映射 XML 序列化。
- 描述方法的安全要求。
- 指定用于强制实施安全性的特征。
- 使用实时 (JIT) 编译器控制优化,使代码更易于调试。
- 获取方法调用方的相关信息。
查看反思方案
反射在以下情境中很有用:
- 访问程序元数据中的属性。 有关详细信息,请参阅 检索存储在属性中的信息。
- 检查和实例化程序集中的类型。
- 使用命名空间中的 System.Reflection.Emit 类在运行时生成新类型。
- 对运行时创建的类型执行后期绑定和访问方法。 有关详细信息,请参阅 动态加载和使用类型。