C# 特性(Attributes)实战指南:从基础到高级应用

news2026/4/20 2:33:52
1. 初识C#特性不只是“装饰”的代码标签很多刚接触C#的朋友第一次看到代码里那些用方括号[]包起来的东西比如[Serializable]或者[Obsolete]可能会有点懵。这玩意儿是注释吗还是什么特殊的语法我刚开始学的时候也这么想过后来在项目里被它“坑”过几次又用它解决过不少头疼的问题才真正体会到它的妙处。你可以把它想象成给你写的类、方法或者属性贴上一个“智能标签”。这个标签不只是给人看的更重要的是给编译器、运行时环境或者你用的各种框架比如ASP.NET Core、Entity Framework看的。它们读到这些标签就知道该对这个代码元素做什么特殊处理。举个最生活化的例子你网购时收到的包裹上除了收件人信息是不是还贴着“易碎品”、“向上”、“生鲜”这类标签快递员看到“易碎品”处理起来就会格外小心看到“生鲜”就会优先配送。C#的特性Attribute干的就是类似的事儿。你给一个类贴上[Serializable]标签就等于告诉序列化工具“这个类可以安全地打包成字节流放心处理吧。”你给一个方法贴上[Obsolete]就等于在代码里立了个“前方施工请绕行”的牌子编译器看到就会提醒其他开发者“嘿这个方法快过时了别再用啦”所以特性本质上是一种声明式的编程方式。什么叫声明式就是你只需要声明“我想要什么”What而不需要详细写出“具体怎么做”How。比如你想让一个方法只在调试时输出日志你不用写一堆#if DEBUG ... #endif的条件编译代码只需要在方法上轻轻松松地加上一行[Conditional(DEBUG)]。至于怎么判断、何时调用那是编译器和运行时去操心的事。这种写法让代码变得异常简洁和清晰把那些繁琐的、重复的“管道”代码和你的核心业务逻辑分离开来。我后来做项目架构设计时特别喜欢用特性来处理像权限校验、日志记录、性能监控这些“横切关注点”代码干净得让人心情舒畅。2. 玩转内置特性站在巨人的肩膀上C#和.NET框架给我们准备了一整套开箱即用的内置特性覆盖了日常开发中的大多数常见场景。用好它们能让你少写很多代码避免很多低级错误。咱们挑几个最常用、最实用的结合我踩过的坑和实战心得好好聊一聊。2.1 基础必备每个C#开发者都应掌握的利器[Obsolete]- 你的代码升级导航员这个特性我愿称之为“团队协作神器”。当你要重构一个旧方法但又不能立刻删掉它因为还有很多地方在用或者你想推荐一个更好的新方法时[Obsolete]就派上用场了。public class PaymentService { // 第一版的老方法计算方式复杂 [Obsolete(此方法计算逻辑已过时请使用更精确的 CalculateTotalV2 方法。)] public decimal CalculateTotal(decimal price, int quantity) { return price * quantity; // 老逻辑没考虑税费 } // 第二版的新方法考虑了税费 public decimal CalculateTotalV2(decimal price, int quantity, decimal taxRate) { return price * quantity * (1 taxRate); } }当你或你的同事在代码里调用那个老的CalculateTotal方法时编译器会立刻给出一个警告并且把你写的那句提示信息显示出来。这比在开会时吼一嗓子“那个方法别用了”要有效得多。如果你确定老方法必须立刻废弃甚至可以把它升级成编译错误[Obsolete(此方法存在安全漏洞已禁止使用。请立即迁移到新方法。, true)] public void OldUnsafeMethod() { }加上error: true参数后任何使用该方法的地方都会直接导致编译失败强制团队进行升级。我在推动一个大型遗留系统升级时就用这招平稳地把几十个危险的老接口给替换掉了。[Conditional]- 调试与发布环境的智能开关这个特性对于管理调试日志特别有用。我们经常会在代码里写很多Console.WriteLine或者Debug.WriteLine来帮助排查问题但肯定不希望这些日志在发布版本中输出既影响性能还可能泄露敏感信息。using System.Diagnostics; public class AppLogger { [Conditional(DEBUG)] public void LogDebug(string message) { Console.WriteLine($[DEBUG] {DateTime.Now}: {message}); } [Conditional(TRACE)] public void LogTrace(string message) { // 更详细的跟踪信息可能记录到文件 File.AppendAllText(trace.log, $[TRACE] {message}\n); } public void LogError(string message) // 这个始终会编译 { Console.WriteLine($[ERROR] {message}); } }在Visual Studio里你可以在项目属性 - 生成 - 条件编译符号中定义DEBUG或TRACE。当你以Debug模式编译时LogDebug方法会被编译进去切换到Release模式这个方法就像被“剪掉”了一样完全不存在于最终的程序集中对性能零影响。这比手动用#if DEBUG包裹代码块要优雅和清晰得多。[Flags]- 让枚举化身为“多选开关”这是处理权限、状态集合等场景的绝佳工具。没有[Flags]的枚举一个变量只能表示一种状态。有了它一个变量可以同时表示多种状态的组合。[Flags] public enum FileAccessPermissions { None 0, // 0b0000 Read 1, // 0b0001 Write 2, // 0b0010 Execute 4, // 0b0100 Delete 8, // 0b1000 // 可以方便地定义常用组合 ReadWrite Read | Write, // 0b0011 (3) FullControl Read | Write | Execute | Delete // 0b1111 (15) } // 使用起来非常直观 FileAccessPermissions userPermissions FileAccessPermissions.Read | FileAccessPermissions.Write; // 检查是否拥有某项权限 if (userPermissions.HasFlag(FileAccessPermissions.Read)) { Console.WriteLine(用户可以读取文件。); } // 添加一项权限 userPermissions | FileAccessPermissions.Execute; // 移除一项权限 userPermissions ~FileAccessPermissions.Write;关键点在于枚举项的值必须是2的幂1,2,4,8...这样每个值在二进制表示中都只有一个位是1进行位运算|、时就不会互相干扰。我曾在做一个配置管理系统时用[Flags]枚举来管理上百个可配置模块的启用状态代码清晰运算高效。2.2 数据序列化的好帮手[Xml*]与[Json*]家族在Web API和微服务大行其道的今天对象和JSON/XML之间的序列化反序列化是家常便饭。内置的特性可以让你精细地控制这个过程。控制JSON序列化假设你有一个C#的模型类但需要对接一个使用蛇形命名snake_case的第三方APIusing System.Text.Json.Serialization; public class UserProfile { // C#属性名是PascalCase但序列化成JSON时变成snake_case [JsonPropertyName(user_id)] public int UserId { get; set; } [JsonPropertyName(full_name)] public string FullName { get; set; } // 这个属性在序列化时完全被忽略比如密码字段 [JsonIgnore] public string PasswordHash { get; set; } // 可以控制顺序某些严格的API要求字段顺序 [JsonPropertyOrder(1)] public string Username { get; set; } }使用System.Text.Json序列化时var user new UserProfile { UserId 123, FullName 张三, PasswordHash secret, Username zhangsan }; string json JsonSerializer.Serialize(user); // 输出: {user_id:123,full_name:张三,username:zhangsan} // 注意PasswordHash 不见了字段顺序也变了控制XML序列化如果你还在处理一些老的SOAP服务或者XML配置文件这些特性就更有用了using System.Xml.Serialization; [XmlRoot(Employee)] // 指定XML根元素名 public class Person { [XmlAttribute(id)] // 作为XML属性输出 public int Id { get; set; } [XmlElement(Name)] // 指定元素名 public string FullName { get; set; } [XmlIgnore] // 忽略这个属性 public DateTime DateOfBirth { get; set; } // 如果属性是数组或列表可以用 XmlArray [XmlArray(Projects)] [XmlArrayItem(Project)] public Liststring ProjectList { get; set; } new Liststring(); }3. 创造你自己的特性释放元数据编程的威力当内置特性无法满足你的特殊需求时就该自己动手打造了。自定义特性就像给你的项目打造一套专属的“标签体系”让代码能承载更丰富的业务语义。3.1 手把手创建第一个自定义特性咱们从一个最实际的需求出发给团队写的API方法添加负责人和版本信息。这样在生成文档或者排查问题时一眼就能知道这个方法是谁写的、属于哪个版本。第一步定义特性类所有自定义特性都必须继承自System.Attribute类。按照约定类名以Attribute结尾但使用时可以省略。using System; // AttributeUsage 是关键它定义了我们的特性可以用在什么地方 [AttributeUsage(AttributeTargets.Method, // 只能用在方法上 AllowMultiple false, // 同一个方法上不能用两次 Inherited true)] // 派生类重写的方法也会继承这个特性 public class AuthorInfoAttribute : Attribute { // 特性可以有自己的属性这些就是“标签”上的信息 public string Author { get; } public string Email { get; set; } // 可选的setter public Version Version { get; set; } // 构造函数参数对应的是使用特性时必须提供的信息 public AuthorInfoAttribute(string author) { Author author ?? throw new ArgumentNullException(nameof(author)); Version new Version(1, 0, 0); // 设置默认版本 } }这里重点说一下[AttributeUsage]它有三个重要参数AttributeTargets指定特性可以应用的目标。可以是Class、Method、Property、Field、Assembly等可以用|运算符组合多个目标比如AttributeTargets.Class | AttributeTargets.Struct。AllowMultiple如果设为true意味着你可以在同一个目标上多次应用同一个特性。比如你可以给一个方法打上多个[Tag(重要)]、[Tag(优化)]标签。Inherited如果设为true当特性应用在基类或虚方法上时派生类或重写方法也会继承这个特性。对于记录作者信息我们通常希望继承。第二步使用特性使用起来和内置特性一模一样非常直观public class OrderService { [AuthorInfo(王工程师, Email wangexample.com, Version new Version(2, 1))] public void CreateOrder(Order order) { // 创建订单的业务逻辑 } [AuthorInfo(李架构师)] [Obsolete(请使用新的 ProcessOrderV2 方法)] public void ProcessOrder(int orderId) { // 老的处理逻辑 } }3.2 通过反射读取特性信息让标签“活”起来特性本身只是静态的元数据它的魔力需要通过反射Reflection来激发。反射可以在运行时检查类型、读取特性信息从而动态地改变程序行为。接上面的例子我们可以写一个简单的文档生成器using System; using System.Linq; using System.Reflection; public static class ApiDocumentGenerator { public static void GenerateDocumentation(Type serviceType) { Console.WriteLine($ {serviceType.Name} 服务API文档 \n); // 获取所有公共实例方法 var methods serviceType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); foreach (var method in methods) { Console.WriteLine($方法: {method.Name}); // 1. 读取 AuthorInfo 特性 var authorAttr method.GetCustomAttributeAuthorInfoAttribute(); if (authorAttr ! null) { Console.WriteLine($ 作者: {authorAttr.Author}); if (!string.IsNullOrEmpty(authorAttr.Email)) Console.WriteLine($ 邮箱: {authorAttr.Email}); Console.WriteLine($ 版本: {authorAttr.Version}); } // 2. 读取 Obsolete 特性 var obsoleteAttr method.GetCustomAttributeObsoleteAttribute(); if (obsoleteAttr ! null) { Console.WriteLine($ ⚠️ 已过时: {obsoleteAttr.Message}); } // 3. 读取参数信息 var parameters method.GetParameters(); if (parameters.Any()) { Console.WriteLine($ 参数:); foreach (var param in parameters) { Console.WriteLine($ - {param.ParameterType.Name} {param.Name}); } } Console.WriteLine(); // 空行分隔 } } } // 使用它 ApiDocumentGenerator.GenerateDocumentation(typeof(OrderService));运行上面的代码你可能会得到类似这样的输出 OrderService 服务API文档 方法: CreateOrder 作者: 王工程师 邮箱: wangexample.com 版本: 2.1 方法: ProcessOrder 作者: 李架构师 版本: 1.0.0.0 ⚠️ 已过时: 请使用新的 ProcessOrderV2 方法 参数: - Int32 orderId你看我们并没有修改OrderService类的任何业务逻辑代码只是通过反射读取了附加在方法上的元数据就自动生成了包含作者、版本、过时警告的API文档。这就是声明式编程的魅力——关注点分离业务代码和元数据描述各司其职。性能提示反射操作是有开销的。如果在高频循环中反复调用GetCustomAttribute可能会成为性能瓶颈。一个常见的优化技巧是缓存结果。例如可以在程序启动时扫描一次所有需要的类型和方法将特性信息缓存到字典里后续直接读取缓存。4. 特性在实战中的高级玩法了解了基础咱们来看看特性在真实项目里能玩出什么花样。这些场景都是我亲身经历过的非常实用。4.1 实现轻量级数据验证框架在Web开发中模型验证是刚需。虽然ASP.NET Core有强大的DataAnnotations但有时我们想在更轻量的场景比如控制台应用、后台服务中做验证。用特性自己造一个轮子能让你对原理理解得更透彻。第一步定义验证特性我们先定义几个常用的验证规则特性// 必填验证 [AttributeUsage(AttributeTargets.Property)] public class RequiredAttribute : Attribute { public string ErrorMessage { get; set; } 字段不能为空。; } // 字符串长度验证 [AttributeUsage(AttributeTargets.Property)] public class StringLengthAttribute : Attribute { public int MaxLength { get; } public int MinLength { get; } public string ErrorMessage { get; set; } public StringLengthAttribute(int maxLength, int minLength 0) { MaxLength maxLength; MinLength minLength; ErrorMessage $长度必须在 {minLength} 到 {maxLength} 个字符之间。; } } // 范围验证用于数字 [AttributeUsage(AttributeTargets.Property)] public class RangeAttribute : Attribute { public double Min { get; } public double Max { get; } public string ErrorMessage { get; set; } public RangeAttribute(double min, double max) { Min min; Max max; ErrorMessage $值必须在 {min} 到 {max} 之间。; } } // 正则表达式验证 [AttributeUsage(AttributeTargets.Property)] public class RegexAttribute : Attribute { public string Pattern { get; } public string ErrorMessage { get; set; } public RegexAttribute(string pattern) { Pattern pattern; ErrorMessage 格式不正确。; } }第二步应用特性到模型定义一个用户注册模型并贴上我们刚创建的验证标签public class UserRegistrationModel { [Required(ErrorMessage 用户名是必填项)] [StringLength(20, 3, ErrorMessage 用户名长度需在3-20个字符之间。)] public string Username { get; set; } [Required] [Regex(^[^\s][^\s]\.[^\s]$, ErrorMessage 请输入有效的电子邮件地址。)] public string Email { get; set; } [Required] [StringLength(100, 6, ErrorMessage 密码长度至少6位。)] public string Password { get; set; } [Range(18, 120, ErrorMessage 年龄需在18至120岁之间。)] public int Age { get; set; } }第三步实现验证器核心就是一个通过反射读取特性并执行验证的静态类using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; public static class SimpleValidator { public static (bool IsValid, Liststring Errors) Validate(object obj) { var errors new Liststring(); var type obj.GetType(); var properties type.GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var prop in properties) { var value prop.GetValue(obj); // 1. 检查 Required var requiredAttr prop.GetCustomAttributeRequiredAttribute(); if (requiredAttr ! null) { if (value null || (value is string str string.IsNullOrWhiteSpace(str))) { errors.Add(${prop.Name}: {requiredAttr.ErrorMessage}); continue; // 如果必填为空跳过后续验证 } } // 2. 检查 StringLength (仅对字符串类型) var stringLengthAttr prop.GetCustomAttributeStringLengthAttribute(); if (stringLengthAttr ! null value is string stringValue) { if (stringValue.Length stringLengthAttr.MinLength || stringValue.Length stringLengthAttr.MaxLength) { errors.Add(${prop.Name}: {stringLengthAttr.ErrorMessage}); } } // 3. 检查 Range (对数值类型) var rangeAttr prop.GetCustomAttributeRangeAttribute(); if (rangeAttr ! null value is IConvertible numericValue) { double doubleValue Convert.ToDouble(numericValue); if (doubleValue rangeAttr.Min || doubleValue rangeAttr.Max) { errors.Add(${prop.Name}: {rangeAttr.ErrorMessage}); } } // 4. 检查 Regex var regexAttr prop.GetCustomAttributeRegexAttribute(); if (regexAttr ! null value is string regexValue !string.IsNullOrEmpty(regexValue)) { if (!Regex.IsMatch(regexValue, regexAttr.Pattern)) { errors.Add(${prop.Name}: {regexAttr.ErrorMessage}); } } } return (!errors.Any(), errors); } }第四步使用验证器现在我们可以在任何地方使用这个通用的验证逻辑了var user new UserRegistrationModel { Username ab, // 太短会触发StringLength错误 Email invalid-email, Password 123, Age 15 // 小于18会触发Range错误 }; var (isValid, errors) SimpleValidator.Validate(user); if (!isValid) { Console.WriteLine(验证失败); foreach (var error in errors) { Console.WriteLine($ - {error}); } } // 输出 // 验证失败 // - Username: 用户名长度需在3-20个字符之间。 // - Email: 请输入有效的电子邮件地址。 // - Password: 密码长度至少6位。 // - Age: 年龄需在18至120岁之间。这个自制的验证框架虽然简单但清晰地展示了特性如何将验证规则元数据与验证逻辑代码解耦。模型类只关心自己有哪些属性以及这些属性应该遵守什么规则验证器只关心如何读取规则并执行检查。两者通过特性这个桥梁优雅地连接在一起。4.2 实现简易的依赖注入标记在小型项目或特定模块中你可能不想引入完整的IoC容器如Autofac、Microsoft.Extensions.DependencyInjection但又想享受依赖注入带来的松耦合好处。用特性可以快速实现一个简易的“服务注册与发现”机制。第一步定义标记特性// 标记一个类是可被注入的服务 [AttributeUsage(AttributeTargets.Class)] public class InjectableAttribute : Attribute { public LifeTime LifeTime { get; } public Type ServiceType { get; } // 通常注册为接口 public InjectableAttribute(Type serviceType, LifeTime lifeTime LifeTime.Transient) { ServiceType serviceType; LifeTime lifeTime; } } public enum LifeTime { Transient, // 每次请求都创建新实例 Scoped, // 同一作用域内单例 Singleton // 全局单例 } // 标记一个属性或构造函数参数需要被注入 [AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter)] public class InjectAttribute : Attribute { }第二步标记服务与依赖// 定义接口 public interface ILogger { void Log(string message); } public interface IEmailService { void Send(string to, string body); } // 标记实现类 [Injectable(typeof(ILogger), LifeTime.Singleton)] public class ConsoleLogger : ILogger { public void Log(string message) Console.WriteLine($[LOG] {message}); } [Injectable(typeof(IEmailService))] public class SmtpEmailService : IEmailService { public void Send(string to, string body) Console.WriteLine($发送邮件给 {to}: {body}); } // 一个需要依赖注入的业务类 public class OrderProcessor { [Inject] // 标记此属性需要注入 public ILogger Logger { get; set; } private readonly IEmailService _emailService; // 标记构造函数参数需要注入 public OrderProcessor([Inject] IEmailService emailService) { _emailService emailService; } public void Process() { Logger?.Log(开始处理订单...); // ... 业务逻辑 _emailService.Send(customerexample.com, 您的订单已处理); Logger?.Log(订单处理完成。); } }第三步实现一个简单的容器using System; using System.Collections.Generic; using System.Linq; using System.Reflection; public class TinyContainer { private readonly DictionaryType, (Type ImplType, LifeTime LifeTime) _registrations new(); private readonly DictionaryType, object _singletons new(); public void RegisterAssembly(Assembly assembly) { // 扫描程序集找到所有带有 [Injectable] 特性的类 var injectableTypes assembly.GetTypes() .Where(t t.IsClass !t.IsAbstract) .Select(t new { Type t, Attr t.GetCustomAttributeInjectableAttribute() }) .Where(x x.Attr ! null); foreach (var item in injectableTypes) { _registrations[item.Attr.ServiceType] (item.Type, item.Attr.LifeTime); Console.WriteLine($注册服务: {item.Attr.ServiceType.Name} - {item.Type.Name} ({item.Attr.LifeTime})); } } public object Resolve(Type serviceType) { if (!_registrations.TryGetValue(serviceType, out var registration)) throw new InvalidOperationException($未找到服务注册: {serviceType.Name}); // 根据生命周期返回实例 switch (registration.LifeTime) { case LifeTime.Singleton: if (!_singletons.TryGetValue(serviceType, out var singleton)) { singleton CreateInstance(registration.ImplType); _singletons[serviceType] singleton; } return singleton; case LifeTime.Transient: default: return CreateInstance(registration.ImplType); } } public T ResolveT() (T)Resolve(typeof(T)); private object CreateInstance(Type implementationType) { var constructor implementationType.GetConstructors().FirstOrDefault(); if (constructor null) return Activator.CreateInstance(implementationType); // 解析构造函数参数 var parameters constructor.GetParameters(); var args new object[parameters.Length]; for (int i 0; i parameters.Length; i) { // 如果参数有 [Inject] 特性或者参数类型是已注册的服务则解析依赖 if (parameters[i].GetCustomAttributeInjectAttribute() ! null || _registrations.ContainsKey(parameters[i].ParameterType)) { args[i] Resolve(parameters[i].ParameterType); } else { // 否则尝试用默认值 args[i] parameters[i].HasDefaultValue ? parameters[i].DefaultValue : null; } } var instance constructor.Invoke(args); // 处理属性注入 var properties implementationType.GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var prop in properties) { if (prop.GetCustomAttributeInjectAttribute() ! null prop.CanWrite) { var propValue Resolve(prop.PropertyType); prop.SetValue(instance, propValue); } } return instance; } }第四步使用我们的小容器// 1. 创建容器并注册服务通过扫描程序集 var container new TinyContainer(); container.RegisterAssembly(Assembly.GetExecutingAssembly()); // 扫描当前程序集 // 2. 解析 OrderProcessor其依赖会自动注入 var orderProcessor container.ResolveOrderProcessor(); // 3. 使用它 orderProcessor.Process(); // 输出示例 // 注册服务: ILogger - ConsoleLogger (Singleton) // 注册服务: IEmailService - SmtpEmailService (Transient) // [LOG] 开始处理订单... // 发送邮件给 customerexample.com: 您的订单已处理 // [LOG] 订单处理完成。这个例子虽然简化了很多比如没有作用域生命周期、循环依赖检测等但它清晰地展示了如何利用特性来实现一种“约定优于配置”的依赖注入模式。你只需要给服务类打上[Injectable]标签给依赖项打上[Inject]标签容器就能自动组装它们。这在开发插件系统、模块化应用时特别有用。4.3 与AOP面向切面编程结合AOP是解决横切关注点如日志、事务、缓存、权限的经典范式。特性是.NET中实现AOP的一种非常自然的方式。上面我们演示了通过反射手动调用前后逻辑。在实际项目中我们更常借助一些轻量级的AOP库如Rougamo.Fody,MethodDecorator.Fody它们在编译时通过IL重写技术织入代码性能几乎无损。这里以记录方法执行时间的通用需求为例看看如何用特性优雅解决第一步定义一个计时特性[AttributeUsage(AttributeTargets.Method, AllowMultiple false)] public class TimeItAttribute : Attribute { public string Category { get; set; } Default; }第二步使用编译时织入以Rougamo为例首先通过NuGet安装Rougamo.Fody。然后using Rougamo; using Rougamo.Context; using System.Diagnostics; // 继承 MoAttribute这是Rougamo的基类 public class TimeItAopAttribute : MoAttribute { private Stopwatch _stopwatch; // 方法执行前 public override void OnEntry(MethodContext context) { _stopwatch Stopwatch.StartNew(); var category context.Method.GetCustomAttributeTimeItAttribute()?.Category ?? Default; Console.WriteLine($[{category}] 进入方法: {context.Method.Name}); } // 方法执行后成功返回 public override void OnSuccess(MethodContext context) { _stopwatch.Stop(); var category context.Method.GetCustomAttributeTimeItAttribute()?.Category ?? Default; Console.WriteLine($[{category}] 离开方法: {context.Method.Name}, 耗时: {_stopwatch.ElapsedMilliseconds}ms); } // 方法抛出异常时 public override void OnException(MethodContext context) { _stopwatch.Stop(); var category context.Method.GetCustomAttributeTimeItAttribute()?.Category ?? Default; Console.WriteLine($[{category}] 方法 {context.Method.Name} 异常: {context.Exception.Message}, 耗时: {_stopwatch.ElapsedMilliseconds}ms); } // 方法最终结束时无论成功或异常 public override void OnExit(MethodContext context) { // 清理资源如果有的话 } }第三步在方法上应用特性public class ExpensiveService { [TimeIt(Category Database)] [TimeItAop] // 注意这里应用了两个特性一个是我们定义的标记一个是AOP逻辑 public ListProduct GetProductsFromDatabase() { // 模拟数据库查询耗时 Thread.Sleep(new Random().Next(100, 500)); return new ListProduct { new Product(), new Product() }; } [TimeIt(Category Calculation)] [TimeItAop] public double CalculateComplexStuff() { // 模拟复杂计算 Thread.Sleep(new Random().Next(200, 800)); return 42.0; } }当你调用GetProductsFromDatabase或CalculateComplexStuff时控制台会自动输出方法的执行时间而你完全不需要在业务方法内写任何计时代码。Rougamo在编译时修改了IL代码将TimeItAopAttribute中的逻辑织入到了目标方法的前后。这种方式对代码侵入性极小只需要添加一个特性就能无痕地实现性能监控、日志记录、异常处理等通用功能。5. 特性使用的注意事项与最佳实践玩了这么多花样最后也得聊聊哪些“坑”要避开以及怎么用得更好。这些都是我多年实战总结的血泪经验。1. 性能考量反射是双刃剑通过GetCustomAttribute()读取特性确实方便但反射操作比直接调用代码要慢。在高性能、低延迟的场景例如高频交易系统、游戏主循环中需要谨慎。缓存缓存再缓存如果你需要反复读取某个类型的特性一定要把结果缓存起来。可以用ConcurrentDictionaryType, TAttribute或者LazyT来实现线程安全的缓存。考虑编译时方案对于极致的性能要求可以考虑使用Source GeneratorsC# 9.0在编译时生成代码完全避免运行时反射。比如你可以写一个Source Generator在编译时扫描所有带有特定特性的类并自动生成对应的注册代码或验证代码。2. 设计原则保持特性的“单纯”单一职责一个特性只应该做一件事。不要设计一个[LogAndValidateAndAuthorize]这样的“瑞士军刀”特性。把它拆分成[Log]、[Validate]、[Authorize]组合使用更灵活。不可变设计特性的属性Property最好只有getter通过构造函数参数初始化。这能保证特性元数据在应用后是不可变的更安全也更容易理解。命名清晰特性类名应该明确表达其用途比如[ValidateEmail]就比[Check1]好得多。遵循XXXAttribute的命名规范。3. 与框架的协作了解框架的“脾气”像ASP.NET Core、Entity Framework这些主流框架对特性有很深度的集成。在使用自定义特性时要确保它们不会和框架内置的特性行为冲突。例如不要定义自己的[Route]特性以免和MVC的路由系统混淆。特性不是银弹虽然特性很强大但不要滥用。简单的配置项可能用一个appsettings.json文件更合适简单的行为扩展用继承或组合模式可能更直观。特性最适合用来标注“元数据”而不是承载复杂的运行时逻辑。4. 调试与维护特性在调试器里是可见的在Visual Studio中调试时你可以在“即时窗口”或通过代码查看特性信息。善用这个功能来排查问题。为自定义特性编写单元测试特性的逻辑尤其是通过反射读取和使用特性的逻辑应该被充分测试。确保你的特性在各种边界条件下都能正确工作。说到底C#特性是一种强大的元编程工具。它让你能够将额外的信息“附加”到代码上从而影响编译器、运行时或工具的行为。从简单的标记过时方法到复杂的AOP和依赖注入框架特性都能提供一种声明式、非侵入式的解决方案。掌握它意味着你多了一种让代码更简洁、更灵活、更易于维护的武器。刚开始可能会觉得有点抽象但多写几次尤其是在自己的工具库或框架中尝试使用你很快就会爱上这种“贴标签”的编程方式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2411831.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…