自定义 Attribute 是继承自 System.Attribute 的 public 非泛型非抽象类,需用 [AttributeUsage] 明确目标、允许多例及继承性,提供 public 构造函数(位置参数)和 public 属性(命名参数),并通过反射如 GetCustomAttribute() 读取。
在 C# 中,自定义 Attribute 本质是一个继承自 System.Attribute 的类。它必须是公共的、非泛型、非抽象的,并且通常用 [AttributeUsage] 明确其适用范围和行为。
常见错误是忘记加 [AttributeUsage],导致编译通过但运行时无法通过反射获取;或把构造函数设为 private,让使用者无法实例化。
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] —— 必须显式声明能修饰哪些目标(如类、方法)、是否允许多次使用、是否继承传递[MyAttr("hello")] 这种写法)[MyAttr("hello", Level = 2)])using System;[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] public class LoggableAttribute : Attribute { public string Category { get; } public int Priority { get; set; } = 1;
public LoggableAttribute(string category) { Category = category ?? throw new ArgumentNullException(nameof(category)); }}
如何在代码中应用并读取自定义 Attribute
应用很简单:
[Loggable("API")]直接写在类或方法上。读取则依赖反射——注意:默认不会自动加载程序集中的所有类型,你得明确拿到Type或MethodInfo对象再查。容易踩的坑是调用
GetCustomAttribute时没处理 null 返回,或误用() GetCustomAttributes(true)在非继承场景下多传了参数。
typeof(MyClass).GetCustomAttribute() 获取单个实例(返回 null 表示没找到)methodInfo.GetCustomAttributes() 获取所有匹配实例(返回 LoggableAttribute[])inherit(如 GetCustomAttribute(true) )只在基类/接口上有该特性且当前类型未重写时才生效,多数业务场景保持默认 false 更可控AllowMultiple = true,别用单值获取方法,否则只拿到第一个var attr = typeof(Program).GetCustomAttribute(); if (attr != null) { Console.WriteLine($"{attr.Category}, Priority: {attr.Priority}"); } // 方法上的多个实例 var method = typeof(Program).GetMethod("DoWork"); var attrs = method?.GetCustomAttributes
().ToArray(); foreach (var a in attrs) { Console.WriteLine($"Method attr: {a.Category}"); }
不是代码写错了,而是反射读取失败的几个高频原因:特性类没加 public、目标元素没实际应用该特性、反射查询路径不对(比如查了父类却忘了 Inherited = true),或者用了不匹配的泛型类型参数。
public class XxxAttribute : Attribute,不能是 internal 或嵌套在其他类里(除非宿主类也是 public)typeof(X).GetCustomAttribute(),方法特性查 typeof(X).GetMethod("Y").GetCustomAttribute()
Inherited = false,子类即使没重复标注也不会从基类继承过来Assembly.LoadFrom() 后再查,别查当前 Assembly.GetExecutingAssembly() 以外的类型却不加载MyAttr,就不能用 GetCustomAttribute() 去拿原生 Attribute 本身不触发编译期逻辑。C# 9+ 的 Source Generator 可以扫描特性并生成新文件,但那是另一层机制——特性只是标记,Generator 才是干活的。别指望加个 [Obsolete] 风格的编译警告靠自定义特性自动实现。
真正需要编译期干预时,得配合 Analyzer + Source Generator,且用户项目要引用对应 NuGet 包。单纯靠 Attribute 类做不到强制校验或报错。
DynamicProxy、Castle.Core 或 .NET 6+ 的 DispatchProxy,不是靠特性本身Attribute 类毫无作用ConcurrentDictionary),尤其在 hot path 上特性本身只是元数据容器,它的价值完全取决于你怎么读、何时读、读完怎么用。别把它当成魔法开关,也别低估反射调用的开销。