getcustomattribute() 返回 null 不是 bug,而是配置或用法错误:常见原因包括缺失或误配[attributeusage]、目标元素不可见、构造函数参数非法、特性类非public/泛型/抽象等。

GetCustomAttributenull 不是 bug,是配置或用法错了——最常见原因是没加 [AttributeUsage],或者目标元素不可见、构造函数参数非法。
为什么 GetCustomAttribute<t>()</t> 总是返回 null
反射查不到特性,90% 情况下不是代码写错,而是特性类本身不满足“可被读取”的硬性条件:
-
[AttributeUsage]缺失或validOn写错:比如声明了[AttributeUsage(AttributeTargets.Class)],却贴在方法上,method.GetCustomAttribute<myattr>()</myattr>必为null - 目标方法非
public,且没传BindingFlags.NonPublic:例如私有方法private void DoWork(),直接typeof(X).GetMethod("DoWork").GetCustomAttribute<t>()</t>会返回null(因为GetMethod默认只找 public) - 构造函数用了运行时对象:如
public MyAttr(List<string> tags)</string>,编译直接报错,根本贴不上特性 - 特性类不是
public、是泛型或抽象类:反射无法实例化,GetCustomAttribute也拿不到
[AttributeUsage] 怎么写才不踩坑
这个特性不是可选的“说明文档”,而是反射读取的开关。漏写、写窄、写错都会让特性变成“隐形”:
- 必须显式指定
validOn:比如要修饰方法和参数,就得写[AttributeUsage(AttributeTargets.Method | AttributeTargets.Parameter)],不能只写Method然后往参数上硬贴 -
AllowMultiple = true才能重复使用:否则[Log("A"), Log("B")]编译失败 -
Inherited = false是多数业务场景更安全的选择:避免父类打了[Retry(3)],子类没打却意外继承到,导致重试逻辑失控 - 别依赖默认值:虽然
AttributeTargets.All是默认值,但显式写出更清晰,也避免未来 .NET 版本变更默认行为带来的隐性风险
位置参数 vs 命名参数怎么传才对
写法差异小,但语义和限制完全不同,混用会编译失败或读不到值:
- 构造函数参数是位置参数:必须按顺序传,且只能是编译期常量(
string、int、typeof(X)、枚举值),例如[MyAttr("api", 2)] - 公开属性是命名参数:写在括号里用
=赋值,比如[MyAttr("api", Level = 2, Enabled = true)];字段(public string Tag;)不推荐,反射读取不稳定 - 命名参数不能省略位置参数:如果构造函数要求一个
string name,那[MyAttr(Level = 2)]编译不过 - 命名参数必须有 public set:只读属性(
public string Name { get; })无法接收赋值,运行时该字段始终为默认值
高频读取时的性能和空值陷阱
每次调用 GetCustomAttribute<t>()</t> 都触发一次反射查找,开销不小,且它永远不抛异常、只返回 null:
- 永远先判空再用:
var attr = method.GetCustomAttribute<retryattribute>(); if (attr?.MaxRetries > 0) { ... }</retryattribute>,否则容易引发后续NullReferenceException - 在中间件、AOP 拦截器等高频路径中,缓存结果更实际:用
ConcurrentDictionary<methodinfo retryattribute></methodinfo>存已查过的MethodInfo和对应特性实例 - 需要读多个同名特性时,别反复调
GetCustomAttribute:改用method.GetCustomAttributes(typeof(RetryAttribute), false),返回object[],再逐个as RetryAttribute -
inherit = true可能带来意料外结果:若接口定义了[Log],实现类没打,但你用了GetCustomAttribute<logattribute>(true)</logattribute>,就会拿到接口上的实例——这在跨层日志、权限控制里容易误判
真正难的不是写一个能编译的 Attribute 类,而是确保它在运行时能被稳定、准确、高效地读到。每个环节都可能断链:从 [AttributeUsage] 的声明粒度,到目标成员的可见性,再到反射调用时的 binding flag 和缓存策略——漏掉任意一环,null 就在那里等着你。











