C#特性(Attributes)详解

第一部分:特性是什么?(类比贴标签)

1.1 最简单的理解

想象一下你在图书馆看书,你可能会:

  • 在重要的页面贴书签(标记重要内容)

  • 在书封面上贴标签(如"新书"、"推荐")

  • 在书的扉页写备注(如"张三借阅")

C#特性就像这些书签、标签和备注,它们为代码添加额外的信息。

1.2 实际代码对比

csharp

复制代码
// 没有特性:只有代码本身
public void Calculate()
{
    int result = 1 + 2;
    Console.WriteLine(result);
}

// 有特性:为代码添加额外信息
[Obsolete("这个方法已弃用,请使用新版CalculateNew")]
public void Calculate()
{
    int result = 1 + 2;
    Console.WriteLine(result);
}

上面代码中,[Obsolete]就像贴在方法上的"注意"标签,告诉开发者这个方法过时了。

第二部分:内置特性的实际应用

2.1 最常用的三个内置特性

示例1:Obsolete(过时警告)

csharp

复制代码
class Calculator
{
    // 旧版本 - 不推荐使用
    [Obsolete("请使用新版Add方法", false)]
    public int AddOld(int a, int b)
    {
        return a + b;
    }
    
    // 新版本
    public int Add(int a, int b)
    {
        return a + b;
    }
}

// 使用
class Program
{
    static void Main()
    {
        Calculator calc = new Calculator();
        int result = calc.AddOld(5, 3);  // 这里会显示警告
        int result2 = calc.Add(5, 3);     // 这是推荐方式
    }
}
  • false参数:只是警告,代码还能运行

  • true参数:会报错,代码无法编译

示例2:Conditional(条件编译)

csharp

复制代码
using System.Diagnostics;

class Logger
{
    [Conditional("DEBUG")]  // 只在DEBUG模式下才执行
    public void LogDebug(string message)
    {
        Console.WriteLine($"[DEBUG] {DateTime.Now}: {message}");
    }
    
    public void LogInfo(string message)
    {
        Console.WriteLine($"[INFO] {DateTime.Now}: {message}");
    }
}

// 使用
class Program
{
    static void Main()
    {
        Logger logger = new Logger();
        
        // 如果在DEBUG模式下编译,这行会执行
        // 如果在RELEASE模式下编译,这行代码就像不存在一样
        logger.LogDebug("程序启动");
        
        // 这行无论什么模式都会执行
        logger.LogInfo("程序启动");
    }
}

示例3:Serializable(序列化标记)

csharp

复制代码
[Serializable]  // 告诉.NET:这个类可以转换成字节流保存
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    
    [NonSerialized]  // 告诉.NET:这个字段不要保存
    public string TemporaryData;
}

// 使用
class Program
{
    static void Main()
    {
        Person person = new Person 
        { 
            Name = "张三", 
            Age = 25, 
            TemporaryData = "临时值" 
        };
        
        // 保存到文件(只能保存Name和Age,不会保存TemporaryData)
        // 读取时,TemporaryData会是null或默认值
    }
}

第三部分:如何创建自己的特性

3.1 最简单的自定义特性

csharp

复制代码
// 第一步:创建特性类
// 1. 必须继承System.Attribute
// 2. 按惯例类名以"Attribute"结尾
public class MyNoteAttribute : Attribute
{
    // 可以有一些属性
    public string Note { get; set; }
    public DateTime Created { get; set; }
}

// 第二步:使用特性
[MyNote(Note = "这是一个重要的类", Created = "2024-01-01")]
public class ImportantClass
{
    [MyNote(Note = "核心方法")]
    public void ImportantMethod()
    {
        Console.WriteLine("做一些重要的事情");
    }
}

3.2 添加一些控制

csharp

复制代码
// 限制特性只能用于类和方法
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorInfoAttribute : Attribute
{
    public string Author { get; }
    public string Version { get; set; }
    
    // 构造函数定义必需信息
    public AuthorInfoAttribute(string author)
    {
        Author = author;
    }
}

// 使用
[AuthorInfo("张三", Version = "1.0")]
public class Document
{
    [AuthorInfo("李四")]
    public void Save()
    {
        Console.WriteLine("保存文档");
    }
    
    // 下面这行会报错,因为特性不支持属性
    // [AuthorInfo("王五")]
    // public string Title { get; set; }
}

第四部分:如何读取和使用特性

4.1 基本读取方法

csharp

复制代码
using System;
using System.Reflection;

// 定义特性
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class TodoAttribute : Attribute
{
    public string Task { get; }
    public Priority Priority { get; set; }
    
    public TodoAttribute(string task)
    {
        Task = task;
    }
}

public enum Priority { Low, Medium, High }

// 使用特性
[Todo("优化性能", Priority = Priority.High)]
public class GameEngine
{
    [Todo("添加错误处理")]
    public void LoadLevel(string levelName)
    {
        Console.WriteLine($"加载关卡: {levelName}");
    }
    
    [Todo("实现物理碰撞", Priority = Priority.Medium)]
    public void UpdatePhysics()
    {
        Console.WriteLine("更新物理");
    }
}

// 读取特性
class Program
{
    static void Main()
    {
        // 获取类型
        Type gameType = typeof(GameEngine);
        
        // 读取类上的Todo特性
        var classTodos = gameType.GetCustomAttributes(typeof(TodoAttribute), false);
        foreach (TodoAttribute todo in classTodos)
        {
            Console.WriteLine($"类待办: {todo.Task}, 优先级: {todo.Priority}");
        }
        
        Console.WriteLine("\n方法待办列表:");
        
        // 读取所有方法上的Todo特性
        foreach (MethodInfo method in gameType.GetMethods())
        {
            var methodTodos = method.GetCustomAttributes(typeof(TodoAttribute), false);
            foreach (TodoAttribute todo in methodTodos)
            {
                Console.WriteLine($"方法: {method.Name}");
                Console.WriteLine($"  任务: {todo.Task}");
                Console.WriteLine($"  优先级: {todo.Priority}");
            }
        }
    }
}

4.2 实用的示例:验证用户输入

csharp

复制代码
using System;
using System.Reflection;

// 验证特性
[AttributeUsage(AttributeTargets.Property)]
public class ValidateAttribute : Attribute
{
    public int MinLength { get; set; }
    public int MaxLength { get; set; }
    public bool Required { get; set; }
}

// 用户类
public class User
{
    [Validate(Required = true, MinLength = 2, MaxLength = 50)]
    public string Name { get; set; }
    
    [Validate(Required = true, MinLength = 6)]
    public string Password { get; set; }
    
    [Validate(MinLength = 0, MaxLength = 120)]
    public int Age { get; set; }
}

// 验证器
public class Validator
{
    public static List<string> Validate(object obj)
    {
        List<string> errors = new List<string>();
        Type type = obj.GetType();
        
        // 检查所有属性
        foreach (PropertyInfo property in type.GetProperties())
        {
            // 获取Validate特性
            ValidateAttribute validate = property.GetCustomAttribute<ValidateAttribute>();
            
            if (validate != null)
            {
                object value = property.GetValue(obj);
                string propertyName = property.Name;
                
                // 检查必填
                if (validate.Required)
                {
                    if (value == null || string.IsNullOrEmpty(value.ToString()))
                    {
                        errors.Add($"{propertyName} 不能为空");
                        continue;
                    }
                }
                
                // 检查字符串长度
                if (value is string strValue)
                {
                    if (strValue.Length < validate.MinLength)
                        errors.Add($"{propertyName} 长度不能小于 {validate.MinLength}");
                    
                    if (validate.MaxLength > 0 && strValue.Length > validate.MaxLength)
                        errors.Add($"{propertyName} 长度不能大于 {validate.MaxLength}");
                }
                
                // 检查数值范围
                if (value is int intValue)
                {
                    if (intValue < validate.MinLength)
                        errors.Add($"{propertyName} 不能小于 {validate.MinLength}");
                    
                    if (validate.MaxLength > 0 && intValue > validate.MaxLength)
                        errors.Add($"{propertyName} 不能大于 {validate.MaxLength}");
                }
            }
        }
        
        return errors;
    }
}

// 使用
class Program
{
    static void Main()
    {
        User user = new User
        {
            Name = "A",  // 太短
            Password = "123",  // 太短
            Age = 150  // 太大
        };
        
        var errors = Validator.Validate(user);
        
        if (errors.Count > 0)
        {
            Console.WriteLine("验证失败:");
            foreach (string error in errors)
            {
                Console.WriteLine($"  - {error}");
            }
        }
        else
        {
            Console.WriteLine("验证通过");
        }
    }
}

第五部分:逐步练习

练习1:文档生成器

csharp

复制代码
// 创建文档特性
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property)]
public class DocumentationAttribute : Attribute
{
    public string Description { get; }
    
    public DocumentationAttribute(string description)
    {
        Description = description;
    }
}

// 标记代码
[Documentation("表示一个用户账户")]
public class UserAccount
{
    [Documentation("用户名,必须是唯一的")]
    public string Username { get; set; }
    
    [Documentation("获取用户欢迎信息")]
    public string GetWelcomeMessage()
    {
        return $"欢迎, {Username}!";
    }
}

// TODO: 写一个程序读取这些文档信息,生成API文档

练习2:权限控制

csharp

复制代码
// 权限特性
public enum UserRole { Guest, User, Admin }

[AttributeUsage(AttributeTargets.Method)]
public class RequireRoleAttribute : Attribute
{
    public UserRole RequiredRole { get; }
    
    public RequireRoleAttribute(UserRole role)
    {
        RequiredRole = role;
    }
}

// 使用
public class AdminPanel
{
    [RequireRole(UserRole.Admin)]
    public void DeleteUser(string username)
    {
        Console.WriteLine($"删除用户: {username}");
    }
    
    [RequireRole(UserRole.User)]
    public void ChangePassword(string newPassword)
    {
        Console.WriteLine("修改密码");
    }
}

// TODO: 写一个安全检查器,在执行方法前检查用户权限

第六部分:特性使用技巧和注意事项

6.1 技巧

csharp

复制代码
// 1. 多个特性可以叠加
[Serializable]
[AuthorInfo("张三")]
[Todo("添加序列化测试")]
public class MyClass { }

// 2. 可以缩写,去掉"Attribute"后缀
[AuthorInfo("李四")]  // 等同于 [AuthorInfoAttribute("李四")]

// 3. 可以放在同一行
[Serializable][AuthorInfo("张三")] public class MyClass { }

6.2 注意事项

  1. 特性只是元数据:它们不会改变代码逻辑,只是添加信息

  2. 需要反射读取:要使用特性信息,需要通过反射

  3. 性能考虑:反射比较慢,不要在频繁调用的地方使用

  4. 编译时特性 :有些特性(如Conditional)是给编译器看的

总结对比

用途 类比 代码示例
标记过时方法 贴"过期"标签 [Obsolete]
条件编译 写"仅调试使用" [Conditional]
序列化控制 标"可存档" [Serializable]
添加文档 写备注 自定义文档特性
权限控制 贴"权限等级" 自定义角色特性

关键理解

  1. 特性就像代码的标签和备注

  2. 不会直接影响代码运行,但可以通过反射获取这些信息

  3. 内置特性解决常见问题,自定义特性解决特定问题

  4. 特性让代码更加声明式和自描述

从最简单的[Obsolete]开始,逐步理解特性如何工作,然后创建自己的特性来解决实际问题,这是掌握C#特性的最佳路径。

相关推荐
历程里程碑3 小时前
C++ 9 stack_queue:数据结构的核心奥秘
java·开发语言·数据结构·c++·windows·笔记·算法
csbysj20203 小时前
JavaScript AI 编程助手
开发语言
t198751283 小时前
基于MATLAB的线性判别分析(LDA)降维算法实现方案
开发语言·算法·matlab
weixin_462446233 小时前
nodejs 下使用 Prettier 美化单个 JS 文件(完整教程)
开发语言·javascript·ecmascript
醇氧3 小时前
【Windows】从守护到终结:解析一个 Java 服务的优雅停止脚本
java·开发语言·windows
reasonsummer3 小时前
【办公类-18-07】20251215(Python)“口腔检查涂氟信息”批量生成打印(区名、学号、姓名、学校、班级、身份证、户籍、性别、民族)
开发语言·python
小鹿学程序3 小时前
FileZilla连接到虚拟机
java·服务器·开发语言
未来魔导3 小时前
Gin版本的路由总结
开发语言·llm·gin·路由
周杰伦_Jay3 小时前
【Eino框架】Go语言驱动的LLM应用开发新范式
开发语言·后端·golang