Attribute 是继承 System.Attribute 的 public 类,必须用 [AttributeUsage] 明确目标、提供 public 构造函数和属性,否则反射返回 null;它仅是元数据,需手动反射读取并配合框架才能生效。

自定义 Attribute 不是“写个类加个方括号”就完事——没加 [AttributeUsage]、构造函数非 public、目标成员不可见,都会导致反射读不到,且不报错,只默默返回 null。
为什么 GetCustomAttribute() 总是返回 null
最常见原因不是代码写错了,而是反射根本没拿到你要查的那个成员。比如:
-
GetMethod("MyMethod")返回null,因为该方法是private或拼写错误;后续调用GetCustomAttribute<loggableattribute>()</loggableattribute>就是空引用操作 - 忘了加
[AttributeUsage(AttributeTargets.Method)],结果特性被允许标在类上,但你却去方法上查——它压根就不该出现在那儿 - 特性类本身不是
public,或构造函数是private/internal,.NET 反射无法实例化它,元数据里虽有记录,但运行时拿不到对象
如何正确定义一个只作用于方法的 Attribute 类
必须显式约束目标、保证可访问、区分位置与命名参数:
- 继承
System.Attribute,类声明为public - 加上
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]——Inherited = true表示子类能继承父类方法上的该特性 - 提供至少一个
public构造函数(参数即「位置参数」,如[Retry(3)]中的3) - 额外需要配置项就用
public自动属性(即「命名参数」,如[Retry(3, DelayMs = 100)]) - 避免在构造函数里做 I/O、网络、锁等耗时或可能失败的操作——每次反射读取都会触发一次
示例:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class RetryAttribute : Attribute
{
public int MaxRetries { get; }
public int DelayMs { get; set; } = 100;
<pre class="brush:php;toolbar:false;">public RetryAttribute(int maxRetries)
{
MaxRetries = maxRetries;
}}
读取时怎么避免 NullReferenceException 和性能陷阱
GetCustomAttribute<t>()</t> 设计就是返回 null,不是异常;而反复反射查同一方法会拖慢热路径:
- 永远先判空:
var attr = method.GetCustomAttribute<retryattribute>(); if (attr != null) { ... }</retryattribute> - 不要在循环或高频调用点(如 ASP.NET Core 中间件内)直接调用
GetCustomAttribute - 启动时批量缓存:用
ConcurrentDictionary<methodinfo retryattribute></methodinfo>预热,查的时候TryGetValue - 如果只是想控制编译行为(比如跳过某方法的源生成),普通
Attribute不行,得用 Source Generator 或[ModuleInitializer]
Attribute 类名要不要带 Attribute 后缀
要,这是约定,也是 C# 编译器识别机制的一部分:
- 类必须叫
LogExecutionTimeAttribute,应用时才能简写为[LogExecutionTime] - 如果不带后缀,比如叫
LogExecutionTime,那使用时必须写全[LogExecutionTimeAttribute],否则编译报错 - 后缀不是装饰,是编译器查找特性的隐式规则——省略后缀是语法糖,不是可选项
真正容易被忽略的是:特性不会自动激活,也不会改变 IL 或执行逻辑;它只是元数据。所有“效果”都依赖你手动反射 + 外部机制(AOP 框架、源生成、运行时拦截)去读取和响应。漏掉这层理解,就容易误以为加了标签就等于功能生效。











