c# 如何自定义特性 attribute

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

如何定义一个可被反射读取的自定义 Attribute 类

在 C# 中,自定义

Attribute
本质是一个继承自
System.Attribute
的类。它必须是公共的、非泛型、非抽象的,并且通常用
[AttributeUsage]
明确其适用范围和行为。

常见错误是忘记加

[AttributeUsage]
,导致编译通过但运行时无法通过反射获取;或把构造函数设为 private,让使用者无法实例化。

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
—— 必须显式声明能修饰哪些目标(如类、方法)、是否允许多次使用、是否继承传递
至少提供一个 public 构造函数(参数会成为“位置参数”,用于
[MyAttr("hello")]
这种写法)
额外 public 属性可作为“命名参数”(如
[MyAttr("hello", Level = 2)]
不要在构造函数里做耗时或可能抛异常的操作——特性实例化发生在编译期元数据写入阶段,运行时才读取,但构造逻辑仍需安全
using System;
<p>[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class LoggableAttribute : Attribute
{
public string Category { get; }
public int Priority { get; set; } = 1;</p><pre class='brush:php;toolbar:false;'>public LoggableAttribute(string category)
{
    Category = category ?? throw new ArgumentNullException(nameof(category));
}

}

如何在代码中应用并读取自定义 Attribute

应用很简单:

[Loggable("API")]
直接写在类或方法上。读取则依赖反射——注意:默认不会自动加载程序集中的所有类型,你得明确拿到
Type
MethodInfo
对象再查。

容易踩的坑是调用

GetCustomAttribute<t>()</t>
时没处理 null 返回,或误用
GetCustomAttributes(true)
在非继承场景下多传了参数。

typeof(MyClass).GetCustomAttribute<loggableattribute>()</loggableattribute>
获取单个实例(返回 null 表示没找到)
methodInfo.GetCustomAttributes<loggableattribute>()</loggableattribute>
获取所有匹配实例(返回
LoggableAttribute[]
第 2 个参数
inherit
(如
GetCustomAttribute<t>(true)</t>
)只在基类/接口上有该特性且当前类型未重写时才生效,多数业务场景保持默认
false
更可控
若特性有
AllowMultiple = true
,别用单值获取方法,否则只拿到第一个
var attr = typeof(Program).GetCustomAttribute<LoggableAttribute>();
if (attr != null)
{
    Console.WriteLine($"{attr.Category}, Priority: {attr.Priority}");
}
<p>// 方法上的多个实例
var method = typeof(Program).GetMethod("DoWork");
var attrs = method?.GetCustomAttributes<LoggableAttribute>().ToArray();
foreach (var a in attrs)
{
Console.WriteLine($"Method attr: {a.Category}");
}

为什么 GetCustomAttribute 返回 null?常见排查点

不是代码写错了,而是反射读取失败的几个高频原因:特性类没加

public
、目标元素没实际应用该特性、反射查询路径不对(比如查了父类却忘了
Inherited = true
),或者用了不匹配的泛型类型参数。

确认特性类本身是
public class XxxAttribute : Attribute
,不能是
internal
或嵌套在其他类里(除非宿主类也是 public)
确认你查的是正确对象:类特性查
typeof(X).GetCustomAttribute()
,方法特性查
typeof(X).GetMethod("Y").GetCustomAttribute()
如果特性定义了
Inherited = false
,子类即使没重复标注也不会从基类继承过来
确保程序集已加载——动态编译或插件场景下,
Assembly.LoadFrom()
后再查,别查当前
Assembly.GetExecutingAssembly()
以外的类型却不加载
泛型参数必须完全一致:比如定义的是
MyAttr<string></string>
,就不能用
GetCustomAttribute<myattr>()</myattr>
去拿

能否在编译期做检查或生成代码?

原生

Attribute
本身不触发编译期逻辑。C# 9+ 的
Source Generator
可以扫描特性并生成新文件,但那是另一层机制——特性只是标记,Generator 才是干活的。别指望加个
[Obsolete]
风格的编译警告靠自定义特性自动实现。

真正需要编译期干预时,得配合 Analyzer + Source Generator,且用户项目要引用对应 NuGet 包。单纯靠

Attribute
类做不到强制校验或报错。

运行时逻辑(如 AOP 日志)用反射读取即可,简单直接 想拦截方法调用?得结合
DynamicProxy
Castle.Core
或 .NET 6+ 的
DispatchProxy
,不是靠特性本身
想让 IDE 显示警告?必须写 Roslyn Analyzer,单独的
Attribute
类毫无作用
注意性能:频繁反射查特性建议缓存结果(如用
ConcurrentDictionary<memberinfo t></memberinfo>
),尤其在 hot path 上

特性本身只是元数据容器,它的价值完全取决于你怎么读、何时读、读完怎么用。别把它当成魔法开关,也别低估反射调用的开销。

相关推荐