C#依赖注入中避免循环依赖并跨不可引用程序集调用方法
解决C#跨程序集调用的循环依赖与无直接引用调用问题
一、避免循环依赖的核心思路:依赖倒置+中间抽象层
循环依赖的根源是领域层与应用层互相引用,解决的核心是依赖倒置原则:
- 新建独立的共享类库(比如
Shared.Contracts),存放双方都需要的抽象接口、事件定义。 - 领域层只引用这个共享类库,依赖抽象而非应用层的具体实现。
- 应用层引用共享类库和领域层,实现抽象接口或处理领域事件。
二、替代反射的最优方案:领域事件驱动
你当前用反射的方式存在类型不安全、性能差、维护难的问题,用领域事件是更符合架构设计的方案:
步骤1:在共享类库定义事件与处理器抽象
// Shared.Contracts/IDomainEvent.cs public interface IDomainEvent { } // Shared.Contracts/SomeEntityCreatedEvent.cs public class SomeEntityCreatedEvent : IDomainEvent { public SomeEntity Entity { get; } public SomeEntityCreatedEvent(SomeEntity entity) => Entity = entity; } // Shared.Contracts/IEventHandler.cs public interface IEventHandler<TEvent> where TEvent : IDomainEvent { Task HandleAsync(TEvent domainEvent); } // Shared.Contracts/IEventPublisher.cs public interface IEventPublisher { Task PublishAsync<TEvent>(TEvent domainEvent) where TEvent : IDomainEvent; }
步骤2:领域层发布事件(不依赖应用层)
修改领域服务,注入抽象的事件发布器,只负责发布事件,无需关心谁处理:
// Domain/SomeDomainService.cs public class SomeDomainService<T> : ISomeDomainService<T> { private readonly IEventPublisher _eventPublisher; public SomeDomainService(IEventPublisher eventPublisher) { _eventPublisher = eventPublisher; } public async Task<SomeEntity> SomeProcess(SomeEntity someEntity) { await Task.Delay(1000); someEntity.Id = new Random().Next(3000); someEntity.Name = $"Entity {someEntity.Id}"; someEntity.Description = "lorem ipsum"; // 发布事件,彻底解耦与应用层的直接依赖 await _eventPublisher.PublishAsync(new SomeEntityCreatedEvent(someEntity)); return someEntity; } }
步骤3:应用层实现事件处理器
应用层实现事件处理器,处理领域层发布的事件,同时调用内部服务:
// Application/SomeEntityCreatedEventHandler.cs public class SomeEntityCreatedEventHandler : IEventHandler<SomeEntityCreatedEvent> { private readonly IAnotherApplicationService<Message> _anotherService; public SomeEntityCreatedEventHandler(IAnotherApplicationService<Message> anotherService) { _anotherService = anotherService; } public async Task HandleAsync(SomeEntityCreatedEvent domainEvent) { var entity = domainEvent.Entity; var domainMessage = new Message { Id = entity.Id.ToString(), Title = $"Some entity {entity.Name}", Body = "Message from Domain" }; await _anotherService.SendSomeMessage(domainMessage); } } // Application/EventPublisher.cs public class EventPublisher : IEventPublisher { private readonly IServiceProvider _serviceProvider; public EventPublisher(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public async Task PublishAsync<TEvent>(TEvent domainEvent) where TEvent : IDomainEvent { // 从DI容器获取所有对应事件的处理器并执行 var handlers = _serviceProvider.GetServices<IEventHandler<TEvent>>(); foreach (var handler in handlers) { await handler.HandleAsync(domainEvent); } } }
步骤4:DI容器注册所有服务
在启动项目(比如Web项目)中完成依赖注入注册:
// Program.cs builder.Services.AddScoped<ISomeDomainService<SomeEntity>, SomeDomainService<SomeEntity>>(); builder.Services.AddScoped<IAnotherApplicationService<Message>, AnotherApplicationService<Message>>(); builder.Services.AddScoped<IEventHandler<SomeEntityCreatedEvent>, SomeEntityCreatedEventHandler>(); builder.Services.AddScoped<IEventPublisher, EventPublisher>();
三、非事件场景的直接调用方案
如果必须直接调用某个服务,同样用依赖倒置解耦:
- 在共享类库定义服务抽象:
// Shared.Contracts/IMessageSender.cs public interface IMessageSender { Task SendMessageAsync(Message message); }
- 应用层实现这个抽象:
// Application/MessageSender.cs public class MessageSender : IMessageSender { private readonly IAnotherApplicationService<Message> _anotherService; public MessageSender(IAnotherApplicationService<Message> anotherService) { _anotherService = anotherService; } public async Task SendMessageAsync(Message message) { await _anotherService.SendSomeMessage(message); } }
- 领域层注入抽象服务直接调用:
// Domain/SomeDomainService.cs public class SomeDomainService<T> : ISomeDomainService<T> { private readonly IMessageSender _messageSender; public SomeDomainService(IMessageSender messageSender) { _messageSender = messageSender; } public async Task<SomeEntity> SomeProcess(SomeEntity someEntity) { // ... 原有逻辑 var domainMessage = new Message { Id = someEntity.Id.ToString(), Title = $"Some entity {someEntity.Name}", Body = "Message from Domain" }; await _messageSender.SendMessageAsync(domainMessage); return someEntity; } }
- DI注册:
builder.Services.AddScoped<IMessageSender, MessageSender>();
为什么反射方案不是最优?
- 类型不安全:硬编码程序集、类名,变更后会触发运行时错误,编译期无法检测。
- 性能损耗:反射调用比直接接口调用慢,频繁调用场景下影响明显。
- 维护困难:代码可读性差,后续调试、修改成本高。
内容的提问来源于stack exchange,提问作者Milton Calderon
相关产品推荐
相关产品推荐

