属性

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

属性具有以下属性:

  • 属性将元数据添加到程序。 元数据 是有关程序中定义的类型的信息。 所有 .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# 关键字 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 类库中类的实际名称等效。

属性参数

许多属性都有参数,可以是 位置未命名命名。 下表介绍如何使用命名属性和位置属性:

位置参数

属性构造函数的参数:

命名参数

属性的属性或字段:

  • 必须指定,不能省略
  • 始终先指定
  • 特定顺序指定
  • 始终可选,如果为 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 方法或 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

查看使用属性的方法

下面是在代码中使用属性的一些常见方法:

  • 使用HttpPost特性标记响应 POST 消息的控制器方法。 有关更多信息,请参见 HttpPostAttribute 类。
  • 介绍如何在与本机代码进行交互时封送方法参数。 有关更多信息,请参见 MarshalAsAttribute 类。
  • 描述类、方法和接口的组件对象模型 (COM) 属性。
  • 使用 DllImportAttribute 类调用非托管代码。
  • 从标题、版本、说明或商标方面描述程序集。
  • 描述哪个类的成员需要序列化以实现持久性。
  • 介绍如何在类成员和 XML 节点之间映射 XML 序列化。
  • 描述方法的安全要求。
  • 指定用于强制实施安全性的特征。
  • 使用实时 (JIT) 编译器控制优化,使代码更易于调试。
  • 获取方法调用方的相关信息。

查看反思方案

反射在以下情境中很有用: