C# 特性(Attribute)详解
一、什么是特性?
1.1 生活比喻
特性(Attribute)就像贴在东西上的便利贴:
你在一个盒子上贴一张便利贴写着"易碎品"——盒子本身没变化,但快递员看到这个标签就知道要轻拿轻放。
你在一个方法上贴[Obsolete]——方法本身没变化,但编译器看到这个标签就会给出警告。
普通便利贴: "易碎品" → 快递员看到后轻拿轻放
C# 特性: [Obsolete] → 编译器看到后警告"这个方法已过时"
1.2 一句话理解
特性 = 给代码元素(类、方法、属性等)添加的"标签",用来提供额外的信息。这些标签可以被编译器、运行时或其他工具读取,从而影响行为。
1.3 你其实早就见过了
[Serializable] // 标记这个类可以被序列化
class Person { }
[Obsolete("用 NewMethod 代替")] // 标记这个方法已过时
void OldMethod() { }
[TestMethod] // 标记这是一个测试方法
public void Test1() { }
[Flags] // 标记这个枚举支持组合
enum Permission { Read = 1, Write = 2, Execute = 4 }
[STAThread] // 标记主线程是 STA 模式(WinForms 需要)
static void Main() { }
二、内置的常用特性
2.1 [Obsolete] —— 标记过时
class Calculator
{
// 标记为过时,调用时出现警告
[Obsolete]
public int AddOld(int a, int b)
{
return a + b;
}
// 标记为过时,给出提示消息
[Obsolete("这个方法已过时,请使用 Add 代替")]
public int AddOld2(int a, int b)
{
return a + b;
}
// 标记为过时,调用时报错(编译不通过)
[Obsolete("这个方法已删除,请使用 Add", true)] // true = 报错
public int AddOld3(int a, int b)
{
return a + b;
}
public int Add(int a, int b) => a + b;
}
// 使用
static void Main()
{
Calculator calc = new Calculator();
calc.AddOld(1, 2); // ⚠️ 警告:'AddOld' is obsolete
calc.AddOld2(1, 2); // ⚠️ 警告:这个方法已过时,请使用 Add 代替
// calc.AddOld3(1, 2); // ❌ 编译错误!
calc.Add(1, 2); // ✅
}
2.2 [Serializable] —— 标记可序列化
[Serializable] // 必须加这个才能被序列化
public class Person
{
public string Name;
public int Age;
[NonSerialized] // 这个字段不序列化
public string Password;
}
2.3 [Flags] —— 枚举组合标记
// 没有 [Flags] —— ToString 输出数字而不是名字组合
enum Permission1 { Read = 1, Write = 2, Execute = 4 }
// 有 [Flags] —— ToString 输出名字组合
[Flags]
enum Permission2 { Read = 1, Write = 2, Execute = 4 }
Permission1 p1 = Permission1.Read | Permission1.Write;
Console.WriteLine(p1); // 3 ← 输出数字
Permission2 p2 = Permission2.Read | Permission2.Write;
Console.WriteLine(p2); // Read, Write ← 输出名字组合
2.4 [Conditional] —— 条件编译
// 只在 DEBUG 模式下编译的方法
[Conditional("DEBUG")]
static void DebugLog(string message)
{
Console.WriteLine($"[DEBUG] {message}");
}
static void Main()
{
DebugLog("程序开始"); // DEBUG 模式下有输出,RELEASE 模式下直接被删掉
DebugLog("处理中..."); // 不仅不执行,连调用语句都从编译结果中移除了
}
2.5 [CallerInfo] 系列 —— 获取调用者信息
static void Log(
string message,
[CallerMemberName] string memberName = "", // 谁调用了这个方法
[CallerFilePath] string filePath = "", // 哪个文件
[CallerLineNumber] int lineNumber = 0) // 哪一行
{
Console.WriteLine($"[{filePath}:{lineNumber}] {memberName}: {message}");
}
// 使用
static void DoSomething()
{
Log("开始处理"); // 不用传文件名、行号,框架自动填!
// 输出: [D:\Code\Program.cs:45] DoSomething: 开始处理
}
三、自定义特性
3.1 定义一个特性类
// 1. 定义一个特性类——继承自 Attribute
// 2. 类名必须以 Attribute 结尾(约定)
// 3. 用 [AttributeUsage] 限制它能用在什么地方
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class DeveloperInfoAttribute : Attribute
{
public string Name { get; set; }
public string Date { get; set; }
public int Version { get; set; }
// 构造函数
public DeveloperInfoAttribute(string name, string date)
{
Name = name;
Date = date;
}
}
3.2 使用自定义特性
// 贴在类上
[DeveloperInfo("张三", "2024-01-15", Version = 1)]
public class UserManager
{
// 贴在方法上
[DeveloperInfo("李四", "2024-02-20", Version = 2)]
public void Register(User user)
{
Console.WriteLine("注册用户...");
}
// 贴在属性上
[DeveloperInfo("王五", "2024-03-10", Version = 1)]
public int MaxUsers { get; set; }
}
// 使用特性时,[DeveloperInfo(...)] 可以简写为 [DeveloperInfo(...)]
// 因为 C# 会自动在类名后加 "Attribute" 去匹配
3.3 使用反射读取特性
using System;
using System.Reflection;
class Program
{
static void Main()
{
// 1. 读取类上的特性
Type type = typeof(UserManager);
var classAttr = type.GetCustomAttribute<DeveloperInfoAttribute>();
if (classAttr != null)
{
Console.WriteLine($"类作者: {classAttr.Name}");
Console.WriteLine($"创建日期: {classAttr.Date}");
Console.WriteLine($"版本: {classAttr.Version}");
}
// 2. 读取方法上的特性
MethodInfo method = type.GetMethod("Register");
var methodAttr = method.GetCustomAttribute<DeveloperInfoAttribute>();
if (methodAttr != null)
{
Console.WriteLine($"\n方法作者: {methodAttr.Name}");
Console.WriteLine($"方法日期: {methodAttr.Date}");
}
// 3. 读取所有特性
Console.WriteLine("\n=== 所有特性 ===");
foreach (var attr in type.GetCustomAttributes())
{
Console.WriteLine($" {attr.GetType().Name}");
}
}
}
输出:
类作者: 张三
创建日期: 2024-01-15
版本: 1
方法作者: 李四
方法日期: 2024-02-20
=== 所有特性 ===
DeveloperInfoAttribute
四、AttributeUsage —— 控制特性的使用范围
4.1 AttributeUsage 的完整语法
[AttributeUsage(
AttributeTargets.Class | AttributeTargets.Method, // 可以用在哪些地方
AllowMultiple = true, // 是否允许多个相同特性
Inherited = true // 子类是否继承这个特性
)]
public class MyAttribute : Attribute { }
4.2 AttributeTargets —— 特性可以用在哪些地方
| 目标 | 说明 |
|---|---|
AttributeTargets.All |
所有地方 |
AttributeTargets.Class |
类 |
AttributeTargets.Struct |
结构体 |
AttributeTargets.Interface |
接口 |
AttributeTargets.Method |
方法 |
AttributeTargets.Property |
属性 |
AttributeTargets.Field |
字段 |
AttributeTargets.Enum |
枚举 |
AttributeTargets.Parameter |
方法参数 |
AttributeTargets.Constructor |
构造函数 |
AttributeTargets.Assembly |
程序集 |
AttributeTargets.ReturnValue |
返回值 |
4.3 AllowMultiple —— 是否允许多个
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class TagAttribute : Attribute
{
public string TagName { get; }
public TagAttribute(string tag) => TagName = tag;
}
// ✅ AllowMultiple = true 时,可以用多个
[Tag("重要")]
[Tag("高优先级")]
[Tag("待审核")]
public class Order { }
4.4 Inherited —— 子类是否继承
[AttributeUsage(AttributeTargets.Class, Inherited = true)]
public class BaseInfoAttribute : Attribute
{
public string Info { get; }
public BaseInfoAttribute(string info) => Info = info;
}
[BaseInfo("基类信息")]
class BaseClass { }
// Inherited = true → 子类自动继承 [BaseInfo]
class DerivedClass : BaseClass { }
// 读取
var attr = typeof(DerivedClass).GetCustomAttribute<BaseInfoAttribute>();
Console.WriteLine(attr?.Info); // "基类信息" ← 继承了!
五、特性中的参数类型
public class ConfigAttribute : Attribute
{
// 构造函数参数(位置参数——使用时必须提供)
public string Name { get; } // 只读,构造函数中赋值
public int Version { get; }
// 属性参数(命名参数——使用时可选)
public string Description { get; set; }
public bool Enabled { get; set; } = true; // 默认值
public ConfigAttribute(string name, int version)
{
Name = name;
Version = version;
}
}
// 使用
[Config("数据库配置", 2, Description = "主数据库连接", Enabled = true)]
// └─ 位置参数 ─┘ └──────── 命名参数 ──────────┘
// 位置参数必须按顺序写,命名参数随便写
// 可以省略命名参数(用默认值)
[Config("日志配置", 1)] // Description 为空,Enabled = true
特性参数的限制:
- 只能是基本类型(int、string、bool、Type、枚举等)
- 不能是自定义类
- 不能是
object、List<T>等复杂类型 - 这些值必须在编译时就能确定
六、常见的预定义特性速查
| 特性 | 作用 | 用法 |
|---|---|---|
[Obsolete] |
标记过时 | [Obsolete("消息", error)] |
[Serializable] |
可序列化 | 放在 class 上 |
[NonSerialized] |
跳过序列化 | 放在字段上 |
[Flags] |
标记枚举可组合 | 放在 enum 上 |
[Conditional] |
条件编译 | 放在方法上 |
[DebuggerDisplay] |
调试时显示格式 | [DebuggerDisplay("Name={Name}")] |
[AttributeUsage] |
限制特性用法 | 放在特性类上 |
[TestMethod] |
标记测试方法 | 单元测试用 |
[DataContract] |
数据契约 | WCF 序列化 |
[DataMember] |
数据成员 | WCF 序列化 |
[DllImport] |
调用系统 API | [DllImport("user32.dll")] |
七、完整实战示例——权限验证系统
using System;
using System.Collections.Generic;
using System.Reflection;
// ===== 1. 定义权限枚举 =====
[Flags]
public enum Permission
{
None = 0,
Read = 1,
Write = 2,
Delete = 4,
Admin = Read | Write | Delete
}
// ===== 2. 定义"需要权限"特性 =====
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class RequirePermissionAttribute : Attribute
{
public Permission Required { get; }
public RequirePermissionAttribute(Permission required)
{
Required = required;
}
}
// ===== 3. 定义"日志记录"特性 =====
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class LogAttribute : Attribute
{
public string Message { get; }
public LogAttribute(string message) => Message = message;
}
// ===== 4. 业务类——使用特性 =====
public class DocumentManager
{
[RequirePermission(Permission.Read)]
[Log("查看文档列表")]
public void ViewDocuments()
{
Console.WriteLine(" → 执行:查看文档列表");
}
[RequirePermission(Permission.Write)]
[Log("创建新文档")]
public void CreateDocument(string title)
{
Console.WriteLine($" → 执行:创建文档《{title}》");
}
[RequirePermission(Permission.Delete)]
[Log("删除文档")]
public void DeleteDocument(int id)
{
Console.WriteLine($" → 执行:删除文档 #{id}");
}
[RequirePermission(Permission.Admin)]
[Log("系统管理")]
[Log("修改配置")] // 多个 Log 特性
public void ManageSystem()
{
Console.WriteLine(" → 执行:系统管理");
}
// 没有 [RequirePermission] —— 不需要权限
[Log("帮助")]
public void ShowHelp()
{
Console.WriteLine(" → 执行:显示帮助");
}
}
// ===== 5. 权限检查框架 =====
public class PermissionChecker
{
// 反射读取特性,检查权限并记录日志
public static void Execute(object target, string methodName,
params object[] args)
{
Type type = target.GetType();
MethodInfo method = type.GetMethod(methodName);
if (method == null)
{
Console.WriteLine($"错误:找不到方法 {methodName}");
return;
}
// 检查权限
var permAttr = method.GetCustomAttribute<RequirePermissionAttribute>();
if (permAttr != null)
{
// 假设当前用户只有 Read 权限
Permission currentUser = Permission.Read;
if (!currentUser.HasFlag(permAttr.Required))
{
Console.WriteLine($"权限不足!需要 {permAttr.Required}");
return;
}
}
// 记录日志
var logAttrs = method.GetCustomAttributes<LogAttribute>();
foreach (var log in logAttrs)
{
Console.WriteLine($" 📝 日志: {log.Message}");
}
// 执行方法
try
{
method.Invoke(target, args);
}
catch (Exception ex)
{
Console.WriteLine($"执行错误: {ex.InnerException?.Message}");
}
}
}
// ===== 6. 测试 =====
class Program
{
static void Main()
{
Console.WriteLine("===== 权限验证系统 — 特性演示 =====\n");
var dm = new DocumentManager();
Console.WriteLine("--- 有权限的操作 ---");
PermissionChecker.Execute(dm, "ViewDocuments"); // ✅ Read 权限够
Console.WriteLine("\n--- 无权限的操作 ---");
PermissionChecker.Execute(dm, "CreateDocument", "新增文档"); // ❌ 需要 Write
Console.WriteLine("\n--- 删除操作 ---");
PermissionChecker.Execute(dm, "DeleteDocument", 123); // ❌ 需要 Delete
Console.WriteLine("\n--- 不需要权限的操作 ---");
PermissionChecker.Execute(dm, "ShowHelp"); // ✅ 不需要权限
Console.WriteLine("\n--- 管理员操作 ---");
PermissionChecker.Execute(dm, "ManageSystem"); // ❌ 需要 Admin
}
}
输出:
===== 权限验证系统 — 特性演示 =====
--- 有权限的操作 ---
📝 日志: 查看文档列表
→ 执行:查看文档列表
--- 无权限的操作 ---
权限不足!需要 Write
--- 删除操作 ---
权限不足!需要 Delete
--- 不需要权限的操作 ---
📝 日志: 帮助
→ 执行:显示帮助
--- 管理员操作 ---
权限不足!需要 Admin
八、完整实战示例二——简单 ORM 框架
using System;
using System.Reflection;
// ===== 1. 定义"表名"特性 =====
[AttributeUsage(AttributeTargets.Class)]
public class TableAttribute : Attribute
{
public string Name { get; }
public TableAttribute(string name) => Name = name;
}
// ===== 2. 定义"列名"特性 =====
[AttributeUsage(AttributeTargets.Property)]
public class ColumnAttribute : Attribute
{
public string Name { get; }
public bool IsPrimaryKey { get; set; }
public ColumnAttribute(string name) => Name = name;
}
// ===== 3. 实体类——使用特性 =====
[Table("Users")]
public class User
{
[Column("Id", IsPrimaryKey = true)]
public int UserId { get; set; }
[Column("UserName")]
public string Name { get; set; }
[Column("Age")]
public int Age { get; set; }
[Column("Email")]
public string Email { get; set; }
}
// ===== 4. 简单的 SQL 生成器(用反射读特性) =====
public class SqlGenerator
{
// 生成 SELECT 语句
public static string GenerateSelect<T>()
{
Type type = typeof(T);
var tableAttr = type.GetCustomAttribute<TableAttribute>();
string tableName = tableAttr?.Name ?? type.Name;
List<string> columns = new List<string>();
string primaryKey = null;
foreach (PropertyInfo prop in type.GetProperties())
{
var colAttr = prop.GetCustomAttribute<ColumnAttribute>();
if (colAttr != null)
{
columns.Add($" {colAttr.Name}");
if (colAttr.IsPrimaryKey)
primaryKey = colAttr.Name;
}
}
string sql = $"SELECT \n";
sql += string.Join(",\n", columns) + "\n";
sql += $"FROM {tableName}";
if (primaryKey != null)
{
sql += $"\nORDER BY {primaryKey}";
}
return sql;
}
// 生成 INSERT 语句
public static string GenerateInsert<T>(T obj)
{
Type type = typeof(T);
var tableAttr = type.GetCustomAttribute<TableAttribute>();
string tableName = tableAttr?.Name ?? type.Name;
List<string> columns = new List<string>();
List<string> values = new List<string>();
foreach (PropertyInfo prop in type.GetProperties())
{
var colAttr = prop.GetCustomAttribute<ColumnAttribute>();
if (colAttr != null && !colAttr.IsPrimaryKey)
{
columns.Add(colAttr.Name);
object value = prop.GetValue(obj);
values.Add(value is string ? $"'{value}'" : value.ToString());
}
}
return $"INSERT INTO {tableName} ({string.Join(", ", columns)}) \nVALUES ({string.Join(", ", values)})";
}
}
// ===== 5. 测试 =====
class Program
{
static void Main()
{
Console.WriteLine("===== 简单 ORM — 特性演示 =====\n");
Console.WriteLine("【生成的 SELECT 语句】");
string selectSql = SqlGenerator.GenerateSelect<User>();
Console.WriteLine(selectSql);
Console.WriteLine("\n【生成的 INSERT 语句】");
var user = new User { UserId = 1, Name = "张三", Age = 25, Email = "zhangsan@example.com" };
string insertSql = SqlGenerator.GenerateInsert(user);
Console.WriteLine(insertSql);
}
}
输出:
===== 简单 ORM — 特性演示 =====
【生成的 SELECT 语句】
SELECT
Id,
UserName,
Age,
Email
FROM Users
ORDER BY Id
【生成的 INSERT 语句】
INSERT INTO Users (UserName, Age, Email)
VALUES ('张三', 25, 'zhangsan@example.com')
九、常见易错点(避坑指南)
坑1:特性参数只能是在编译时常量
// ❌ 不能传变量
int version = 2;
// [Config("name", version)] // 编译错误!参数必须是常量
// ✅ 必须是字面量或常量
[Config("name", 2)]
坑2:类名省略了 "Attribute" 后缀但反射时要写全
// 定义时类名加 Attribute 后缀
public class MyTestAttribute : Attribute { }
// 使用时可以省略后缀
[MyTest] class Foo { } // ✅
// 反射时两种写法都能找到
typeof(Foo).GetCustomAttribute<MyTestAttribute>(); // ✅
typeof(Foo).GetCustomAttribute<MyTest>(); // ❌ 不存在 MyTest 类
// 反射时必须用全名!
坑3:AllowMultiple = false 时重复使用不会报错
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class OnlyOnceAttribute : Attribute { }
[OnlyOnce]
// [OnlyOnce] // ❌ 编译错误!不允许重复使用
public class Test { }
坑4:忘记加 [AttributeUsage] 导致特性用在了不该用的地方
// ❌ 没有加 AttributeUsage → 可以放在任何地方
public class MyAttribute : Attribute { }
[My] // 可以
class Test
{
[My] // 也可以(但可能不是你想的)
public void Method() { }
}
// ✅ 加上 AttributeUsage 限制
[AttributeUsage(AttributeTargets.Class)]
public class ClassOnlyAttribute : Attribute { }
坑5:特性的构造函数不支持重载参数歧义
public class InfoAttribute : Attribute
{
public InfoAttribute(string name) { }
public InfoAttribute(int id) { }
}
// [Info("test")] // ✅ 可以
// [Info(123)] // ✅ 可以
// 但如果两个构造函数参数类型相同,只是数量不同,就可能产生歧义
坑6:反射读特性有性能开销
// GetCustomAttribute 用到反射,有性能开销
// 如果频繁调用,应该缓存结果
// ❌ 每次都反射
for (int i = 0; i < 10000; i++)
{
var attr = typeof(Test).GetCustomAttribute<MyAttribute>();
}
// ✅ 缓存结果
static readonly MyAttribute _cached = typeof(Test).GetCustomAttribute<MyAttribute>();
for (int i = 0; i < 10000; i++)
{
var attr = _cached; // 直接用缓存
}
十、总结
特性的本质
特性(Attribute) = 贴在代码元素上的标签
= 不直接影响代码运行
= 通过反射在运行时读取
= 可以被编译器/框架/工具识别
定义: class MyAttribute : Attribute { }
使用: [MyAttribute] 或 [My](省略后缀)
[My(param, param, Prop = value)]
读取: typeof(X).GetCustomAttribute<MyAttribute>()
特性 vs 注释
| 维度 | 注释(//) | 特性([Attribute]) |
|---|---|---|
| 谁读 | 人类 | 编译器/运行时/工具 |
| 影响代码 | 完全不影响 | 可能影响编译或运行时行为 |
| 可以被程序读取 | ❌ | ✅(反射) |
| 示例 | // TODO: 优化 |
[Obsolete], [Serializable] |
记忆口诀
特性就像便利贴,贴在代码做标注
Obsolete 标过时,Flags 枚举能组合
Serializable 能序列,Conditional 条件编
自定义特性三步走:
继承 Attribute 起好名,AttributeUsage 限范围
反射读取 GetCustom,运行时里拿标签
一句话总结:特性(Attribute)就是给代码贴的"标签",定义时继承
Attribute类,使用时用[...]语法贴上去,运行时通过反射读取。它不直接改变代码行为,但编译器和框架会读取特性来做额外处理(如[Obsolete]让编译器报警告,[TestMethod]让测试框架识别测试方法)。