跳转至内容

.NET 开发基金会/属性

来自 Wikibooks,开放世界中的开放书籍


反射:构建自定义属性


附录:构建自定义属性

[编辑 | 编辑源代码]

此页面原始文本由 William "Scott" Baker 撰写

属性摘要

[编辑 | 编辑源代码]

属性是一种“标记”代码元数据元素的方法,使用描述性信息,这些信息可以在运行时使用反射访问。属性必须直接或间接派生自System.Attribute。.NET 框架中存在大量属性;您也可以定义自己的属性。在代码中使用属性有三个方面

  1. 定义自定义属性类,包括以下步骤
    1. AttributeUsageAttribute属性分配给您的类。
    2. 编写代码来定义您的自定义属性类。
    3. 为您的类创建参数。
  2. 将属性分配给代码成员。
  3. 在运行时检索属性信息。

创建自定义属性类

[编辑 | 编辑源代码]

如前所述,.NET 框架中存在多个预定义属性;您可能已经在代码中使用过它们。特别是 XML 解析器在(反)序列化对象时严重依赖属性。您也可以定义自己的自定义属性,如下所示。定义自定义属性包括三个步骤

  1. AttributeUsageAttribute属性分配给您的类。
  2. 编写代码来定义您的自定义属性类。
  3. 为您的类创建参数。

"AttributeUsageAttribute"分配给您的类

[编辑 | 编辑源代码]
属性的“范围”和其他特性应通过使用AttributeUsageAttribute属性来指定。
注意:在 Visual Basic 中,必须在所有自定义属性上使用AttributeUsageAttribute属性。将AttributeUsageAttribute属性应用于类
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
public Class QualityCheckAttribute : System.Attribute 
{
  // ...
}

请注意使用“AttributeUsage”与“AttributeUsageAttribute”。按照惯例,所有属性都以“Attribute”后缀命名 - 但在代码中使用时可以省略后缀。这对于用户定义的属性也是如此;QualityCheckAttribute属性可以引用为

[QualityCheck] // or... 
[QualityCheckAttribute]

AttributeUsageAttribute具有三个成员ValidOn、AllowMultipleInherited.

  • ValidOn成员接受AttributeTargets枚举值,并将您的属性限制为您指定的代码类型。默认值为AttributeTargets.All。您可以将您的属性限制为类、枚举、返回值或以下列表中的任何内容
All (any element)   Delegate    GenericParameter    Parameter
Assembly            Enum        Interface           Property
Class               Event       Method              ReturnValue
Constructor         Field       Module*             Struct

*Module refers to a portable executable (.exe or .dll), and not a Visual Basic standard module.

您还可以将目标类型组合为按位 OR 运算以指定多个可接受值

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
  • AllowMultiple是一个布尔值,它确定是否可以将属性多次应用于给定成员。默认值为false。以下示例说明了在代码元素上使用多个相同属性的实例
[QualityCheck("Scott Baker", "02/28/06", IsApproved = true,
   Comment = "This code follows all established guidelines.  Release approved.")]
[QualityCheck("Matt Kauffman", "01/15/06", IsApproved = false,
   Comment = "Code quality much improved. Minor revision required.")]
[QualityCheck("Joe Schmoe", 01/01/06", IsApproved = false,
   Comment = "This code is a mess and needs a complete rewrite")]
public class MyClass
{
// ... 
}
  • Inherited成员确定是否将类上设置的属性继承到继承树中的更低级别类。默认值为 true
[AttributeUsage(AttributeTargets.Class)]
public class AttrOneAttribute : Attribute
{
  // ... 
}

// This attribute will not be inherited 
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class AttrTwoAttribute : Attribute
{
  // ... 
}

[AttrOne]
[AttrTwo]
public class ClassOne 
{
  // ... 
}

// This class inherits AttrOne from ClassOne, 
// but not AttrTwo 
public class ClassTwo : ClassOne
{
  // ... 
}

定义自定义属性类

[编辑 | 编辑源代码]
  • 属性是继承自System.Attribute的类,直接或间接继承
public Class QualityCheckAttribute : System.Attribute // direct 
{
  // ...
}

public Class FinalCheck : QualityCheckAttribute  // indirect 
{
// ...
}
  • 属性类具有AttributeUsageAttribute属性
[AttributeUsage(AllowMultiple = true, Inherited = false)]
public Class QualityCheckAttribute : System.Attribute
{
  // ...
}

如前所述,在 VB 中必须使用AttributeUsageAttribute。在 C# 中,如果未声明,则会自动应用默认值。

为属性创建参数

[编辑 | 编辑源代码]
属性接受两种类型的参数:位置参数和命名参数。位置参数由类中的公共构造函数定义,必须按定义的顺序排列,并且是必需的;命名参数由公共属性定义,可以按任何顺序排列,并且是可选的。
位置参数
[编辑 | 编辑源代码]
属性的位置参数由类中的构造函数定义。与任何类一样,构造函数可以重载,并且可以定义不接受任何参数的默认构造函数。与任何其他类一样,位置参数的签名必须与属性类中构造函数的签名匹配
public class QualityCheckAttribute : Attribute
{
  public QualityCheckAttribute(string Name, string Date)
  // ... 
}
命名参数
[编辑 | 编辑源代码]
属性的命名参数由类中定义的公共属性定义。命名参数是可选的,并在任何位置参数之后声明。该IsApproved属性演示了命名参数
public class QualityCheckAttribute : Attribute
{
  private string _name;
  private string _date;
  private bool isApproved;
  public bool IsApproved
  {
    get {return isApproved;}
    set {isApproved = value;}
  }
  public QualityCheckAttribute(string Name, string Date)
  {
    // ...
  }
}

请记住,代码中的变量可以既是位置参数又是命名参数。如果我们要为 _name_date 字段添加公共属性,我们可以将它们用作命名参数或位置参数。当然,这并不推荐:必需参数应为位置参数,可选参数应为命名参数。

将属性分配给代码成员

[编辑 | 编辑源代码]

您已经看到了将属性分配给代码成员的示例。但是,需要澄清一些要点。

  • 消歧义是指在代码成员上使用属性的澄清。
  • 语法 - 应用多个属性的方法不止一种。

消歧义

[编辑 | 编辑源代码]
以下代码不清楚SomeAttribute是否适用于方法MyMethod或其返回值
public class MyAttribute : Attribute
{
  [SomeAttribute("Hello")]
  public string MyMethod(aString)
  {
    return aString;
  }
}

消除歧义可以解决这些问题。通过指定属性应用到的代码类型,我们可以消除混淆。以下代码显示属性应用于返回值

public class MyAttribute : Attribute
{
  [return : SomeAttribute]
  public string MyMethod(aString)
  {
    return aString;
  }
}

下表列出了所有允许使用属性的声明;对于每个声明,第二列列出了声明中属性的可能目标。**粗体**的目标是默认目标。

Declaration               Possible targets
assembly                  assembly
module                    module
class                     type
struct                    type
interface                 type
enum                      type
delegate                  type, return
method                    method, return
parameter                 param
field                     field
property — indexer        property
property — get accessor   method, return
property — set accessor   method, param, return
event — field             event, field, method
event — property          event, property
event — add               method, param
event — remove            method, param

*Reference: Disambiguating Attribute Targets (C# Programming Guide)

人们可能会认为AttributeUsageAttribute 的 AttributeTargets在属性定义中将有助于防止这种混淆:这是错误的。编译器在解决冲突时不会使用AttributeUsageAttribute信息。即使您定义了一个属性使其仅应用于特定类型,例如AttributeTargets.Return,您仍然必须明确它应用于返回值类型,在应用属性时,否则编译器将使用默认目标方法类型,并抛出错误。

语法:应用多个属性

[编辑 | 编辑源代码]
将多个属性应用于成员时,有两种方法可以做到这一点
[AttrOne(...), AttrTwo(...)]  
  // or...
[AttrOne(...)]
[AttrTwo(...)]

这两种方法是等效的。请记住,如果您要在单个大括号中指定多个属性,则它们必须应用于相同的目标类型。否则,您必须为每种类型提供单独的声明

[return : AttrOne(...), method : AttrTwo(...)]  // <-- invalid!
  // instead, you must...
[return : AttrOne(...)]
[method : AttrTwo(...)]

在运行时检索属性信息

[编辑 | 编辑源代码]

能够声明和应用属性,除非我们能够检索这些数据并使用它们,否则并不是很有帮助。幸运的是,这是一个简单的过程。将解决三个基本场景

  1. 从成员中检索单个属性。
  2. 从成员中检索多个属性。
  3. 从多个成员中检索单个类型的属性。

从成员中检索单个属性

[编辑 | 编辑源代码]

要访问属性信息

  1. 声明属性类型的实例。
  2. 使用Attribute.GetCustomAttribute(类型, typeof)方法将属性读入实例。
  3. 使用实例的属性来读取值。

以下示例代码声明了一个类ExampleClass具有一个QualityCheck属性。该GetSingleAttribute方法接受目标成员类型和要查找的属性类型。该Attribute.GetCustomAttribute方法将属性信息检索到attr对象中,从中我们可以读取最重要的IsApproved属性

[QualityCheck("Scott Baker", "02/04/2006", IsApproved = false)]
public class ExampleClass
{

  public static void Main()
  {
    GetSingleAttribute(typeof(ExampleClass), typeof(QualityCheck))
  }
 
  public static void GetSingleAttribute(Type targetType, Type attrType)
  {
    typeof(attrType) attr = (attrType)Attribute.GetCustomAttribute(targetType, typeof(attrType));
  
    if (attr == null)
    { //... }
    else
    {
      Console.Writeline(attr.IsApproved);
    }
  }

要记住的一个重要因素是GetCustomAttribute方法设计为仅读取**一个**属性。GetCustomAttribute实际上检查是否有多个属性匹配 - 如果没有匹配,它将返回null,但如果有多个匹配,它将抛出AmbiguousMatchException。在检查属性时,唯一可以安全地使用GetCustomAttribute的是当属性的定义声明[AttributeUsage(AllowMultiple=false)].

从成员中检索多个属性

[编辑 | 编辑源代码]

读取成员上的多个属性实例与读取一个属性实例并没有太大区别;要读取多个属性,请使用复数GetCustomAttributes,它返回一个属性数组。然后,您可以遍历生成的数组并读取值

QualityCheck[] attrArray = (QualityCheck[])Attribute.GetCustomAttributes(t, typeof(QualityCheck));

foreach (QualityCheck attr in attrArray)
{
  Console.Writeline(attr.IsApproved);
}

从多个成员中检索单个属性类型

[编辑 | 编辑源代码]

如果要执行更复杂的操作,例如检查类中的每个方法是否具有 QualityCheck 属性,该怎么办?由于System.Reflection命名空间,我们甚至不需要费力。只需将所有成员(在本例中为方法)读入MemberInfo数组并遍历它们

using System.Reflection

public class ExampleClass
{
  public static void Main()
  {
    RetrieveAttributes(typeof(ExampleClass));
  }

  public void RetrieveAttributes(Type t)
  {
    MemberInfo[] methodList = t.GetMethods();
    foreach (MemberInfo m in methodList)
    {
      QualityCheck[] attrArray = (QualityCheck[])Attribute.GetCustomAttributes(m, typeof(QualityCheck));
      foreach (QualityCheck attr in attrArray)
      {
        Console.Writeline(attr.IsApproved);
      }
    }
  }
}
[编辑 | 编辑源代码]
华夏公益教科书