C# 特性详解

我们将系统性地介绍 C# 中的特性(Attribute)机制,并演示如何自定义一个特性、应用它,并通过反射在运行时读取和使用它。


在 C# 中,特性(Attribute)本质上是一种元数据(metadata),它本身不会自动执行任何逻辑或产生任何行为。它的作用是在编译时将额外的信息"附加"到程序元素上(如类、方法、属性、参数、程序集等),这些信息会被嵌入到程序集的元数据中。真正让特性"发挥作用"的,是运行时通过反射(Reflection)读取这些元数据,并根据其内容执行相应的逻辑。

一、什么是 C# 特性(Attribute)?

特性(Attribute)是 C# 提供的一种声明式编程机制 ,用于向程序元素(如类、方法、属性、字段、参数、程序集等)附加元数据(metadata)

这些元数据会被编译器写入程序集(.dll 或 .exe)中,在运行时可以通过 反射(Reflection) 读取。

⚠️ 注意:特性本身不会改变代码的行为,它们只是"标签"。真正起作用的是读取这些标签的代码


二、C# 内置的常见特性示例

C# 和 .NET 提供了许多内置特性:

特性 用途
[Obsolete] 标记过时的 API,编译时发出警告或错误
[DllImport] 调用非托管 DLL 函数
[Serializable] 标记类可序列化
[Conditional] 条件编译(仅当定义了指定符号时才调用方法)
ASP.NET Core 中的 [HttpGet], [FromBody] 路由与模型绑定

三、如何自定义一个特性?

步骤 1:继承 System.Attribute

所有自定义特性必须直接或间接继承自 System.Attribute 类。

步骤 2:使用 [AttributeUsage] 指定适用目标(可选但推荐)

控制该特性可以应用到哪些程序元素上。

示例:定义一个用于标记"API 接口描述"的自定义特性

csharp 复制代码
using System;

// 指定该特性只能用于方法,且不可继承,允许多次使用
[AttributeUsage(
    validOn: AttributeTargets.Method,
    AllowMultiple = false,
    Inherited = true)]
public class ApiDescriptionAttribute : Attribute
{
    public string Title { get; }
    public string Summary { get; }

    // 构造函数(必需通过构造函数传入必要信息)
    public ApiDescriptionAttribute(string title)
    {
        Title = title;
        Summary = string.Empty;
    }

    // 可选属性(可通过命名参数设置)
    public string Version { get; set; } = "1.0";
}

关键点说明

  • 特性的位置参数(Positional Parameters)通过构造函数传入。
  • 命名参数(Named Parameters)通过公共属性或字段设置。
  • [AttributeUsage] 是可选的,但强烈建议使用以提高安全性。

四、应用自定义特性

将特性应用到方法上:

csharp 复制代码
public class UserService
{
    [ApiDescription("获取用户信息", Summary = "根据用户ID返回完整用户资料", Version = "2.1")]
    public User GetUserById(int id)
    {
        return new User { Id = id, Name = "Alice" };
    }

    [ApiDescription("创建新用户")]
    public void CreateUser(User user)
    {
        // 创建逻辑
    }
}

🔍 注意:使用时可以省略 Attribute 后缀,即写成 [ApiDescription(...)] 而不是 [ApiDescriptionAttribute(...)],这是 C# 的语法糖。


五、在运行时通过反射读取特性

现在我们编写代码,在运行时扫描某个类型的所有方法,提取其上的 ApiDescriptionAttribute 并输出文档信息:

csharp 复制代码
using System;
using System.Linq;
using System.Reflection;

public class Program
{
    public static void Main()
    {
        Type type = typeof(UserService);
        MethodInfo[] methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance);

        foreach (var method in methods)
        {
            var attr = method.GetCustomAttribute<ApiDescriptionAttribute>();
            if (attr != null)
            {
                Console.WriteLine($"方法: {method.Name}");
                Console.WriteLine($"  标题: {attr.Title}");
                Console.WriteLine($"  摘要: {attr.Summary}");
                Console.WriteLine($"  版本: {attr.Version}");
                Console.WriteLine();
            }
        }
    }
}

输出结果:

复制代码
方法: GetUserById
  标题: 获取用户信息
  摘要: 根据用户ID返回完整用户资料
  版本: 2.1

方法: CreateUser
  标题: 创建新用户
  摘要: 
  版本: 1.0

六、高级用法补充

1. 支持多个相同特性(AllowMultiple = true)

如果你希望同一个方法可以标记多个相同特性:

csharp 复制代码
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class TagAttribute : Attribute
{
    public string Name { get; }
    public TagAttribute(string name) => Name = name;
}

// 使用
[Tag("user")]
[Tag("security")]
public void SecureMethod() { }

读取时用 GetCustomAttributes<T>()(注意是复数):

csharp 复制代码
var tags = method.GetCustomAttributes<TagAttribute>();
foreach (var tag in tags)
    Console.WriteLine(tag.Name);

2. 特性用于编译时处理(如 Source Generators)

从 C# 9 开始,结合 源生成器(Source Generators) ,特性还可以在编译期被分析,用于生成额外代码(如自动实现 INotifyPropertyChanged、DTO 映射等),这比运行时反射更高效。


七、总结

项目 说明
本质 特性是元数据,不执行逻辑
生效方式 必须通过反射(或源生成器)主动读取
自定义步骤 继承 Attribute + [AttributeUsage] + 构造函数/属性
应用场景 框架扩展(如 MVC、EF)、日志、权限控制、文档生成、AOP 等
性能注意 反射有开销,高频场景建议缓存反射结果

相关推荐
她说彩礼65万2 小时前
C# AutoResetEvent和ManualResetEvent
java·jvm·c#
Hi202402176 小时前
消除FFmpeg库的SONAME依赖
linux·ffmpeg
gfanbei7 小时前
ARM V8 Cortex R52 上电运行在什么状态?— Deepseek 解答
linux·arm开发·嵌入式硬件
liu****8 小时前
14.日志封装和线程池封装
linux·开发语言·c++
云动雨颤8 小时前
访问宝塔面板安全入口404?SSH命令轻松解决
linux·运维·安全
NPE~8 小时前
[Linux命令分享]日志查看 — — less
linux·运维·less·常用命令·日志查看
赖small强8 小时前
Linux 系统调用在 ARM 上的实现与工作机制
linux·系统调用·内核态·用户态·上下文切换
面向星辰9 小时前
扣子开始节点和结束节点
java·服务器·前端
一匹电信狗9 小时前
【C++】封装红黑树实现map和set容器(详解)
服务器·c++·算法·leetcode·小程序·stl·visual studio