Skip to content

C# - 特性

特性(attribute)是一种声明式标签,用于向你的 C# 代码添加元数据。这些元数据可以向 C# 编译器、.NET 运行时或其他工具和框架传达关于类、方法、属性(property)和程序集(assembly)等元素行为的信息。特性用方括号 [] 括起来,并直接放置在它们所应用的 C# 代码元素上方。

可以将特性视为可以附加到代码上的标签。这些标签本身不会改变代码的执行方式,但它们提供了可以被其他系统读取和操作的上下文信息。.NET 平台包含了许多预定义的特性,也允许你创建自己的自定义特性。

应用特性的语法包括其名称、可选的位置参数(positional parameters)和可选的命名参数(named parameters)。

[AttributeName(PositionalParameter, NamedParameter = value)]
public class MyClass { /*...*/ }
  • AttributeName:按照惯例,特性类名以 Attribute 结尾(例如 ObsoleteAttribute),但在应用时可以省略后缀(如 [Obsolete])。
  • Positional Parameters:这些参数对应于特性构造函数的参数,并且是必需的。
  • Named Parameters:这些参数对应于特性类的公共属性(public properties),并且是可选的。

.NET 提供了一套丰富的内置特性。以下是一些最基本的特性。

[Obsolete] 特性将代码元素标记为已过时。它通过引导开发者使用新的替代方案来帮助管理 API 演进。当使用过时代码时,编译器会生成警告或错误。

public class LegacyApiClient
{
[Obsolete("This method is deprecated. Please use SendRequestAsync instead.", isError: false)]
public void SendRequest()
{
// ... 旧的实现
}
public Task SendRequestAsync()
{
// ... 新的实现
Console.WriteLine("Using the new and improved SendRequestAsync method!");
return Task.CompletedTask;
}
}
// 用法:
var client = new LegacyApiClient();
client.SendRequest(); // 触发编译器警告

[Conditional] 特性根据是否定义了特定的预处理符号来包含或排除对方法的调用。它通常用于创建仅在 DEBUG 构建中运行的日志记录或调试辅助工具。

#define DEBUG // 通常在项目设置中为 Debug 配置定义
using System;
using System.Diagnostics;
public class Logger
{
[Conditional("DEBUG")]
public static void Log(string message)
{
Console.WriteLine($"[DEBUG] {message}");
}
}
public class Program
{
public static void Main()
{
Logger.Log("Starting application..."); // 只有在 DEBUG 定义时,此行才会被编译
Console.WriteLine("Application running.");
}
}
// 如果移除 '#define DEBUG' 并重新编译,输出将只包含 "Application running."

C# 5 引入的这些特性允许方法捕获关于其调用者的信息,例如方法名称、文件路径和行号。它们对于日志记录、诊断以及实现 INotifyPropertyChanged 非常有用。

using System;
using System.Runtime.CompilerServices;
public class DiagnosticsHelper
{
public static void Trace(
string message,
[CallerMemberName] string memberName = "",
[CallerFilePath] string filePath = "",
[CallerLineNumber] int lineNumber = 0)
{
Console.WriteLine($"Message: {message}");
Console.WriteLine($" -> Called from: {memberName}");
Console.WriteLine($" -> In file: {filePath}");
Console.WriteLine($" -> At line: {lineNumber}");
}
}
public class Program
{
public static void Main()
{
DiagnosticsHelper.Trace("Something interesting happened.");
}
}

你可以通过定义一个继承自 System.Attribute 的类来创建自己的特性。这是一种为应用程序的领域或框架添加特定自定义元数据的强大方法。

该过程涉及几个关键步骤:

  1. 声明特性类:创建一个继承自 System.Attribute 的类。
  2. 使用 [AttributeUsage] 指定目标:在你的自定义特性类上使用 [AttributeUsage] 特性来控制它可以应用于何处(例如,类、方法、属性)。
  3. 定义构造函数和属性:构造函数定义位置参数,公共属性定义命名参数。
  4. 在运行时访问特性:使用反射(reflection)检查代码元素并检索自定义特性的实例。

让我们创建一个特性来跟踪代码中的 bug 修复。它将存储一个 ID、描述以及修复状态。

using System;
// 2. 指定此特性可以在哪里使用。
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
// 1. 声明特性类。
public class BugFixAttribute : Attribute
{
// 3. 为命名参数和位置参数定义属性。
public int BugId { get; }
public string Description { get; }
public bool IsFixed { get; set; } = false; // 具有默认值的命名参数
// 位置参数的构造函数。
public BugFixAttribute(int bugId, string description)
{
BugId = bugId;
Description = description;
}
}
// 应用自定义特性
[BugFix(101, "User login fails with special characters")]
[BugFix(102, "Data export is too slow", IsFixed = true)]
public class UserAuthentication
{
[BugFix(103, "Password reset link expires immediately", IsFixed = true)]
public void Authenticate(string username, string password)
{
// ... 方法实现
}
}

应用特性只是故事的一半。要使其有用,你需要编写代码在运行时使用反射(reflection)读取这些元数据。

using System;
using System.Reflection;
public class AttributeReader
{
public static void Main()
{
// 获取 UserAuthentication 类的 Type 对象
Type type = typeof(UserAuthentication);
// 获取类上的所有 BugFix 特性
foreach (var attr in type.GetCustomAttributes(typeof(BugFixAttribute), false))
{
if (attr is BugFixAttribute bugFix)
{
Console.WriteLine($"Class-level Bug #{bugFix.BugId}: {bugFix.Description} (Fixed: {bugFix.IsFixed})");
}
}
// 获取 Authenticate 方法上的特性
MethodInfo? method = type.GetMethod("Authenticate");
if (method != null)
{
foreach (var attr in method.GetCustomAttributes(typeof(BugFixAttribute), false))
{
if (attr is BugFixAttribute bugFix)
{
Console.WriteLine($"Method-level Bug #{bugFix.BugId}: {bugFix.Description} (Fixed: {bugFix.IsFixed})");
}
}
}
}
}
// Output:
// Class-level Bug #101: User login fails with special characters (Fixed: False)
// Class-level Bug #102: Data export is too slow (Fixed: True)
// Method-level Bug #103: Password reset link expires immediately (Fixed: True)

特性是现代 .NET 开发的基础,并被广泛应用于:

  • ASP.NET Core:用于路由([HttpGet("users/{id}")])、授权([Authorize])和模型绑定([FromBody])。
  • Entity Framework Core:用于定义数据模型,例如主键([Key])和关系([ForeignKey])。
  • 序列化:用于控制对象如何转换为/从 JSON 或 XML([JsonPropertyName("user_name")]、[XmlIgnore])。
  • 单元测试:xUnit 和 NUnit 等框架使用特性来标识测试([Fact]、[Test])和设置测试条件([Theory]、[SetUp])。