C# 特性(Attribute)

内置特性、自定义特性、`AttributeUsage`、以及通过反射读取。

Advanced
Key Points特性是贴在代码上的元数据,本身不执行任何逻辑,要靠反射读取才有用。

内置特性

C#
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("调试信息");
Output
旧方法
新方法
调试信息
AttributeEffect
[Obsolete]标记过时,编译警告或错误
[Conditional]按符号决定是否编译调用
[Serializable]可序列化
[DebuggerDisplay]调试器里显示什么
[JsonIgnore]JSON 序列化时跳过
[Required]模型校验必填
[Fact] / [Test]标记测试方法

自定义特性

C#
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("特性已标注");
Output
特性已标注

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.

通过反射读取

C#
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}");
Output
张三 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 参数

C#
[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
ParameterMeaning
AttributeTargets能贴在哪里(可按位组合)
AllowMultiple同一处能否贴多个
Inherited派生类是否继承

实用例子:简单的命令路由

C#
[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);
Output
你好

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.

Friends & Partners