C#中的Attribute本质是编译期标记+运行时可反射读取的元数据,不自动执行逻辑;需显式调用GetCustomAttribute等反射方法才能生效,内置特性如[Obsolete]由编译器硬编码识别,自定义特性须手动读取或借助AOP/源生成器。

直接说结论:C# 中的 Attribute 不是“语法糖”,也不是装饰器式运行时魔法——它本质是一组编译期标记 + 运行时可反射读取的元数据,**不执行任何逻辑,除非你显式调用 GetCustomAttribute 或类似反射方法**。
为什么加了 [Obsolete] 会报错,但自定义 [Log] 却完全没反应?
这是最常被误解的点:特性本身不触发行为。内置特性如 [Obsolete]、[Serializable] 是被编译器或运行时“硬编码识别”的,而你写的 [MyAttr] 默认只是存着,没人看、没人管。
- 编译器只对少数内置特性做特殊处理(如
[Obsolete]触发警告/错误,[DllImport]影响 P/Invoke) - 自定义特性必须靠你自己写反射代码去读取、判断、执行逻辑(比如在方法入口自动打日志)
- 若想“自动生效”,得配合 AOP 框架(如 Fody、Castle DynamicProxy)或 .NET 8+ 的源生成器(Source Generator),而非靠特性自己动
如何正确定义并应用一个可被反射读取的特性?
关键在三处:继承 Attribute、加 AttributeUsage、用 GetCustomAttribute 读取。漏任一环都会“失效”。
-
AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)必须显式声明作用目标,否则默认只允许用在类上 - 构造函数参数决定你在方括号里能传什么:
[Retry(3, "Network")]要求有public Retry(int times, string reason) - 属性(property)用于命名参数:
[Retry(Times = 3)]对应public int Times { get; set; },且需为 public set - 读取时注意:要查方法上的特性,得从
MethodInfo查,不是从委托或调用栈查;类型上的是从Type查
示例:
[AttributeUsage(AttributeTargets.Method)]
public class LogAttribute : Attribute
{
public string Tag { get; set; } = "default";
}
<p>// 使用
public class Service
{
[Log(Tag = "api")]
public void Fetch() { }
}</p><p>// 读取(必须手动触发)
var method = typeof(Service).GetMethod("Fetch");
var attr = method.GetCustomAttribute<logattribute>(); // 返回非 null
</logattribute></p>
Attribute.GetCustomAttribute 性能差,高频调用怎么办?
反射读取特性是相对重的操作,尤其在循环、中间件、高频 API 入口反复调用时,会明显拖慢性能。
- 不要在每次请求中都调用
method.GetCustomAttribute—— 改为启动时缓存:用() ConcurrentDictionary预热 - .NET 5+ 可用
MemberInfo.GetCustomAttributesData()获取原始数据(不触发属性 getter),适合只判断是否存在、不依赖属性值的场景 - 若只是开关型控制(如是否启用缓存),建议把特性解析结果下沉到静态字段或配置对象,避免重复反射
- 注意:
typeof(T).GetCustomAttributes()比instance.GetType().GetCustomAttributes()略快,因前者跳过实例类型推导
真正麻烦的从来不是怎么写 [MyAttr],而是想清楚“谁来读它、什么时候读、读完之后做什么”。特性本身不带执行上下文,也不绑定生命周期——它只是钉在 IL 里的小纸条,等你亲手拆开、念出来、再决定要不要照做。











