自定义 Attribute 必须继承 Attribute 类并显式标记 [AttributeUsage],否则反射可能查不到;构造函数参数需为编译期常量,公开属性需 public get;反射读取时应判空或用类型安全方式,高频场景须缓存结果。

怎么定义一个能被反射读取的自定义 Attribute
Attribute 必须继承 Attribute 类,且推荐显式标记 [AttributeUsage] 来约束使用场景。不加这个,编译器不会报错,但运行时反射可能查不到——因为默认只允许用在类上,而你可能想用在方法或参数上。
常见错误:定义完直接往方法上贴,GetCustomAttributes() 却返回空数组。八成是忘了 [AttributeUsage] 或写错了 AttributeTargets。
-
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = false, AllowMultiple = true)]—— 明确支持类和方法,不允许继承,允许多次使用 - 构造函数参数必须是编译期常量(
string、int、typeof(...)、枚举等),不能传new List<string>()这种运行时对象 - 公开属性要加
public get,否则反射读不到值;字段不推荐,反射读取逻辑不一致
如何用反射安全读取 Attribute 实例
别直接调 GetCustomAttribute<T>() 就完事。它在没找到时返回 null,但如果你用了泛型约束又没处理 null,后续调用会崩;更糟的是,如果 T 不是继承自 Attribute,编译都过不去。
推荐统一走 GetCustomAttributes(Type, bool) + 类型检查,尤其当你要读多个同名 Attribute 或不确定是否存在的时候。
- 读单个:用
GetCustomAttribute<MyLogAttribute>(method),但记得判null - 读多个或动态类型:用
method.GetCustomAttributes(typeof(MyLogAttribute), false),返回object[],再逐个as MyLogAttribute -
inherit = false更安全——避免意外继承父类的 Attribute,特别是用在接口实现方法上时
示例:
var attr = method.GetCustomAttribute<RetryAttribute>();<br>if (attr != null && attr.MaxRetries > 0) { /* 执行重试逻辑 */ }
为什么 [Obsolete] 能直接报编译警告,而你的自定义 Attribute 不行
因为 [Obsolete] 是编译器“硬编码识别”的特殊 Attribute,不是靠反射。你写的任何自定义 Attribute 都不会触发编译警告/错误,哪怕名字叫 [MustUseAsync] 也一样。
想达到类似效果,只有两个现实路径:
- 写 Roslyn 分析器(Analyzer)——需要额外项目、发布 .nupkg、用户手动引用包,适合团队级规范
- 在运行时拦截(比如用 AOP 框架或中间件)——适用于 Web API、gRPC 等有统一入口的场景
别试图用 [Conditional] 或预处理器模拟,它们和 Attribute 完全无关,也控制不了编译行为。
Attribute 的性能开销到底有多大
反射读取本身不慢,慢的是反复调用 GetCustomAttributes() 做重复解析。.NET 6+ 对常用 Attribute(如 [DllImport])做了缓存,但你的自定义 Attribute 不在其中。
真实瓶颈往往出现在高频路径:比如 MVC 控制器每个 Action 都反射读一次 [Authorize],框架内部已做缓存;但你自己在循环里对每个 PropertyInfo 都调 GetCustomAttribute<JsonNameAttribute>(),就容易拖慢序列化。
- 高频场景务必缓存结果:用
ConcurrentDictionary<MemberInfo, T>或静态字典 - 避免在
ToString()、GetHashCode()等基础方法里触发反射读取 -
Attribute.GetCustomAttributeData()比GetCustomAttribute<>()更轻量,适合只读元数据不实例化的场景
真正容易被忽略的点是:Attribute 构造函数里的逻辑会在每次反射实例化时执行——如果里面写了日志、IO 或复杂计算,那性能就不是“有点慢”,而是“不该这么用”。










