自定义Attribute只是编译后存入元数据的静态描述,不会自动运行;必须通过反射(如GetCustomAttribute)显式读取并配合手动逻辑或AOP才能生效,否则始终沉默。

自定义 Attribute 不会自动运行,也不会触发任何逻辑——它只是编译后存进元数据的一段静态描述,读不读、怎么用,全由你决定。
为什么 [Log] 贴上去却没日志?
因为 LogAttribute 本身不执行日志逻辑;它只是一张“标签纸”,写好了“这里要打日志”,但没人拿笔去写。内置特性如 [Obsolete] 是编译器硬编码识别的例外,而你写的每个自定义特性,默认就是“沉默的元数据”。
- 没调用
GetCustomAttribute<LogAttribute>(),它就永远不会被实例化,构造函数都不会执行 - 即使调用了,返回的是一个新实例,不会自动注入到方法调用链中
- 想让它“生效”,必须配合反射 + 手动逻辑(比如在方法入口检查并写日志),或借助 AOP 框架 / 源生成器
GetCustomAttribute() 总是返回 null 的真实原因
不是代码写错了,而是反射根本没定位到你要查的那个成员——或者该成员压根没带这个特性。
-
GetMethod("Fetch")返回null:方法是private、拼写错误、或在基类里没用virtual/override正确暴露 - 忘了加
[AttributeUsage(AttributeTargets.Method)]:特性被允许标在类上,但你却去方法上查——元数据里根本没存这一条 - 特性类不是
public,或构造函数是internal:反射无法创建实例,GetCustomAttribute只能返回null - 用了命名参数但属性没写
public set:[Log(Tag = "api")]会静默失败,Tag保持默认值或null
如何安全高效地读取 Attribute 元数据
别把 GetCustomAttribute<T>() 当成“保证有值”的 API——它设计就是返回 null,且每次调用都 new 实例,高频路径下是性能隐患。
- 永远先判空:
var attr = method.GetCustomAttribute<RetryAttribute>(); if (attr != null) { ... } - 避免在 ASP.NET Core 中间件、gRPC 拦截器等热路径反复反射;应提前缓存结果(例如用
ConcurrentDictionary<MethodInfo, RetryAttribute>) - 若需支持多个同类型特性,用
GetCustomAttributes(typeof(T), false)并手动as T,比泛型重载更可控 -
Inherited = false更安全:防止子类意外继承父类方法上的[Authorize]类特性,造成权限误判
构造函数和属性的参数限制很实际
这些不是语法刁难,而是元数据序列化的硬性要求:编译器必须能在编译期确定所有值。
- 构造函数参数只能是常量表达式:
string、int、typeof(...)、enum、一维数组(如new[] { "a", "b" }) - 不能传
Func<T>、object实例、new List<string>()等运行时对象 - 命名参数对应 public 自动属性,且必须有
public set;只读属性(只有get)无法在方括号里赋值 - 构造函数里别做 I/O 或锁操作——每次反射读取都会触发一次,可能引发不可预知的并发问题
最易被忽略的一点:没被读取的 Attribute 零开销,但一旦开始反射,它就从“静态标签”变成“运行时对象”,构造、赋值、GC 都真实发生。别指望它轻量,除非你确认它永远不被触碰。


















