自定义特性是继承attribute并用[attributeusage]声明适用位置的类;需注意allowmultiple和inherited设置、位置参数须在命名参数前、运行时用getcustomattribute()安全获取,内置特性不可继承。

怎么定义一个自定义 Attribute 类
自定义特性本质是一个继承自 Attribute 的类,必须用 [AttributeUsage] 明确它能用在哪些位置(比如类、方法、参数等),否则编译会警告,运行时也取不到。
常见错误:忘记加 [AttributeUsage],或者写成 [AttributeUsage(AttributeTargets.Class)] 却试图把它标在方法上——这时 GetCustomAttribute 返回 null,但不报错,容易误以为“没生效”。
示例:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class ApiVersionAttribute : Attribute
{
public string Version { get; }
public ApiVersionAttribute(string version) => Version = version;
}
-
AllowMultiple = false表示同一个目标上不能重复标记(如不允许两个[ApiVersion("v2")]) -
Inherited = true表示子类能继承父类上的该特性(对类/方法有效,对参数无效) - 构造函数参数会成为「位置参数」,属性(
get/set)是「命名参数」,调用时写法不同
怎么把特性加到代码上并传参
语法就是方括号 + 类名(可省略 Attribute 后缀),传参分两种:位置参数(构造函数参数)和命名参数(属性赋值)。
容易踩的坑:[ApiVersion("v1", IsDeprecated = true)] 是合法的,但 [ApiVersion(IsDeprecated = true, "v1")] 会编译失败——位置参数必须在命名参数前面。
使用场景举例:
[ApiVersion("v2")]
public class UserController { }
<p>[ApiVersion("v1", IsDeprecated = true)]
public void GetUsers() { }</p>
- 类名写
ApiVersion或ApiVersionAttribute都行,C# 会自动补全 - 如果特性类叫
RouteAttribute,你写[Route("/api")]就够了,不用写[RouteAttribute("/api")] - 所有参数类型必须是编译期常量(
string、int、typeof(...)、枚举等),不能传new List<string>()</string>
怎么在运行时获取特性信息
核心是用 GetCustomAttribute<t>()</t> 或 GetCustomAttributes<t>()</t>,它们属于反射 API,必须指定泛型类型或传 Type 对象。别直接用非泛型版本(返回 object[]),容易类型转换出错。
常见错误现象:GetCustomAttribute<apiversionattribute>(method)</apiversionattribute> 返回 null,大概率是:method 对象本身没取对(比如用了 typeof(T).GetMethod("xxx") 但拼错名字)、特性没加 [AttributeUsage]、或者目标被 JIT 内联优化掉(极少见,但单元测试里 mock 方法时可能遇到)。
实操建议:
- 获取单个:用
method.GetCustomAttribute<apiversionattribute>()</apiversionattribute>,比GetCustomAttribute(typeof(ApiVersionAttribute)) as ApiVersionAttribute更安全 - 获取多个同类型:用
type.GetCustomAttributes<obsoleteattribute>()</obsoleteattribute>(注意是复数),它返回ApiVersionAttribute[],不是object[] - 要检查是否定义了某特性,别写
attr != null就完事——先确认attr.Version是否为空字符串,因为构造函数可能传了空值
内置特性如 [Obsolete]、[DllImport] 能不能继承或修改行为
不能。所有以 System. 开头的内置特性([Serializable]、[Flags]、[DebuggerStepThrough] 等)都是密封类(sealed),无法继承,也不支持重写逻辑。它们的行为由 CLR 直接识别并硬编码处理。
例如:[Obsolete("Use NewMethod instead", error: true)] 的 error: true 会让编译器报错而非警告,这个控制权完全在编译器手里,你自定义的特性做不到这点。
如果你需要类似效果,只能自己实现检查逻辑(比如在程序启动时遍历所有方法,用反射找 [MyObsolete] 并抛异常),但无法让 C# 编译器参与。
性能提醒:大量使用反射读特性(尤其在 hot path 如 HTTP 请求入口)会影响吞吐量。高频场景建议缓存结果(比如用 ConcurrentDictionary<methodinfo apiversionattribute></methodinfo>)。











