C# 特性(Attribute)
内置特性、自定义特性、`AttributeUsage`、以及通过反射读取。
内置特性
using System;
using System.Diagnostics;
class Demo
{
[Obsolete("请改用 NewMethod", error: false)]
public void OldMethod() => Console.WriteLine("旧方法");
public void NewMethod() => Console.WriteLine("新方法");
[Conditional("DEBUG")]
public void Trace(string msg) => Console.WriteLine(msg);
[STAThread]
static void Main() { }
}
var d = new Demo();
d.OldMethod(); // 编译警告
d.NewMethod();
d.Trace("调试信息");旧方法
新方法
调试信息| Attribute | Effect |
|---|---|
[Obsolete] | 标记过时,编译警告或错误 |
[Conditional] | 按符号决定是否编译调用 |
[Serializable] | 可序列化 |
[DebuggerDisplay] | 调试器里显示什么 |
[JsonIgnore] | JSON 序列化时跳过 |
[Required] | 模型校验必填 |
[Fact] / [Test] | 标记测试方法 |
自定义特性
using System;
// AttributeUsage 限制能贴在哪、能否重复
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
class AuthorAttribute : Attribute
{
public string Name { get; }
public int Version { get; set; }
public AuthorAttribute(string name) => Name = name;
}
[Author("张三", Version = 1)]
[Author("李四", Version = 2)]
class Report
{
[Author("王五")]
public void Generate() { }
}
Console.WriteLine("特性已标注");特性已标注Name the class with an Attribute suffix; usage may omit it ([Author] means [AuthorAttribute]). Constructor arguments must be compile-time constants — numbers, strings, enums, typeof.
通过反射读取
using System;
using System.Linq;
using System.Reflection;
var type = typeof(Report);
// 读类上的所有 Author
var authors = type.GetCustomAttributes<AuthorAttribute>();
foreach (var a in authors)
{
Console.WriteLine($"{a.Name} v{a.Version}");
}
// 读方法上的
var method = type.GetMethod("Generate")!;
var mAuthor = method.GetCustomAttribute<AuthorAttribute>();
Console.WriteLine($"方法作者 {mAuthor?.Name}");张三 v1
李四 v2
方法作者 王五An attribute does nothing by itself. It stores data in assembly metadata; someone (a framework, reflection code) must read it and act. ASP.NET Core routing, EF Core mapping and xUnit discovery all work this way.
AttributeUsage 参数
[AttributeUsage(
AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method,
AllowMultiple = false,
Inherited = true)]
class MarkerAttribute : Attribute { }
// AttributeTargets 可组合:
// All, Assembly, Module, Class, Struct, Enum, Constructor,
// Method, Property, Field, Event, Interface, Parameter,
// Delegate, ReturnValue, GenericParameter, Type| Parameter | Meaning |
|---|---|
AttributeTargets | 能贴在哪里(可按位组合) |
AllowMultiple | 同一处能否贴多个 |
Inherited | 派生类是否继承 |
实用例子:简单的命令路由
[AttributeUsage(AttributeTargets.Method)]
class CommandAttribute : Attribute
{
public string Name { get; }
public CommandAttribute(string name) => Name = name;
}
class Commands
{
[Command("hello")]
public void Hello() => Console.WriteLine("你好");
[Command("bye")]
public void Bye() => Console.WriteLine("再见");
}
var map = typeof(Commands)
.GetMethods()
.Select(m => (Attr: m.GetCustomAttribute<CommandAttribute>(), Method: m))
.Where(x => x.Attr != null)
.ToDictionary(x => x.Attr!.Name, x => x.Method);
var instance = new Commands();
map["hello"].Invoke(instance, null);你好This is the core pattern of many frameworks: declare with attributes, build a map by reflection, invoke at runtime. ASP.NET Core's [HttpGet], xUnit's [Fact] and NUnit's [Test] all follow it.
内置特性
using System;
using System.Diagnostics;
class Demo
{
[Obsolete("请改用 NewMethod", error: false)]
public void OldMethod() => Console.WriteLine("旧方法");
public void NewMethod() => Console.WriteLine("新方法");
[Conditional("DEBUG")]
public void Trace(string msg) => Console.WriteLine(msg);
[STAThread]
static void Main() { }
}
var d = new Demo();
d.OldMethod(); // 编译警告
d.NewMethod();
d.Trace("调试信息");旧方法
新方法
调试信息| 特性 | 作用 |
|---|---|
[Obsolete] | 标记过时,编译警告或错误 |
[Conditional] | 按符号决定是否编译调用 |
[Serializable] | 可序列化 |
[DebuggerDisplay] | 调试器里显示什么 |
[JsonIgnore] | JSON 序列化时跳过 |
[Required] | 模型校验必填 |
[Fact] / [Test] | 标记测试方法 |
自定义特性
using System;
// AttributeUsage 限制能贴在哪、能否重复
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
class AuthorAttribute : Attribute
{
public string Name { get; }
public int Version { get; set; }
public AuthorAttribute(string name) => Name = name;
}
[Author("张三", Version = 1)]
[Author("李四", Version = 2)]
class Report
{
[Author("王五")]
public void Generate() { }
}
Console.WriteLine("特性已标注");特性已标注特性类名以 Attribute 结尾,使用时可以省略后缀:[Author] 就是 [AuthorAttribute]。构造参数只能是编译期常量(数字、字符串、枚举、typeof)。
通过反射读取
using System;
using System.Linq;
using System.Reflection;
var type = typeof(Report);
// 读类上的所有 Author
var authors = type.GetCustomAttributes<AuthorAttribute>();
foreach (var a in authors)
{
Console.WriteLine($"{a.Name} v{a.Version}");
}
// 读方法上的
var method = type.GetMethod("Generate")!;
var mAuthor = method.GetCustomAttribute<AuthorAttribute>();
Console.WriteLine($"方法作者 {mAuthor?.Name}");张三 v1
李四 v2
方法作者 王五特性本身什么都不做。它只是把数据存进程序集的元数据;必须有人(框架、反射代码)去读它并执行相应逻辑。ASP.NET Core 的路由、EF Core 的映射、xUnit 的测试发现都靠这个机制。
AttributeUsage 参数
[AttributeUsage(
AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method,
AllowMultiple = false,
Inherited = true)]
class MarkerAttribute : Attribute { }
// AttributeTargets 可组合:
// All, Assembly, Module, Class, Struct, Enum, Constructor,
// Method, Property, Field, Event, Interface, Parameter,
// Delegate, ReturnValue, GenericParameter, Type| 参数 | 含义 |
|---|---|
AttributeTargets | 能贴在哪里(可按位组合) |
AllowMultiple | 同一处能否贴多个 |
Inherited | 派生类是否继承 |
实用例子:简单的命令路由
[AttributeUsage(AttributeTargets.Method)]
class CommandAttribute : Attribute
{
public string Name { get; }
public CommandAttribute(string name) => Name = name;
}
class Commands
{
[Command("hello")]
public void Hello() => Console.WriteLine("你好");
[Command("bye")]
public void Bye() => Console.WriteLine("再见");
}
var map = typeof(Commands)
.GetMethods()
.Select(m => (Attr: m.GetCustomAttribute<CommandAttribute>(), Method: m))
.Where(x => x.Attr != null)
.ToDictionary(x => x.Attr!.Name, x => x.Method);
var instance = new Commands();
map["hello"].Invoke(instance, null);你好这就是很多框架的核心模式:用特性声明元数据 + 反射建立映射 + 运行时调用。ASP.NET Core 的 [HttpGet("/users")]、xUnit 的 [Fact]、NUnit 的 [Test] 都是这个套路。