C# 自定义特性方法 C#如何创建和使用Attribute

来源:这里教程网 时间:2026-02-21 17:39:09 作者:

怎么定义一个可作用于方法的自定义 Attribute

自定义

Attribute
必须继承
System.Attribute
,且推荐显式指定
AttributeUsage
来限定它能修饰的目标(比如只允许用在方法上)。不加这个约束,编译器不会报错,但运行时反射读取时可能拿到意外位置的实例。

常见错误是忘记加

[AttributeUsage(AttributeTargets.Method)]
,结果特性被误标在类或属性上,后续通过
GetCustomAttribute<myattr>(method)</myattr>
查不到——因为根本没允许它出现在方法上。

类名建议以
Attribute
结尾(如
LogExecutionTimeAttribute
),C# 允许使用时省略后缀(
[LogExecutionTime]
构造函数参数决定「位置参数」,
public
属性决定「命名参数」
如果需要在 JIT 或 AOT 编译阶段生效(比如影响 IL 生成),普通
Attribute
不行,得用源生成器或
ModuleInitializer
配合

如何在方法上应用并读取自定义 Attribute

应用很简单:

[MyCustomAttr("value", Level = 2)]
;读取必须靠反射,且注意:只有在目标成员被反射访问时,.NET 才会解析并实例化该特性——不是声明时就创建对象。

典型场景是 AOP 式日志、权限校验、序列化控制。但别在热路径里反复调用

GetCustomAttribute
,它有反射开销;建议启动时缓存(比如用
ConcurrentDictionary<methodinfo t></methodinfo>
)。

typeof(MyClass).GetMethod("MyMethod").GetCustomAttribute<mycustomattr>()</mycustomattr>
获取单个实例
GetCustomAttributes<mycustomattr>(true)</mycustomattr>
的第二个参数为
true
表示查找继承链上的特性
若特性类含复杂初始化逻辑(如打开文件、连接数据库),要格外小心——它会在每次反射获取时执行

为什么 GetCustomAttribute 返回 null 而不是抛异常

GetCustomAttribute<t>()</t>
设计为“查无此特则返回
null
”,这是有意为之:绝大多数使用场景下,缺失特性是合法状态(比如某个方法不需要日志,就不打标签),没必要用异常干扰流程。

容易踩的坑是直接对返回值调用方法而不判空,导致

NullReferenceException
。更隐蔽的问题是:你写了
[MyAttr]
,但忘了在类/方法上加
public
访问修饰符,导致反射拿不到该成员——此时
GetMethod
返回
null
,再调
GetCustomAttribute
就是空引用。

检查目标方法是否为
public
,非 public 成员需用
BindingFlags.NonPublic | BindingFlags.Instance
确认程序集已加载,且特性类型未被 Trim(发布时若启用
TrimMode=link
,需在
.csproj
中添加
<istrimmable>false</istrimmable>
调试时可用
typeof(MyClass).GetMethods().Select(m => (m.Name, m.GetCustomAttribute<myattr>()))</myattr>
快速验证是否被正确识别

Attribute 和 Source Generator 怎么配合避免运行时反射

Attribute
只能在运行时通过反射读取,而 Source Generator 能在编译期扫描这些特性,生成额外代码(比如自动注册 Handler、注入拦截逻辑)。这对性能敏感或 AOT 场景很关键。

比如你写了个

[Route("/api/users")]
,不用等请求进来再反射找路由,Generator 可直接生成
MapGet
调用。但要注意:Generator 看不到私有成员,也无法处理动态构造的类型名(如字符串拼接的类名)。

Generator 必须引用含特性的程序集,且该程序集不能是
InternalsVisibleTo
隐藏的
特性类本身需为
public
,且所有构造函数和属性也要是
public
,否则 Generator 读不到值
不要在特性中放不可序列化的字段(如委托、Stream),Generator 在编译期无法执行它们

实际项目里最常出问题的,是特性被定义成

internal
却期望在另一个程序集里被反射读到,或者忘了给
AttributeUsage
Inherited = true
导致子类方法拿不到父类标记的特性。

相关推荐