C# 特性

特性(Attribute)是用于在运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)的行为信息的声明性标签。您可以通过使用特性向程序添加声明性信息。一个声明性标签是通过放置在它所应用的元素前面的方括号([ ])来描述的。

特性(Attribute)用于添加元数据,如编译器指令和注释、描述、方法、类等其他信息。.Net 框架提供了两种类型的特性:预定义特性和自定义特性。

一、规定特性(Attribute)

规定特性(Attribute)的语法如下:

  1. [attribute(positional_parameters, name_parameter = value, ...)]
  2. element

特性(Attribute)的名称和值是在方括号内规定的,放置在它所应用的元素之前。positional_parameters 规定必需的信息,name_parameter 规定可选的信息。

二、预定义特性(Attribute)

.Net 框架提供了三种预定义特性:

  1. AttributeUsage
  2. Conditional
  3. Obsolete

2.1 AttributeUsage

预定义特性 AttributeUsage 描述了如何使用一个自定义特性类。它规定了特性可应用到的项目的类型。

规定该特性的语法如下:

  1. [AttributeUsage(
  2. validon,
  3. AllowMultiple=allowmultiple,
  4. Inherited=inherited
  5. )]

其中:

  • 参数 validon 规定特性可被放置的语言元素(规定当前特性适用的范围,比如适用于类、构造函数、字段、方法、属性)。它是枚举器 AttributeTargets 的值的组合。默认值是 AttributeTargets.All

  • 参数 allowmultiple(可选的)为该特性的 AllowMultiple 属性(property)提供一个布尔值。如果为 true,则该特性是多用的(在同一个类上多次调用)。默认值是 false(单用的)。

  • 参数 inherited(可选的)为该特性的 Inherited 属性(property)提供一个布尔值。如果为 true,则该特性可被派生类继承(当此特性的类被继承以后,该特性也被继承)。默认值是 false(不被继承)。

例如:

  1. [AttributeUsage(AttributeTargets.Class |
  2. AttributeTargets.Constructor |
  3. AttributeTargets.Field |
  4. AttributeTargets.Method |
  5. AttributeTargets.Property,
  6. AllowMultiple = true),
  7. Inherited = false]

2.2 Conditional

这个预定义特性标记了一个条件方法,其执行依赖于指定的预处理标识符。

它会引起方法调用的条件编译,取决于指定的值,比如 DebugTrace。例如,当调试代码时显示变量的值。

规定该特性的语法如下:

  1. [Conditional(
  2. conditionalSymbol
  3. )]

例如:

  1. [Conditional("DEBUG")]

下面的实例演示了该特性:

  1. #define DEBUG
  2. using System;
  3. using System.Diagnostics;
  4. public class Myclass
  5. {
  6. [Conditional("DEBUG")]
  7. public static void Message(string msg)
  8. {
  9. Console.WriteLine(msg);
  10. }
  11. }
  12. class Test
  13. {
  14. static void function1()
  15. {
  16. Myclass.Message("In Function 1.");
  17. function2();
  18. }
  19. static void function2()
  20. {
  21. Myclass.Message("In Function 2.");
  22. }
  23. public static void Main()
  24. {
  25. Myclass.Message("In Main function.");
  26. function1();
  27. Console.ReadKey();
  28. }
  29. }

当上面的代码被编译和执行时,在Debug模式下它会产生下列结果,在Release模式下则不会有任何结果:

  1. In Main function
  2. In Function 1
  3. In Function 2

此时,可以通过预处理指令在Release模式下也可以实现DEBUG的输出:

  1. #define DEBUG

2.3 Obsolete

这个预定义特性标记了不应被使用的程序实体。它可以让您通知编译器丢弃某个特定的目标元素。例如,当一个新方法被用在一个类中,但是您仍然想要保持类中的旧方法,您可以通过显示一个应该使用新方法,而不是旧方法的消息,来把它标记为 obsolete过时的)。

规定该特性的语法如下:

  1. [Obsolete( message )]
  2. [Obsolete( message, iserror )]

其中:

  • 参数 message,是一个字符串,描述项目为什么过时的原因以及该替代使用什么。

  • 参数 iserror,是一个布尔值。如果该值为 true,编译器应把该项目的使用当作一个错误。默认值是 false(编译器生成一个警告)。

下面的实例演示了该特性:

  1. using System;
  2. public class MyClass
  3. {
  4. [Obsolete("Don't use OldMethod, use NewMethod instead", true)]
  5. static void OldMethod()
  6. {
  7. Console.WriteLine("It is the old method");
  8. }
  9. static void NewMethod()
  10. {
  11. Console.WriteLine("It is the new method");
  12. }
  13. public static void Main()
  14. {
  15. OldMethod();
  16. }
  17. }

当您尝试编译该程序时,编译器会给出一个错误消息说明:

  1. Don't use OldMethod, use NewMethod instead

三、创建自定义特性(Attribute)

.Net 框架允许创建自定义特性,用于存储声明性的信息,且可在运行时被检索。该信息根据设计标准和应用程序需要,可与任何目标元素相关。

创建并使用自定义特性包含四个步骤:

  1. 声明自定义特性
  2. 构建自定义特性
  3. 在目标程序元素上应用自定义特性
  4. 通过反射访问特性

最后一个步骤包含编写一个简单的程序来读取元数据以便查找各种符号。元数据是用于描述其他数据的数据和信息。该程序应使用反射来在运行时访问特性。我们将在下一章详细讨论这点。

3.1 声明自定义特性

一个新的自定义特性应派生自 System.Attribute 类。例如:

  1. // 一个自定义特性 BugFix 被赋给类及其成员
  2. [AttributeUsage(AttributeTargets.Class |
  3. AttributeTargets.Constructor |
  4. AttributeTargets.Field |
  5. AttributeTargets.Method |
  6. AttributeTargets.Property,
  7. AllowMultiple = true)]
  8. public class DeBugInfo : System.Attribute

在上面的代码中,我们已经声明了一个名为 DeBugInfo 的自定义特性。

3.2 构建自定义特性

让我们构建一个名为 DeBugInfo 的自定义特性,该特性将存储调试程序获得的信息。它存储下面的信息:

  1. bug 的代码编号
  2. 辨认该 bug 的开发人员名字
  3. 最后一次审查该代码的日期
  4. 一个存储了开发人员标记的字符串消息

我们的 DeBugInfo 类将带有三个用于存储前三个信息的私有属性(property)和一个用于存储消息的公有属性(property)。所以 bug 编号、开发人员名字和审查日期将是 DeBugInfo 类的必需的定位(positional)参数,消息将是一个可选的命名(named)参数。

每个特性必须至少有一个构造函数。必需的定位(positional)参数应通过构造函数传递。下面的代码演示了 DeBugInfo 类:

  1. // 一个自定义特性 BugFix 被赋给类及其成员
  2. [AttributeUsage(AttributeTargets.Class |
  3. AttributeTargets.Constructor |
  4. AttributeTargets.Field |
  5. AttributeTargets.Method |
  6. AttributeTargets.Property,
  7. AllowMultiple = true)]
  8. public class DeBugInfo : System.Attribute
  9. {
  10. private int bugNo;
  11. private string developer;
  12. private string lastReview;
  13. private string message;
  14. public DeBugInfo(int bg, string dev, string d)
  15. {
  16. this.bugNo = bg;
  17. this.developer = dev;
  18. this.lastReview = d;
  19. }
  20. public int BugNo
  21. {
  22. get
  23. {
  24. return bugNo;
  25. }
  26. }
  27. public string Developer
  28. {
  29. get
  30. {
  31. return developer;
  32. }
  33. }
  34. public string LastReview
  35. {
  36. get
  37. {
  38. return lastReview;
  39. }
  40. }
  41. public string Message
  42. {
  43. get
  44. {
  45. return message;
  46. }
  47. set
  48. {
  49. message = value;
  50. }
  51. }
  52. }

3.3 应用自定义特性

通过把特性放置在紧接着它的目标之前,来应用该特性:

  1. [DeBugInfo(45, "Zara Ali", "12/8/2012", Message = "Return type mismatch")]
  2. [DeBugInfo(49, "Nuha Ali", "10/10/2012", Message = "Unused variable")]
  3. class Rectangle
  4. {
  5. // 成员变量
  6. protected double length;
  7. protected double width;
  8. public Rectangle(double l, double w)
  9. {
  10. length = l;
  11. width = w;
  12. }
  13. [DeBugInfo(55, "Zara Ali", "19/10/2012",
  14. Message = "Return type mismatch")]
  15. public double GetArea()
  16. {
  17. return length * width;
  18. }
  19. [DeBugInfo(56, "Zara Ali", "19/10/2012")]
  20. public void Display()
  21. {
  22. Console.WriteLine("Length: {0}", length);
  23. Console.WriteLine("Width: {0}", width);
  24. Console.WriteLine("Area: {0}", GetArea());
  25. }
  26. }

在下一章中,我们将使用 Reflection 类对象来检索这些信息。

🔚