第一部分:特性是什么?(类比贴标签)
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 注意事项
-
特性只是元数据:它们不会改变代码逻辑,只是添加信息
-
需要反射读取:要使用特性信息,需要通过反射
-
性能考虑:反射比较慢,不要在频繁调用的地方使用
-
编译时特性 :有些特性(如
Conditional)是给编译器看的
总结对比
| 用途 | 类比 | 代码示例 |
|---|---|---|
| 标记过时方法 | 贴"过期"标签 | [Obsolete] |
| 条件编译 | 写"仅调试使用" | [Conditional] |
| 序列化控制 | 标"可存档" | [Serializable] |
| 添加文档 | 写备注 | 自定义文档特性 |
| 权限控制 | 贴"权限等级" | 自定义角色特性 |
关键理解:
-
特性就像代码的标签和备注
-
不会直接影响代码运行,但可以通过反射获取这些信息
-
内置特性解决常见问题,自定义特性解决特定问题
-
特性让代码更加声明式和自描述
从最简单的[Obsolete]开始,逐步理解特性如何工作,然后创建自己的特性来解决实际问题,这是掌握C#特性的最佳路径。