C# 的特性 Attribute(学习心得 28)

特性(Attribute):用于,在运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)的 行为信息 的 声明性标签。

您可以通过使用 特性 向程序添加 声明性信息 。

一个 声明性标签 是通过放置在它所应用的元素前面的方括号([ ])来描述的。

特性(Attribute)用于添加 元数据,如编译器指令和注释、描述、方法、类等其他信息。

.Net 框架提供了两种类型的特性:预定义特性和自定义特性。

一、规定特性(Attribute)

语法:

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

特性(Attribute)的名称和值是在方括号内规定的。

放置在它所应用的元素之前。

positional_parameters 规定必需的信息。

name_parameter 规定可选的信息。

二、预定义特性(Attribute)

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

  • AttributeUsage
  • Conditional
  • Obsolete

2.1 AttributeUsage

预定义特性 AttributeUsage 描述了如何使用一个 自定义特性 类。

它规定了特性可应用到的项目的类型。

语法:

[AttributeUsage(
   validon,
   AllowMultiple=allowmultiple,
   Inherited=inherited
)]

参数:

  • 参数 validon 规定特性可被放置的语言元素。它是枚举器 AttributeTargets 的值的组合。默认值是 AttributeTargets.All
  • 参数 allowmultiple(可选的)为该特性的 AllowMultiple 属性(property)提供一个布尔值。如果为 true,则该特性是多用的。默认值是 false(单用的)。
  • 参数 inherited(可选的)为该特性的 Inherited 属性(property)提供一个布尔值。如果为 true,则该特性可被派生类继承。默认值是 false(不被继承)。

例:

[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property, 
AllowMultiple = true)]

2.2 Conditional

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

它会引起方法调用的条件编译,取决于指定的值,比如 DebugTrace

例如,当调试代码时显示变量的值。

语法:

[Conditional(
   conditionalSymbol
)]

例:

[Conditional("DEBUG")]

例:演示该特性

#define D1 // 这里就是选择开关,选择了运行 D1
using System;
using System.Diagnostics;
namespace helloworld
{
    public class Myclass
    {
        [Conditional("D1")] // 仅在 D1 情况下运行
        public static void M1(string msg)
        {
            Console.WriteLine(msg);
        }

        [Conditional("D2")] // 仅在 D2 情况下运行
        public static void M2(string msg)
        {
            Console.WriteLine(msg);
        }
    
        static void Main()
        {

            Myclass.M1("In Function 1.");
            Myclass.M2("In Function 2.");
            Console.ReadKey();
        }
    }
}

运行结果:

In Function 1.

这里显示,只有 D1 条件下的方法被运行了。

如果我们改成 #define D2,则 D2 条件下的被运行。

总结:

使用Conditional是封闭#if和#endif内部方法的替代方法,它更整洁,减少了出错的机会。

这里有个注意点,那就是如果设定 [Conditional("DEBUG")] 这里用的是 “DEBUG”,那么在 Visual Studio 中会出现奇怪的情况,那就是即使你定义 #define D1,那 “DEBUG” 下的方法依然会运行。

从官网文档查询到,那是因为:

在 Visual Studio C#和 Visual Basic 项目中,默认情况下,为调试版本定义 “debug” 条件编译符号,同时为调试版本和发布版本定义 “TRACE” 符号。

也就是说,我们在 Visual Basic 运行程序本身就已经定义了 #define DEBUG 这个前提,不管你怎么设定 #define,这个前提一直在。同时,官方文档也给出了禁用的方法,需要用的时候可以去查阅。

2.3 Obsolete

这个预定义特性标记了不应被使用的程序实体。

它可以让您通知编译器丢弃某个特定的目标元素。

例如,当一个新方法被用在一个类中,但是您仍然想要保持类中的旧方法,您可以通过显示一个应该使用新方法,而不是旧方法的消息,来把它标记为 obsolete(过时的)。

语法:

[Obsolete(
   message
)]
[Obsolete(
   message,
   iserror
)]

参数:

  • message,是一个字符串,描述项目为什么过时以及该替代使用什么。
  • iserror,是一个布尔值。如果该值为 true,编译器应把该项目的使用当作一个错误。默认值是 false(编译器生成一个警告)。

例:

using System;
public class MyClass
{
   [Obsolete("Don't use OldMethod, use NewMethod instead", true)]
   static void OldMethod()
   {
      Console.WriteLine("It is the old method");
   }
   static void NewMethod()
   {
      Console.WriteLine("It is the new method");
   }
   public static void Main()
   {
      OldMethod();
   }
}

运行结果:

Don't use OldMethod, use NewMethod instead

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

.Net 框架允许创建自定义特性,用于存储声明性的信息,且可在运行时被检索。

该信息根据设计标准和应用程序需要,可与任何目标元素相关。

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

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

最后一个步骤,包含编写一个简单的程序,来读取元数据以便查找各种符号。

元数据是用于描述其他数据的数据和信息。

该程序应使用反射来在运行时访问特性。

3.1 声明自定义特性

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

例:声明了一个名为 DeBugInfo 的自定义特性

// 一个自定义特性 BugFix 被赋给类及其成员
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple = true)]

public class DeBugInfo : System.Attribute

3.2 构建自定义特性

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

存储下面的信息:

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

DeBugInfo 类将带有三个用于存储前三个信息的私有属性(property)和一个用于存储消息的公有属性(property)。

所以 bug 编号、开发人员名字和审查日期将是 DeBugInfo 类的必需的定位( positional)参数,消息将是一个可选的命名(named)参数。

每个特性必须至少有一个构造函数。

必需的定位( positional)参数应通过构造函数传递。

程序:

// 一个自定义特性 BugFix 被赋给类及其成员
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple = true)]

public class DeBugInfo : System.Attribute
{
  private int bugNo;
  private string developer;
  private string lastReview;
  public string message;

  // 构造函数
  public DeBugInfo(int bg, string dev, string d)
  {
      this.bugNo = bg;
      this.developer = dev;
      this.lastReview = d;
  }

  public int BugNo
  {
      get
      {
          return bugNo;
      }
  }
  public string Developer
  {
      get
      {
          return developer;
      }
  }
  public string LastReview
  {
      get
      {
          return lastReview;
      }
  }
  public string Message
  {
      get
      {
          return message;
      }
      set
      {
          message = value;
      }
  }
}

3.3 应用自定义特性

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

例:

[DeBugInfo(45, "Zara Ali", "12/8/2012", Message = "Return type mismatch")]
[DeBugInfo(49, "Nuha Ali", "10/10/2012", Message = "Unused variable")]
class Rectangle
{
  // 成员变量
  protected double length;
  protected double width;
  public Rectangle(double l, double w)
  {
      length = l;
      width = w;
  }
  [DeBugInfo(55, "Zara Ali", "19/10/2012",
  Message = "Return type mismatch")]
  public double GetArea()
  {
      return length * width;
  }
  [DeBugInfo(56, "Zara Ali", "19/10/2012")]
  public void Display()
  {
      Console.WriteLine("Length: {0}", length);
      Console.WriteLine("Width: {0}", width);
      Console.WriteLine("Area: {0}", GetArea());
  }
}

我们可以使用反射 Reflection 类对象来检索这些信息。

例:

using System;
// 1. 创建一个自定义特性
// 描述如何使用一个自定义特性 SomethingAttribute
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = true)]    

//********自定义特性SomethingAttribute**************//
public class SomethingAttribute : Attribute    
{
    private string name; // 名字
    private string data; // 日期
    public string Name // 属性
    {
        get { return name; }
        set { name = value; }
    }
    public string Data // 属性
    {
        get { return data; }
        set { data = value; }
    }
    
  	// 构造函数传递必须的定位参数
  	public SomethingAttribute(string name)
    {
        this.name = name;
        this.name = name;
    }
}

// 2. 实例化自定义特性
[Something("Amy", Data = "2018-06-18")]
[Something("Jack", Data = "2018-06-18")]
class Test{}

// 3. 获取自定义特性的中的变量(该部分待验证)
Type t = typeof(Test);
var something = t.GetCustomAttributes(typeof(SomethingAttribute),true);
foreach(SomethingAttribute each in something)
{
    Console.WriteLine("Name:{0}", each.Name);
    Console.WriteLine("Data:{0}", each.Data);
}