目 录CONTENT

文章目录

CSharp(五十七) 特性(Attribute)详解

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、枚举等)
  • 不能是自定义类
  • 不能是 objectList<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] 让测试框架识别测试方法)。

0
博主关闭了当前页面的评论