属性

特性提供了一种强大的元数据或声明性信息与代码(程序集、类型、方法、属性等)相关联的方法。 将特性与程序实体相关联后,可以在运行时使用反射这项技术查询特性。

属性具有以下属性:

  • 属性将元数据添加到程序。 元数据 是有关程序中定义的类型的信息。 所有 .NET 程序集都包含一组指定的元数据,用于描述程序集中定义的类型和类型成员。 可以添加自定义属性来指定所需的任何其他信息。
  • 可以将一个或多个属性应用于整个程序集、模块或较小的程序元素,例如类和属性。
  • 特性可以接受与方法和属性相同的参数。
  • 程序可以使用反射检查其自己的元数据或其他程序中的元数据。

反射提供描述程序集、模块和类型的对象(Type 类型)。 可以使用反射动态创建类型的实例、将类型绑定到现有对象或从现有对象获取类型,并调用其方法或访问其字段和属性。 如果在代码中使用属性,反射使你能够访问它们。 有关详细信息,请参阅 属性

以下是一个使用 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=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e

注意

C# 关键字 protectedinternal 在中间语言(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 类库中属性的实际名称。

属性参数

许多属性具有参数,这些参数可以是有位置的、未命名的或命名的。 必须按特定顺序指定任何位置参数,并且不能省略。 命名参数是可选的,可以按任意顺序指定。 首先指定位置参数。 例如,这三个属性等效:

[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 方法或 getset 属性访问器
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

特性的常见用途

以下列表包括代码中属性的一些常见用途:

  • 使用 Web 服务中的 WebMethod 属性标记方法,以指示该方法应该可通过 SOAP 协议调用。 有关详细信息,请参阅 WebMethodAttribute
  • 描述在与本机代码互操作时如何处理方法参数。 有关详细信息,请参阅 MarshalAsAttribute
  • 描述类、方法和接口的 COM 属性。
  • 使用 DllImportAttribute 类调用非托管代码。
  • 从标题、版本、说明或商标方面描述程序集。
  • 描述要序列化并暂留类的哪些成员。
  • 描述如何在类成员和 XML 节点之间进行映射,以便进行 XML 序列化。
  • 描述方法的安全要求。
  • 指定用于强制实施安全性的特征。
  • 通过实时 (JIT) 编译器控制优化,使代码更易于调试。
  • 获取方法调用方的相关信息。

反射概述

在以下情况下,反射非常有用:

  • 当必须访问程序元数据中的属性时。 有关详细信息,请参阅 检索存储在属性中的信息。
  • 检查和实例化程序集中的类型。
  • 用于在运行时生成新类型。 使用 System.Reflection.Emit 中的类。
  • 执行后期绑定,访问在运行时创建的类型上的方法。 请参阅文章 "动态加载和使用类型"。

更多信息,请参阅: