特性

特性

公共语言运行时使你能够添加类似于关键字的描述性声明(称为特性),特性是元数据的一种应用,也是作为描述数据的数据,以便批注编程元素(如类型、字段、方法和属性)。编译运行时的代码时,它将被转换为 Microsoft 中间语言 (MSIL),并和编译器生成的元数据一起放置在可移植可执行 (PE) 文件内。 特性使你能够将额外的描述性信息放到可使用运行时反射服务提取的元数据中。 当你声明派生自 System.Attribute 的特殊类的实例时,编译器会创建特性。

.NET 出于多种原因且为解决许多问题而使用特性。 特性描述如何将数据序列化、指定用于强制安全性的特征并限制通过实时 (JIT) 编译器进行优化,从而使代码易于调试。 特性还可记录文件的名称或代码的作者,或控制窗体开发过程中控件和成员的可见性。

应用属性

编译代码时,特性将被发到元数据中,并且通过运行时反射服务可用于公共语言运行时和任何自定义工具或应用程序。

按照惯例,所有特性名称都以“Attribute”结尾。 但是,面向运行时的几种语言(如 Visual Basic 和 C#)无需指定特性的全名。 例如,若要初始化 System.ObsoleteAttribute,只需将它引用为 Obsolete 即可。

将特性应用于方法

以下代码示例显示如何使用 System.ObsoleteAttribute(其将代码标记为已过时)。 将字符串 "Will be removed in next version" 传递给特性。 当特性描述的代码被调用时,此特性会导致产生编译器警告,显示传递的字符串。

public class Example
{
    // Specify attributes between square brackets in C#.
    // This attribute is applied only to the Add method.
    [Obsolete("Will be removed in next version.")]
    public static int Add(int a, int b)
    {
        return (a + b);
    }
}

class Test
{
    public static void Main()
    {
        // This generates a compile-time warning.
        int i = Example.Add(2, 2);
    }
}

在程序集级别应用特性

如果要在程序集级别应用属性,请使用 assemblyAssembly(Visual Basic 中用assembly )关键字。 下列代码显示在程序集级别应用的 AssemblyTitleAttribute。

using System.Reflection;
[assembly:AssemblyTitle("My Assembly")]

应用此特性时,字符串 "My Assembly" 将被放置在文件元数据部分的程序集清单中。


编写自定义属性

自定义属性直接或间接派生自 System.Attribute 类的传统类。 与传统类一样,自定义特性包含用于存储和检索数据的方法。
正确设计自定义特性的主要步骤如下:

  1. 应用 AttributeUsageAttribute
  1. 声明特性类
  2. 声明构造函数
  3. 声明属性

应用 AttributeUsageAttribute

自定义属性声明以 System.AttributeUsageAttribute 属性开头,定义特性类的一些主要特征。 例如,你可以指定其他类是否可以继承你的属性,或者此属性可以应用到哪些元素。 以下代码片段演示 AttributeUsageAttribute 的使用方法:

[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]

AttributeUsageAttribute 包含下列三个成员,它们对创建自定义属性非常重要:AttributeTargets、Inherited 和 AllowMultiple。

AttributeTargets 成员

上述示例中指定 AttributeTargets.All,表示此属性可应用于所有程序元素。 或者,你可指定 AttributeTargets.Class 和 AttributeTargets.Method,前者表示你的特性仅可适用于一个类,后者表示你的特性仅可应用于一种方法。 所有程序元素都可以通过这种方式使用自定义特性来标记,以对其进行描述。还可传递多个 AttributeTargets 值。 以下代码段指定可以将自定义属性应用于任何类或方法:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] 

Inherited 属性

AttributeUsageAttribute.Inherited 属性指明要对其应用属性的类的派生类能否继承此属性。 此属性使用 true(默认值)或 false 标志。 在以下示例中,MyAttribute 的默认 Inherited 值为 true,而 YourAttribute 的 Inherited 值为 false:

// This defaults to Inherited = true.
public class MyAttribute : Attribute
{
    //...
}

[AttributeUsage(AttributeTargets.Method, Inherited = false)]
public class YourAttribute : Attribute
{
    //...
}

AllowMultiple 属性

AttributeUsageAttribute.AllowMultiple 属性指明元素能否包含属性的多个实例。 如果设置为 true,则允许多个实例。 如果设置为 false(默认值),那么只允许一个实例。

在以下示例中,MyAttribute 的默认 AllowMultiple 值为 false,而 YourAttribute 的值为 true:

//This defaults to AllowMultiple = false.
public class MyAttribute : Attribute
{
}

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class YourAttribute : Attribute
{
}

当应用这些特性的多个实例时, MyAttribute 会生成编译器错误。 以下代码示例显示 YourAttribute 的有效用法以及 MyAttribute的无效用法:

public class MyClass
{
    // This produces an error.
    // Duplicates are not allowed.
    [MyAttribute]
    [MyAttribute]
    public void MyMethod()
    {
        //...
    }

    // This is valid.
    [YourAttribute]
    [YourAttribute]
    public void YourMethod()
    {
        //...
    }
}

如果 AllowMultiple 属性和 Inherited 属性都设置为 true,从另一个类继承的类可以继承一个属性,并具有在同一个子类中应用相同属性的另一个实例。 如果 AllowMultiple 设置为 false,则父类中的所有特性的值将被子类中同一特性的新实例覆盖。

声明特性类

应用 AttributeUsageAttribute 以后,开始定义属性的细节。 特性类的声明类似于传统类的声明,如以下代码所示

[AttributeUsage(AttributeTargets.Method)]
public class MyAttribute : Attribute
{
    // . . .
}

此特性定义说明了以下几点:

  • 特性类必须声明为公共类。
  • 按照约定,特性类的名称以单词 Attribute结束。 尽管没有要求,但仍建议执行此约定以保证可读性。 应用特性时,可以选择是否包含单词 Attribute。
  • 所有特性类必须直接或间接从 System.Attribute 类继承。
  • 在 Microsoft Visual Basic 中,所有自定义特性类必须具有 System.AttributeUsageAttribute 特性。

声明构造函数

类似于传统类,特性是通过构造函数初始化的。 下面的代码段阐明了典型的特性构造函数。 此公共构造函数采用一个参数,并设置一个等于其值的成员变量。

public MyAttribute(bool myvalue)
{
    this.myvalue = myvalue;
}

可以重载此构造函数以适应值的各种组合。 如果你还为自定义特性类定义了 属性 ,则在初始化该特性时可以使用命名参数和定位参数的组合。 通常情况下,将所有必选的参数定义为定位参数,将所有可选的参数定义为命名参数。 在这种情况下,没有必需的参数就无法初始化属性。 其他所有参数都是可选参数。

声明属性

如果你想要定义一个命名参数,或者提供一种简单的方法来返回由特性存储的值,请声明 属性。 应将特性的属性声明为公共实体,此公告实体包含将返回的数据类型的描述。 定义将保存属性值的变量,并将此变量与 get 和 set 方法相关联。 以下代码示例说明如何在属性中实现一个简单属性:

public bool MyProperty
{
    get {return this.myvalue;}
    set {this.myvalue = value;}
}

自定义特性的示例

结合了前面的信息,显示如何设计一个属性来记录有关一段代码的作者的信息。 本示例中的特性存储了编程人员的姓名和级别,以及是否已检查此代码的信息。 它使用三个私有变量来存储要保存的实际值。 每个变量用获取和设置这些值的公共属性表示。 最后,使用两个必需的参数定义构造函数:

[AttributeUsage(AttributeTargets.All)]
public class DeveloperAttribute : Attribute
{
// Private fields.
private string name;
private string level;
private bool reviewed;

// This constructor defines two required parameters: name and level.

public DeveloperAttribute(string name, string level)
{
    this.name = name;
    this.level = level;
    this.reviewed = false;
}

// Define Name property.
// This is a read-only attribute.

public virtual string Name
{
    get {return name;}
}

// Define Level property.
// This is a read-only attribute.

public virtual string Level
{
    get {return level;}
}

// Define Reviewed property.
// This is a read/write attribute.

public virtual bool Reviewed
{
    get {return reviewed;}
    set {reviewed = value;}
}

}

可以采用以下任一种方法,使用全称 DeveloperAttribute 或缩写名称 Developer 应用此属性:

[Developer("Joan Smith", "1")]

-or-

[Developer("Joan Smith", "1", Reviewed = true)]

检索存储在特性中的信息

检索自定义属性的过程非常简单。 首先,声明要检索的属性实例。 然后,使用 Attribute.GetCustomAttribute 方法,用要检索的属性的值初始化新属性。 在初始化新特性后,可使用它的属性来获取值。

检索一个属性实例

在下面的示例中,DeveloperAttribute(如上一部分所述)在类一级适用于 MainApp 类。 GetAttribute 方法使用 GetCustomAttribute 在类级别检索 DeveloperAttribute 中存储的值,再在控制台中显示它们。

using System;
using System.Reflection;
using CustomCodeAttributes;

[Developer("Joan Smith", "42", Reviewed = true)]
class MainApp
{
    public static void Main()
    {
        // Call function to get and display the attribute.
        GetAttribute(typeof(MainApp));
    }

    public static void GetAttribute(Type t)
    {
        // Get instance of the attribute.
        DeveloperAttribute MyAttribute =
            (DeveloperAttribute) Attribute.GetCustomAttribute(t, typeof (DeveloperAttribute));

        if (MyAttribute == null)
        {
            Console.WriteLine("The attribute was not found.");
        }
        else
        {
            // Get the Name value.
            Console.WriteLine("The Name Attribute is: {0}." , MyAttribute.Name);
            // Get the Level value.
            Console.WriteLine("The Level Attribute is: {0}." , MyAttribute.Level);
            // Get the Reviewed value.
            Console.WriteLine("The Reviewed Attribute is: {0}." , MyAttribute.Reviewed);
        }
    }
}

//output:
//The Name Attribute is: Joan Smith.  
//The Level Attribute is: 42.  
//The Reviewed Attribute is: True.  

如果找不到特性,GetCustomAttribute 方法会将 MyAttribute 初始化为 null 值。 此示例在 MyAttribute 中查找此类实例,并在找不到特性时通知用户。 如果在类范围中找不到 DeveloperAttribute,控制台会显示以下消息:

The attribute was not found.

检索应用于同一范围的多个属性实例

在上一示例中,要检查的类和要查找的特定特性都传递给 GetCustomAttribute 方法。 此代码非常适用于只有一个属性实例在类一级应用的情况。 不过,如果在相同类级别应用一个特性的多个实例,GetCustomAttribute 方法不会检索所有信息。 如果同一特性的多个实例应用于相同范围,可以使用 Attribute.GetCustomAttributes 方法将特性的所有实例都放入一个数组中。 例如,如果在相同的类一级应用两个 DeveloperAttribute 实例,可以将 GetAttribute 方法修改为显示在这两个属性中找到的信息。 请记住,在同一级别应用多个特性。 必须在 AttributeUsageAttribute 类中将 AllowMultiple 属性设置为 true 来定义该特性。

下面的代码示例展示了如何使用 GetCustomAttributes 方法来创建数组,以引用任何给定类中的所有 DeveloperAttribute 实例。 然后,代码会将所有特性的值输出到控制台。

public static void GetAttribute(Type t)
{
    DeveloperAttribute[] MyAttributes =
        (DeveloperAttribute[]) Attribute.GetCustomAttributes(t, typeof (DeveloperAttribute));

    if (MyAttributes.Length == 0)
    {
        Console.WriteLine("The attribute was not found.");
    }
    else
    {
        for (int i = 0 ; i < MyAttributes.Length ; i++)
        {
            // Get the Name value.
            Console.WriteLine("The Name Attribute is: {0}." , MyAttributes[i].Name);
            // Get the Level value.
            Console.WriteLine("The Level Attribute is: {0}." , MyAttributes[i].Level);
            // Get the Reviewed value.
            Console.WriteLine("The Reviewed Attribute is: {0}.", MyAttributes[i].Reviewed);
        }
    }
}

如果找不到任何属性,此代码会向用户发出警报。 如果找到,就会显示两个 DeveloperAttribute 实例中包含的信息。

检索应用于不同范围的多个属性实例

GetCustomAttributes 和 GetCustomAttribute 方法不会搜索整个类,也不会返回该类中特性的所有实例。 而是一次只搜索一个指定方法或成员。 如果你有一个类将同一特性应用于每个成员,并且你需要检索应用于这些成员的所有特性的值,则必须将各个方法或成员单独提供给 GetCustomAttributes 和 GetCustomAttribute。

下面的代码示例将一个类用作参数,并在类级别以及该类的每一个方法上搜索 DeveloperAttribute(如前面所定义):

public static void GetAttribute(Type t)
{
    DeveloperAttribute att;

    // Get the class-level attributes.

    // Put the instance of the attribute on the class level in the att object.
    att = (DeveloperAttribute) Attribute.GetCustomAttribute (t, typeof (DeveloperAttribute));

    if (att == null)
    {
        Console.WriteLine("No attribute in class {0}.\n", t.ToString());
    }
    else
    {
        Console.WriteLine("The Name Attribute on the class level is: {0}.", att.Name);
        Console.WriteLine("The Level Attribute on the class level is: {0}.", att.Level);
        Console.WriteLine("The Reviewed Attribute on the class level is: {0}.\n", att.Reviewed);
    }

    // Get the method-level attributes.

    // Get all methods in this class, and put them
    // in an array of System.Reflection.MemberInfo objects.
    MemberInfo[] MyMemberInfo = t.GetMethods();

    // Loop through all methods in this class that are in the
    // MyMemberInfo array.
    for (int i = 0; i < MyMemberInfo.Length; i++)
    {
        att = (DeveloperAttribute) Attribute.GetCustomAttribute(MyMemberInfo[i], typeof (DeveloperAttribute));
        if (att == null)
        {
            Console.WriteLine("No attribute in member function {0}.\n" , MyMemberInfo[i].ToString());
        }
        else
        {
            Console.WriteLine("The Name Attribute for the {0} member is: {1}.",
                MyMemberInfo[i].ToString(), att.Name);
            Console.WriteLine("The Level Attribute for the {0} member is: {1}.",
                MyMemberInfo[i].ToString(), att.Level);
            Console.WriteLine("The Reviewed Attribute for the {0} member is: {1}.\n",
                MyMemberInfo[i].ToString(), att.Reviewed);
        }
    }
}

如果在方法级别或类级别找不到 DeveloperAttribute 实例,GetAttribute 方法会通知用户找不到特性,并显示不包含该特性的方法名称或类名称。 如果找到特性,控制台会显示 Name、Level 和 Reviewed 字段。

可以使用 Type 类的成员,在传递的类中获取各个方法和成员。 此示例先查询 Type 对象,以获取类级别的特性信息。 接下来,它使用 Type.GetMethods 将所有方法实例都放入 System.Reflection.MemberInfo 对象数组,以检索方法一级的属性信息。 还可以使用 Type.GetProperties 方法检查属性一级的属性,或使用 Type.GetConstructors方法检查构造函数一级的属性。

posted @ 2024-10-18 11:28  请明月  阅读(27)  评论(0编辑  收藏  举报