首页 / C# 入门教程 / 特性 Attribute 基础

C# 入门教程

特性 Attribute 基础

本教程共 100 篇 · 第 77 篇 · 更新于 2026-07-31 · 约 10 分钟阅读

C#C# 入门教程特性Attribute元数据

77. 特性 Attribute 基础

本节目标:学完你明白特性是”贴给编译器或运行时看的标签”,会写方括号语法,并认识 [Obsolete]、[Serializable] 等常见标注。

你可能注意过有些写法是用方括号 [ ] 括起来的,比如 [Obsolete]。这就是特性(attribute)。它不改变程序运行逻辑,而是往代码上”贴标签”,把额外信息(元数据)写进程序集,供编译器或运行时读取。

先用个比方:商品上的标签

特性就像贴在商品上的标签。一瓶药,瓶身写着”孕妇慎用""每日一次”——这些信息不改变药本身,但药店(编译器)和医生(运行时)会读它来做事。代码也一样:[Obsolete] 像”本方法已停产”的告示,[Serializable] 像”本类可快递(序列化)“的标记。

关键是:标签自己不会动,得有人去读它才有意义。

特性是什么

特性本质上是一个类(继承自 System.Attribute),用在方括号里。它描述”这段代码有什么特殊性质”,但不直接变成执行语句。比如标了 [Obsolete] 的方法,编译器会提示”已过时”。

namespace CSharpDemo;

var calc = new Calculator();
Console.WriteLine(calc.Add(1, 2));
// calc.OldAdd(1, 2);   // 编译时产生警告

class Calculator
{
    [Obsolete("请改用 Add 方法")]
    public int OldAdd(int a, int b) => a + b;

    public int Add(int a, int b) => a + b;
}
Note

特性的信息会被写进程序集的元数据里。它”安静地待着”,直到编译器或反射代码主动去读它——这就是它和”普通代码”最大的不同。

方括号语法与命名约定

写特性时把类(或其名字,去掉 Attribute 后缀)放在方括号里,紧贴在被标注的元素上方。按约定,所有特性类的名字都以 Attribute 结尾,但使用时可以省略。

namespace CSharpDemo;

[Serializable]            // 等同于 [SerializableAttribute]
class Person
{
    public string Name = "";
    public int Age;
}

也就是说 [Serializable][SerializableAttribute] 完全等价,SerializableAttribute 才是它在 .NET 库里的真名。编译器认得这个”省略后缀”的约定。

[Obsolete]:标记已过时

[Obsolete] 告诉调用者”这个成员不推荐用了”。它有两个可选参数:说明文字,以及一个 bool 表示”是否当作错误”。

namespace CSharpDemo;

class Demo
{
    [Obsolete("即将移除,请用 NewMethod")]
    public void OldMethod() { }

    [Obsolete("禁止使用", true)]   // true = 编译直接报错
    public void Forbidden() { }
}

// new Demo().Forbidden();   // 编译直接报错
Tip

维护老代码时,[Obsolete] 是温和退场的好工具:先标警告让调用方慢慢改,确认没人用再删。设成 true 则一步到位变编译错误。

[Serializable]:标记可序列化

[Serializable] 标在类上,表示这个类的对象可以被”序列化”(转成字节流保存或传输)。它是最早一批内置特性之一。

namespace CSharpDemo;

[Serializable]
class Config
{
    public string Host = "localhost";
    public int Port = 8080;
}
Note

现代 .NET 更多用 System.Text.Json 等显式序列化器,[Serializable] 在二进制/远程场景仍有用。知道它”是个标签”即可。

特性能贴在哪:目标(target)

特性几乎能贴在任何代码元素上:类、方法、属性、参数,甚至整个程序集。想明确指定贴在哪,用 目标: 特性 的写法。

using System.Reflection;

namespace CSharpDemo;

[assembly: AssemblyTitle("我的示例")]   // 贴在整个程序集上

class Sample
{
    [method: Obsolete("方法过时")]      // 明确贴在方法上
    public void Old() { }
}

常见的目标值有:assembly(程序集)、class/typemethodpropertyfieldparamreturnmodule。不写时,特性默认贴在它紧挨着的那个元素上。

特性也能带参数

特性本质是个类,所以它的构造参数(位置参数)和公开属性(命名参数)都能在方括号里传。位置参数必须写在前、按顺序;命名参数可选、顺序随意。

namespace CSharpDemo;

[MyTag("管理员", Note = "拥有全部权限")]
class Admin { }

class MyTagAttribute : Attribute
{
    public string Note { get; set; }
    public MyTagAttribute(string role) => Role = role;
    public string Role { get; }
}
Note

位置参数对应构造函数参数,命名参数对应属性/字段。这一规则和”方法调用”几乎一致,只是写在方括号里。

多个特性可以叠加

一个元素上能贴多个特性,逐个写在上方或用逗号分隔都行。顺序通常不影响结果。

namespace CSharpDemo;

[Obsolete("已弃用")]
[Serializable]
class Legacy { }

特性 vs 注释 vs 普通代码

方式谁读它能否影响编译/运行
// 注释只有人读不能
普通语句编译器照执行直接执行
特性编译器或反射间接(被读取后才生效)

注释只是给人看的”便利贴”;普通代码是立即执行的指令;特性则是”写进元数据的说明书”——它自己不做事,但能让编译器报警告、让框架按标签装配。这也解释了为什么特性常被称作”声明式编程”的基石。

常见坑与注意事项

Warning

自定义特性时,类必须直接或间接继承 System.Attribute,否则它只是个普通类、方括号里用不了。写 class MyTag : Attribute 是铁律。

Warning

别把”省略后缀”搞反:[Serializable] 等价于 [SerializableAttribute],但你不能凭空发明一个不存在的特性名。只有 .NET 内置或你自己定义的 XxxAttribute 才能这么用。

其它易错点:

  1. 贴错目标:把只该贴在方法上的特性误贴到类上,编译器可能直接报错或根本不生效。不确定时就用 目标: 特性 显式指定。
  2. 忘了读它:贴了特性却没人(编译器/反射)去读,它就只是个安静的标注,不会产生任何效果。
  3. 位置参数顺序错:位置参数必须按构造函数声明的顺序写,混了顺序编译会报错。

常见内置特性清单

除了已讲的 [Obsolete][Serializable],.NET 还内置很多:

特性作用
Obsolete标过时
Serializable标可序列化
Flags枚举按位组合
Conditional按编译符号裁剪调用
DllImport调用非托管函数
AttributeUsage限制自定义特性的使用范围

下一章会挑几个高频的细讲,这里先混个脸熟。

特性到底被谁读取

特性自己不会”主动做事”。它被写进元数据后,要么被编译器读(如 Obsolete 产生警告),要么被运行时通过反射读(如框架扫描你的标签)。理解”谁来读”很重要:贴了特性却没人读,它就只是个安静的标注。

Tip

自定义特性本身也是类(继承 Attribute)。想自己写一个,只需 class XxxAttribute : Attribute,下一章的 AttributeUsage 就是控制它的开关。

适用场景一览

  • 给编译器提示:[Obsolete]、可空注解([NotNull] 等)。
  • 给序列化器看:[Serializable][JsonPropertyName]
  • 给测试/框架看:单元测试的 [Fact]、Web 框架的路由特性。
  • 给自己的工具看:自定义 [Plugin][Authorize] 等,配合反射做通用逻辑。

简单说:凡是”想给代码贴说明、又不希望它变成一条具体执行语句”的地方,特性就派上用场。

小结

特性是用 [ ] 写的”元数据标签”,编译进程序集,被编译器或反射读取而不直接执行。[Obsolete] 标过时、[Serializable] 标可序列化,名字可省 Attribute 后缀,还能用 目标: 指定贴的位置。下一章我们看几个最常用的内置特性怎么玩。