C#基础之特性讲解

1 特性

1.1 简介

1.1.1 定义

特性(Attribute)是用于在运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)的行为信息的声明性标签。通过使用特性向程序添加声明性信息。一个声明性标签是通过放置在它所应用的元素前面的方括号([ ])来描述的。
特性(Attribute)用于添加元数据,如编译器指令和注释、描述、方法、类等其他信息。.Net 框架提供了两种类型的特性:预定义特性和自定义特性。

特性(Attribute) 是一种元数据机制,允许开发者将额外的信息嵌入到代码中,如类、方法、属性等。特性提供了一种声明性编程的方式,便于在运行时或编译时通过反射等手段获取和使用这些信息。

1.1.2 特性的定义

特性是从 System.Attribute 派生的类。可以通过在目标元素上添加特性,附加额外信息。

常见语法

[AttributeName]
[AttributeName(Parameter1, Parameter2)]
public class MyClass
{
    [AttributeName]
    public void MyMethod() { }
}

1.1.3 与java注解区别

C#中的特性(Attribute)与Java中的注解(Annotation)在功能和用途上确实存在相似之处,但也有一些区别。

  • 相似之处
    • 元数据描述:
      C#中的特性和Java中的注解都用于为代码(如类、方法、属性等)添加元数据或声明性信息。
      这些信息可以在运行时通过反射技术进行查询和使用。
    • 自定义:
      在C#和Java中,都可以自定义特性和注解。
      自定义的特性和注解可以用于特定的应用场景,如验证、序列化、日志记录等。
    • 语法结构:
      在C#中,特性通常使用方括号[]包围,并放在所修饰的元素之前。
      在Java中,注解也使用@符号引导,并放在所修饰的元素之前。
  • 区别之处
    • 继承与派生:
      在C#中,特性是从Attribute类派生的。要创建一个新的特性,需要定义一个从Attribute类派生的类。
      Java中的注解则是一个独立的类型,不需要从任何类派生。
    • 使用方式:
      C#中的特性可以应用于程序集、模块、类、方法、属性等,并提供了多种使用方式,如属性、方法等。
      Java中的注解也可以应用于包、类、字段、方法、局部变量、方法参数等,但使用方式可能因注解的具体类型和用途而有所不同。
    • 内置特性与注解:
      C#和Java都提供了一些内置的特性和注解,用于实现特定的功能,如序列化、安全性等。
      这些内置的特性和注解在各自的编程环境中都有广泛的应用。
    • 反射机制:
      在C#中,可以使用反射来查询和使用特性。反射提供了一种在运行时检查和操作代码结构的方法。
      Java中的注解也可以通过反射进行查询和使用,但具体的反射机制和API可能与C#有所不同。

1.2 内置特性

1.2.1 常见内置特性

常见内置特性:

特性 作用
[Obsolete] 标记代码已过时,编译时提示警告或错误
[Serializable] 指定类可以序列化
[DllImport] 用于调用非托管代码的 DLL 函数
[Conditional] 条件编译,特定条件下执行代码(如 DEBUG 模式)
[Required] 标记某个属性在模型绑定时是必需的(常用于 ASP.NET)

1.2.2 预定义特性

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

1.2.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)]

1.2.2.2 Conditional

这个预定义特性标记了一个条件方法,其执行依赖于指定的预处理标识符。
它会引起方法调用的条件编译,取决于指定的值,比如 DebugTrace。例如,当调试代码时显示变量的值。
规定该特性的语法如下:

[Conditional(
   conditionalSymbol
)]

例如:[Conditional("DEBUG")]

1.2.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

1.3 自定义特性

.Net 框架允许创建自定义特性,用于存储声明性的信息,且可在运行时被检索。该信息根据设计标准和应用程序需要,可与任何目标元素相关。自定义特性只需要继承 Attribute 类即可,约定特性类名称以 Attribute 结尾。要使用特性时,既可以使用特性类名,也可以使用 去掉末尾 Attribute 后的名称。

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

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

1.3.1 声明自定义特性

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

[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple = true)]
public class DeBugInfo : System.Attribute

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

1.3.2 构建自定义特性

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

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

DeBugInfo 类将带有三个用于存储前三个信息的私有属性(property)和一个用于存储消息的公有属性(property)。所以 bug 编号、开发人员名字和审查日期将是 DeBugInfo 类的必需的定位( positional)参数,消息将是一个可选的命名(named)参数。
每个特性必须至少有一个构造函数。必需的定位( positional)参数应通过构造函数传递。下面的代码演示了 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;
      }
  }
}

1.3.3 应用自定义特性

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

[DeBugInfo(45, "Zara Ali", "12/8/2022", Message = "Return type mismatch")]
[DeBugInfo(49, "Nuha Ali", "10/10/2022", 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());
  }
}

1.3.4 通过反射访问特性

使用反射(Reflection)来读取 Rectangle 类中的元数据。

using System;
using System.Reflection;
namespace BugFixApplication
{
   [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());
      }
   }//end class Rectangle  
   
   class ExecuteRectangle
   {
      static void Main(string[] args)
      {
         Rectangle r = new Rectangle(4.5, 7.5);
         r.Display();
         Type type = typeof(Rectangle);
         // 遍历 Rectangle 类的特性
         foreach (Object attributes in type.GetCustomAttributes(false))
         {
            DeBugInfo dbi = (DeBugInfo)attributes;
            if (null != dbi)
            {
               Console.WriteLine("Bug no: {0}", dbi.BugNo);
               Console.WriteLine("Developer: {0}", dbi.Developer);
               Console.WriteLine("Last Reviewed: {0}",
                                        dbi.LastReview);
               Console.WriteLine("Remarks: {0}", dbi.Message);
            }
         }
         
         // 遍历方法特性
         foreach (MethodInfo m in type.GetMethods())
         {
            foreach (Attribute a in m.GetCustomAttributes(true))
            {
               DeBugInfo dbi = (DeBugInfo)a;
               if (null != dbi)
               {
                  Console.WriteLine("Bug no: {0}, for Method: {1}",
                                                dbi.BugNo, m.Name);
                  Console.WriteLine("Developer: {0}", dbi.Developer);
                  Console.WriteLine("Last Reviewed: {0}",
                                                dbi.LastReview);
                  Console.WriteLine("Remarks: {0}", dbi.Message);
               }
            }
         }
         Console.ReadLine();
      }
   }
}
posted @ 2024-12-01 17:19  上善若泪  阅读(1)  评论(0编辑  收藏  举报