浅谈C#特性

nhb 发布于 26 天前 84 次阅读


特性

特性(Attrubute)

特性是用于在运行时传递程序的元素(类,方法,结构,枚举,组件等)的行为信息的声明性标签。通过使用方括号 ( [ ] ) 来添加声明性信息。

当使用特性后,其他人可以通过反射来读取这个标签,根据标签内容来做出反应。

接下来介绍.Net框架提供的两种类型的特性:预定义特性和自定义特性。

预定义特性

预定义特性分为三种:

AttributeUsage

预定义特性AttributeUsage规定如何使用自定义的特性,就像给特性加的特性。

规定语法如下:

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

参数列表:

  1. ValidOn(必选):指定可放置的语法元素(如类,方法,参数),它是枚举器AttributeTargets的值的组合,通过 | 来连接。默认为AttributeTargets.All。
  2. AllowMultiple(可选):是否允许在同一元素上使用多次,布尔值,默认为false(单用)。
  3. Inherited(可选):是否允许被派生类继承,布尔值,默认为false(不继承)。

使用示例:

using System;
// 1. 限定只能给方法贴标签
// 2. 允许一个方法贴多个该标签 (AllowMultiple = true)
// 3. 子类重写该方法时,不自动继承这个权限要求 (Inherited = false)
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
public class RequirePermissionAttribute : Attribute
{
    public string PermissionName { get; }
    public RequirePermissionAttribute(string permissionName)
    {
        PermissionName = permissionName;
    }
}
// --- 使用场景 ---
public class AdminPanel
{
    [RequirePermission("UserRead")]
    [RequirePermission("UserDelete")]
    public void DeleteUser() 
    {
        // 业务逻辑
    }
}

Conditional

Conditional特性的作用是条件编译控制。它能够根据是否定义了特定的预处理符号(如 DEBUG),来决定编译器是否应该包含对该方法的“调用”。

using System.Diagnostics;

public class Tool
{
    [Conditional("DEBUG")] // 只有定义了 DEBUG 符号,调用处才会被编译
    public static void ShowDebugInfo(string message)
    {
        Console.WriteLine($"DEBUG INFO: {message}");
    }
}

// 调用处
public void DoWork()
{
    // 在 Release 模式下,编译器会自动忽略下面这一行,就像它从未写过一样
    Tool.ShowDebugInfo("正在执行核心逻辑..."); 

    Console.WriteLine("工作完成");
}

对于Conditional特性执行时的逻辑:如果Method()附加了该特性,那么编译器会扫描所有代码,发现有调用 Method() 的地方,如果符号未定义,就把那行调用代码删掉。对于以下情况要注意:

  • 反射调用: 如你用 MethodInfo.Invoke 去执行,[Conditional] 是拦不住的,因为它是在编译期剔除静态调用的,拦截不了运行时的反射。
  • 委托(Delegate): 如果把该方法赋值给一个 Action 变量再执行,特性也会失效。
  • Main() : 是由 .NET 运行时(CLR) 直接启动的,而不是由代码在某处手动调用的。

Obsolete

这个预定义特性标记了不应被使用的程序实体。它可以让您通知编译器丢弃某个特定的目标元素。

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

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

创建自定义特性

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

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

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

声明自定义特性

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

public class DeBugInfo : System.Attribute

构建自定义特性

在这里我们声明一个DebugInfo的自定义特性。

// 一个自定义特性 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;
      }
  }
}

应用自定义特性

通过放置在它的目标之前来应用该特性:

[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());
  }
}

下一篇文章,将浅谈反射来使用这个信息。

最后更新于 2026-04-01