深入理解 C# 特性(Attribute):概念、实现与实战

在 C# 开发中,"特性(Attribute) "是一个高频出现、却极易与"属性(Property) "混淆的概念。

特性并不参与业务逻辑的直接执行,而是作为元数据扩展机制 ,为代码元素附加"说明信息",并在运行时通过反射读取,是 ORM、序列化、验证框架、AOP 等体系的核心基础。

本文将从 概念本质、内置特性、自定义特性、反射读取、实战应用与设计思想 六个层面,系统、深入地解析 C# 特性。

一、特性的核心概念:元数据的"声明式说明"

1. 什么是特性(Attribute)

特性本质上是一个继承自 System.Attribute 的特殊类。

它的作用不是"执行代码",而是:

在编译期写入程序集元数据,在运行期通过反射读取的声明式信息。

关键特征:

  • ✔ 编译期生成,存储在程序集元数据中
  • ✔ 运行期只读,无法被修改
  • ❌ 不会自动生效、不包含业务逻辑
  • ✔ 必须配合反射或框架解析才有意义

Attribute = 描述规则
Reflection / Framework = 执行规则

2. 特性(Attribute) vs 属性(Property)

这是初学者最容易混淆的地方,两者完全不是一个维度的概念

对比维度 特性(Attribute) 属性(Property)
本质 继承自 Attribute 的类 类的成员
作用 为代码元素附加元数据 封装字段、控制访问
存储位置 程序集元数据 对象实例 / 静态内存
使用方式 [Attribute] 标注 obj.Property
是否参与逻辑 ❌ 否 ✔ 是
应用场景 ORM、验证、序列化、AOP 封装状态、校验数据

一句话总结:

Property 是程序运行的一部分
Attribute 是程序"描述信息"的一部分

3. 特性的使用形式

cs 复制代码
[Obsolete("该类已过时,请使用 NewClass")]
public class OldClass
{
}

说明:

  • 特性类命名通常以 Attribute 结尾
  • 使用时可以省略 Attribute 后缀(语法糖)
  • [Custom] 等价于 [CustomAttribute]

二、内置特性:.NET 提供的元数据能力

1. Obsolete:标记过时 API(最常用)

cs 复制代码
[Obsolete("该方法已废弃,请使用 NewMethod", false)]
public void OldMethod()
{
}

构造参数说明:

cs 复制代码
[Obsolete(string message, bool error)]
  • message:编译器提示信息
  • error:
    false:编译警告(默认)
    true:编译错误(禁止使用)

2. 常见内置特性一览

特性 作用
Serializable 标记类型可被序列化
Required 数据验证必填项
DisplayName UI 显示名称
DllImport P/Invoke 调用非托管代码
Conditional 条件编译方法

三、自定义特性:打造业务专属的元数据

1. 自定义特性的三大规则

1. 必须继承 System.Attribute
2. 建议使用 AttributeUsage 约束适用范围
3. 通过构造函数 / 属性定义元数据参数

2. 自定义特性的完整实现

cs 复制代码
using System;

// 自定义特性的使用规则配置
// AttributeTargets.Class | AttributeTargets.Method:该特性仅可应用于类或方法
// AllowMultiple = false:不允许在同一个目标上多次应用该特性
// Inherited = true:该特性可被派生类/重写方法继承
[AttributeUsage(
    AttributeTargets.Class | AttributeTargets.Method,
    AllowMultiple = false,
    Inherited = true)]
/// <summary>
/// 自定义描述特性,用于为类或方法添加元数据描述信息(描述、作者、创建时间)
/// </summary>
/// <remarks>
/// 特性说明:
/// 1. 必选参数:描述信息(通过构造函数传入)
/// 2. 可选参数:作者、创建时间(通过命名参数设置)
/// 3. 适用范围:类、方法(不可用于其他目标如字段、属性等)
/// 4. 继承性:派生类/重写方法会继承该特性
/// 5. 唯一性:同一目标仅可应用一次该特性
/// </remarks>
public class CustomDescriptionAttribute : Attribute
{
    /// <summary>
    /// 核心描述信息(必填)
    /// </summary>
    /// <value>目标对象的详细描述文本</value>
    public string Description { get; }

    /// <summary>
    /// 作者信息(可选,命名参数)
    /// </summary>
    /// <value>特性创建者/维护者的名称</value>
    public string Author { get; set; }

    /// <summary>
    /// 特性创建时间(可选,命名参数)
    /// </summary>
    /// <value>默认值为特性实例化时的当前系统时间</value>
    public DateTime CreateTime { get; set; } = DateTime.Now;

    /// <summary>
    /// 初始化 <see cref="CustomDescriptionAttribute"/> 类的新实例(必填构造函数)
    /// </summary>
    /// <param name="description">目标对象的核心描述信息,不可为空</param>
    /// <exception cref="ArgumentNullException">当description为null或空字符串时抛出</exception>
    public CustomDescriptionAttribute(string description)
    {
        // 可选:添加参数校验,确保必填参数有效
        if (string.IsNullOrWhiteSpace(description))
        {
            throw new ArgumentNullException(nameof(description), "描述信息不能为空");
        }
        
        Description = description;
    }
}

3. AttributeUsage 参数详解

参数 说明
ValidOn 可标注目标(Class / Method / Property 等)
AllowMultiple 是否允许重复标注
Inherited 是否可被派生类继承
⚠ 注意:
  • Inherited 对 方法 仅在 virtual / override 情况下生效
  • 对接口实现并不总是有效

4. 特性参数的底层规则(高频考点)

cs 复制代码
[MyAttr("必填", Level = 1)]
  • 构造函数参数:必须是编译期常量
  • 命名参数:必须是 public set 属性
  • 支持类型:
    基元类型、stringenum
    Type
    以上类型的数组
    ❌ 不允许:
cs 复制代码
[MyAttr(DateTime.Now)] // 非常量

四、反射:运行时读取特性元数据

1. 反射读取特性的标准流程

  1. 获取 Type / MemberInfo
  2. 调用 `GetCustomAttribute()``
  3. 解析特性实例中的元数据

2. 完整实战示例(推荐写法)

cs 复制代码
using System;
using System.Reflection;

// 自定义描述特性(标记类/方法的描述、作者等信息)
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomDescriptionAttribute : Attribute
{
    // 描述内容
    public string Description { get; set; }
    // 作者
    public string Author { get; set; }
    // 创建时间
    public DateTime CreateTime { get; set; }

    // 构造函数:初始化描述
    public CustomDescriptionAttribute(string description)
    {
        Description = description;
    }
}

// 标记类过时(指定替代类)
[Obsolete("MyClass 已过时,请使用 MyClassV2")]
// 给类添加自定义描述特性
[CustomDescription("示例业务类", Author = "开发者A")]
public class MyClass
{
    // 标记方法过时(指定替代方法)
    [Obsolete("请使用 NewMethod")]
    public void OldMethod() { }

    // 给方法添加自定义描述特性
    [CustomDescription("新业务方法", Author = "开发者B", CreateTime = new DateTime(2025, 1, 1))]
    public void NewMethod() { }
}

class Program
{
    static void Main()
    {
        // 获取MyClass类型信息
        Type type = typeof(MyClass);

        // 反射获取类的自定义描述特性
        var classAttr = type.GetCustomAttribute<CustomDescriptionAttribute>();
        // 输出类描述
        Console.WriteLine($"类描述:{classAttr?.Description}");

        // 获取NewMethod方法信息
        MethodInfo method = type.GetMethod("NewMethod");
        // 反射获取方法的自定义描述特性
        methodAttr = method.GetCustomAttribute<CustomDescriptionAttribute>();
        // 输出方法描述
        Console.WriteLine($"方法描述:{methodAttr?.Description}");
    }
}

五、AllowMultiple:多特性高级用法(权限 / AOP 基础)

cs 复制代码
using System;

/// <summary>
/// 权限标记特性
/// 用于标注方法所需的权限编码,支持为单个方法标记多个权限
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] // 特性仅作用于方法,允许同一方法多次标注
public class PermissionAttribute : Attribute
{
    /// <summary>
    /// 权限编码(如"Order.Read"表示订单读取权限)
    /// </summary>
    public string Code { get; }

    /// <summary>
    /// 初始化权限特性
    /// </summary>
    /// <param name="code">权限编码字符串</param>
    public PermissionAttribute(string code) => Code = code;
}

/// <summary>
/// 订单业务服务类
/// </summary>
public class OrderService
{
    /// <summary>
    /// 订单导出方法
    /// 需要同时具备订单读取(Order.Read)和订单导出(Order.Export)权限
    /// </summary>
    [Permission("Order.Read")]    // 标记该方法需要订单读取权限
    [Permission("Order.Export")]  // 标记该方法需要订单导出权限
    public void Export() { }
}

读取:

cs 复制代码
// 获取方法上所有权限特性
var permissions = methodInfo.GetCustomAttributes<PermissionAttribute>();

// 遍历输出各权限编码
foreach (var p in permissions)
{
    Console.WriteLine(p.Code);
}

👉 这是权限系统、拦截器、切面编程的基础形态

六、典型应用场景

1. 标记过时 API

  • Obsolete
  • 引导开发者升级接口

2. 数据验证

  • [Required][MaxLength]
  • ASP.NET Core 模型验证机制

3. ORM 映射

cs 复制代码
// 用户实体(映射User表)
[Table("User")]
public class User
{
    // 用户ID(映射user_id列)
    [Column("user_id")]
    public int Id { get; set; }
}

4. 序列化控制

  • [Serializable]
  • [JsonIgnore]

5. AOP / 框架设计

  • [Log]
  • [Transaction]
  • [Authorize]

七、性能与使用注意事项

  • ⚠ 反射有性能成本,高频场景应缓存结果
  • ⚠ Attribute 只读,运行期不可修改
  • ⚠ 必须继承 Attribute 才有效
  • ⚠ 不要把业务逻辑写进 Attribute

缓存示例:

cs 复制代码
static readonly Dictionary<MemberInfo, Attribute> _cache = new();

八、总结:Attribute 的设计思想

Attribute 并不"做事情",
它只负责"描述事情应该如何做"。

它是 C# 中声明式编程的核心工具:

  • 解耦规则与实现
  • 降低业务代码侵入性
  • 支撑框架级能力
    当你真正理解 Attribute 时,
    你已经从"写功能代码",迈入了:
相关推荐
WebRuntime2 小时前
所有64位WinForm应用都是Chromium浏览器(2)
javascript·c#·.net·web
Sunsets_Red4 小时前
待修改莫队与普通莫队优化
java·c++·python·学习·算法·数学建模·c#
时光追逐者4 小时前
一款基于 .NET 9 构建的企业级 Web RBAC 快速开发框架
前端·c#·.net·.net core
想你依然心痛4 小时前
【TextIn大模型加速器+火山引擎】打造智能文档处理流水线:从跨国药企手册到金融单据核验的全链路实战
金融·c#·火山引擎
kingwebo'sZone4 小时前
win11智能应用控制已阻止此应用
c#
baivfhpwxf20236 小时前
c# 删除文件夹里的所有文件
c#
easyboot6 小时前
python获取C#WEBAPI的数据
开发语言·python·c#
许泽宇的技术分享6 小时前
当AI遇见UI:用.NET Blazor实现Google A2UI协议的完整之旅
人工智能·ui·.net·blazor·a2ui
数据的世界016 小时前
C#权威指南第9课:类
c#