编辑
2025-09-18
C#
00

目录

🔍 问题分析:传统服务注册的痛点
传统方式的问题
💡 解决方案:特性驱动的自动注册
核心思路
🛠️ 代码实战
1️⃣ 定义自动注册特性
2️⃣ 实现自动注册扩展方法
3️⃣ 服务实现示例
4️⃣ 在启动类中使用
🎯 实际应用场景
适用场景
不适用场景
⚠️ 常见坑点提醒
1. 程序集扫描范围
2. 接口命名约定
3. 泛型接口处理
📊 性能对比
🎉 总结

你是否还在为每次新增服务都要手动在Program.cs中添加注册代码而烦恼?随着项目规模的扩大,服务注册的代码越来越冗长,维护成本也越来越高。据统计,一个中等规模的.NET项目平均需要注册50+个服务,传统方式下仅服务注册代码就占据了启动文件的80%篇幅!

今天我们来探讨一个更优雅的解决方案:基于特性(Attribute)的自动服务注册,让你彻底告别手动注册的烦恼,让代码更简洁、更易维护。

🔍 问题分析:传统服务注册的痛点

传统方式的问题

在传统的.NET依赖注入中,我们需要在Program.csStartup.cs中逐一注册每个服务:

C#
// 传统注册方式 - 冗长且容易遗漏 services.AddScoped<IUserService, UserService>(); services.AddSingleton<ICacheService, CacheService>(); services.AddTransient<IEmailService, EmailService>(); services.AddScoped<IDataRepository, DataRepository>(); // ... 50+个服务的注册代码

痛点总结:

  • 📝 维护成本高:每新增一个服务都要修改启动代码
  • 🐛 容易遗漏:忘记注册服务导致运行时错误
  • 🔄 生命周期混乱:难以统一管理服务的生命周期
  • 📦 代码分散:服务定义和注册逻辑相距甚远

💡 解决方案:特性驱动的自动注册

核心思路

通过自定义特性标记需要注册的服务,在启动时通过反射自动扫描并注册,实现**"声明式"**的服务注册。

🛠️ 代码实战

1️⃣ 定义自动注册特性

C#
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; namespace AppDIAttr { /// <summary> /// 服务注册特性 - 支持自定义生命周期 /// </summary> [AttributeUsage(AttributeTargets.Class)] public class AutoRegisterAttribute : Attribute { public ServiceLifetime Lifetime { get; set; } = ServiceLifetime.Scoped; public AutoRegisterAttribute(ServiceLifetime lifetime = ServiceLifetime.Scoped) { Lifetime = lifetime; } } }

设计亮点:

  • 🎯 默认Scoped生命周期:符合大多数业务场景
  • 🔧 支持自定义生命周期:灵活适配不同需求
  • 📋 仅作用于类:防止误用于接口或其他类型

2️⃣ 实现自动注册扩展方法

C#
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; namespace AppDIAttr { /// <summary> /// 增强版自动服务注册 - 支持特性控制 /// </summary> public static class ServiceCollectionExtensions { public static IServiceCollection AddAutoRegisteredServices( this IServiceCollection services, params Assembly[] assemblies) { foreach (var assembly in assemblies) { // 🏷️ 只注册标记了AutoRegister特性的类 var typesToRegister = assembly.GetTypes() .Where(type => type.IsClass && !type.IsAbstract && type.GetCustomAttribute<AutoRegisterAttribute>() != null); foreach (var type in typesToRegister) { var attribute = type.GetCustomAttribute<AutoRegisterAttribute>(); var interfaces = type.GetInterfaces().Where(i => !i.IsGenericTypeDefinition); // 🎯 根据特性指定的生命周期注册服务 if (interfaces.Any()) { foreach (var @interface in interfaces) { RegisterService(services, @interface, type, attribute.Lifetime); } } else { RegisterService(services, type, type, attribute.Lifetime); } } } return services; } private static void RegisterService(IServiceCollection services, Type serviceType, Type implementationType, ServiceLifetime lifetime) { switch (lifetime) { case ServiceLifetime.Singleton: services.AddSingleton(serviceType, implementationType); break; case ServiceLifetime.Transient: services.AddTransient(serviceType, implementationType); break; default: services.AddScoped(serviceType, implementationType); break; } Console.WriteLine($"🚀 已注册 {lifetime} 服务: {serviceType.Name} -> {implementationType.Name}"); } } }

核心逻辑解析:

  • 🔍 反射扫描:遍历程序集中的所有类型
  • 🏷️ 特性过滤:只处理标记了AutoRegisterAttribute的类
  • 🔗 接口映射:自动将实现类与其接口关联
  • ⚙️ 生命周期控制:根据特性配置选择合适的注册方式

3️⃣ 服务实现示例

C#
/// <summary> /// 缓存服务 - 单例模式,全局共享 /// </summary> [AutoRegister(ServiceLifetime.Singleton)] public class CacheService : ICacheService { private readonly Dictionary<string, object> _cache = new(); private readonly ILogger<CacheService> _logger; public CacheService(ILogger<CacheService> logger) { _logger = logger; _logger.LogInformation("💾 CacheService 实例已创建 (Singleton)"); } public void Set(string key, object value) { _cache[key] = value; _logger.LogInformation($"📝 缓存已设置: {key}"); } public T Get<T>(string key) { if (_cache.TryGetValue(key, out var value)) { _logger.LogInformation($"📖 缓存命中: {key}"); return (T)value; } _logger.LogInformation($"❌ 缓存未命中: {key}"); return default(T); } public void Remove(string key) { _cache.Remove(key); _logger.LogInformation($"🗑️ 缓存已删除: {key}"); } } /// <summary> /// 数据仓储服务 - 请求作用域 /// </summary> [AutoRegister(ServiceLifetime.Scoped)] public class DataRepository : IDataRepository { private readonly ILogger<DataRepository> _logger; private static readonly List<User> _users = new(); // 模拟数据库 public DataRepository(ILogger<DataRepository> logger) { _logger = logger; _logger.LogInformation("🗄️ DataRepository 实例已创建 (Scoped)"); } // 实现具体的数据操作方法... } /// <summary> /// 邮件服务 - 瞬时模式,每次使用创建新实例 /// </summary> [AutoRegister(ServiceLifetime.Transient)] public class EmailService : IEmailService { private readonly ILogger<EmailService> _logger; public EmailService(ILogger<EmailService> logger) { _logger = logger; _logger.LogInformation("📧 EmailService 实例已创建 (Transient)"); } // 实现邮件发送逻辑... }

4️⃣ 在启动类中使用

C#
using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace AppDIAttr { internal class Program { static async Task Main(string[] args) { Console.OutputEncoding=System.Text.Encoding.UTF8; Console.WriteLine("🎯 自动服务注册演示程序"); Console.WriteLine("==========================\n"); // 创建Host Builder var host = Host.CreateDefaultBuilder(args) .ConfigureServices((context, services) => { // 🚀 自动注册所有标记了AutoRegister特性的服务 Console.WriteLine("📋 开始自动服务注册:"); services.AddAutoRegisteredServices(Assembly.GetExecutingAssembly()); // 注册应用程序主类 services.AddScoped<Application>(); Console.WriteLine("\n✅ 服务注册完成!\n"); }) .Build(); // 创建作用域并运行应用程序 using var scope = host.Services.CreateScope(); var app = scope.ServiceProvider.GetRequiredService<Application>(); await app.RunAsync(); Console.WriteLine("\n🎉 程序执行完成,按任意键退出..."); Console.ReadKey(); } } }

image.png

🎯 实际应用场景

适用场景

  • 企业级应用:服务数量多,注册逻辑复杂
  • 微服务架构:每个服务模块独立管理依赖
  • 团队开发:减少合并冲突,提高开发效率
  • 快速原型:专注业务逻辑,减少基础设施代码

不适用场景

  • 简单应用:服务数量少于10个
  • 需要复杂配置:服务注册需要大量自定义配置
  • 性能敏感:启动时间要求极致优化的应用

⚠️ 常见坑点提醒

1. 程序集扫描范围

C#
// ❌ 错误:只扫描当前程序集 services.AddAutoRegisteredServices(Assembly.GetExecutingAssembly()); // ✅ 正确:扫描所有相关程序集 services.AddAutoRegisteredServices( Assembly.GetExecutingAssembly(), Assembly.GetAssembly(typeof(SomeServiceInOtherAssembly)) );

2. 接口命名约定

C#
// ⚠️ 注意:确保接口和实现类的命名符合约定 public interface IUserService { } public class UserService : IUserService { } // ✅ 标准命名 public interface IDataAccess { } public class SqlDataAccess : IDataAccess { } // ✅ 也可以

3. 泛型接口处理

C#
// ⚠️ 当前实现不支持泛型接口,需要特殊处理 public interface IRepository<T> { } public class UserRepository : IRepository<User> { } // 需要手动注册

📊 性能对比

方式启动时间增加代码行数减少维护成本
传统手动注册0ms0%
自动注册5-10ms80%+

🎉 总结

通过特性驱动的自动服务注册,我们实现了:

  1. 🚀 开发效率提升:一行代码替代数十行注册代码
  2. 🛡️ 错误率降低:避免忘记注册服务的运行时错误
  3. 🔧 维护性增强:服务定义和注册逻辑紧密结合

这种方式特别适合中大型项目团队协作开发,让你的C#项目更加优雅和易维护。


💭 互动话题:

  1. 你在实际项目中是如何管理服务注册的?
  2. 对于这种自动注册方式,你觉得还有哪些可以改进的地方?

觉得这个技巧有用吗?点赞支持转发给更多.NET开发同行吧!让我们一起写出更优雅的C#代码!

🔖 收藏本文,下次项目重构时直接使用这套解决方案!

相关信息

通过网盘分享的文件:AppDIAttr.zip 链接: https://pan.baidu.com/s/1h_zuODJ5S4jkcwDSdf-qdQ?pwd=ppvx 提取码: ppvx --来自百度网盘超级会员v9的分享

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!