diff --git a/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs b/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs index 39445d48..4e3b5b23 100644 --- a/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs +++ b/GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs @@ -249,6 +249,46 @@ public class MicrosoftDiContainerTests Assert.That(results.Count, Is.EqualTo(0)); } + /// + /// 测试容器未冻结时,会折叠“不同服务类型指向同一实例”的兼容别名重复, + /// 但会保留同一服务类型的重复显式注册。 + /// + [Test] + public void GetAll_Should_Preserve_Duplicate_Registrations_For_The_Same_ServiceType_While_Deduplicating_Aliases() + { + var instance = new AliasAwareService(); + + _container.Register(instance); + _container.Register(instance); + _container.Register(instance); + + var results = _container.GetAll(); + + Assert.That(results, Has.Count.EqualTo(2)); + Assert.That(results[0], Is.SameAs(instance)); + Assert.That(results[1], Is.SameAs(instance)); + } + + /// + /// 测试非泛型 GetAll 在容器未冻结时与泛型重载保持相同的别名去重语义。 + /// + [Test] + public void + GetAll_Type_Should_Preserve_Duplicate_Registrations_For_The_Same_ServiceType_While_Deduplicating_Aliases() + { + var instance = new AliasAwareService(); + + _container.Register(instance); + _container.Register(instance); + _container.Register(instance); + + var results = _container.GetAll(typeof(ISharedAliasService)); + + Assert.That(results, Has.Count.EqualTo(2)); + Assert.That(results[0], Is.SameAs(instance)); + Assert.That(results[1], Is.SameAs(instance)); + } + /// /// 测试获取排序后的所有实例的功能 /// @@ -716,6 +756,28 @@ public interface IMixedService string? Name { get; set; } } +/// +/// 用于验证未冻结查询路径中的服务别名去重行为。 +/// +public interface ISharedAliasService; + +/// +/// 主服务别名接口。 +/// +public interface IPrimaryAliasService : ISharedAliasService; + +/// +/// 次级兼容别名接口。 +/// +public interface ISecondaryAliasService : ISharedAliasService; + +/// +/// 同时实现多个别名接口的测试服务。 +/// +public sealed class AliasAwareService : IPrimaryAliasService, ISecondaryAliasService +{ +} + /// /// 实现优先级的服务 /// diff --git a/GFramework.Core/Ioc/MicrosoftDiContainer.cs b/GFramework.Core/Ioc/MicrosoftDiContainer.cs index 78295ed4..5c49621a 100644 --- a/GFramework.Core/Ioc/MicrosoftDiContainer.cs +++ b/GFramework.Core/Ioc/MicrosoftDiContainer.cs @@ -35,6 +35,14 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null) #endregion + /// + /// 记录某个实例在未冻结查询中可见的服务类型分组信息。 + /// + /// 当前分组对应的服务类型。 + /// 该服务类型下的描述符数量。 + /// 该服务类型首次出现的位置,用于稳定打破并列。 + private sealed record VisibleServiceTypeGroup(Type ServiceType, int Count, int FirstIndex); + #region Fields /// @@ -593,32 +601,7 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null) { if (_provider == null) { - // 如果容器未冻结,从服务集合中获取已注册的实例 - var serviceType = typeof(T); - var registeredServices = GetServicesUnsafe - .Where(s => s.ServiceType == serviceType || serviceType.IsAssignableFrom(s.ServiceType)).ToList(); - - var result = new List(); - var seenInstances = new HashSet(ReferenceEqualityComparer.Instance); - foreach (var descriptor in registeredServices) - { - if (descriptor.ImplementationInstance is T instance) - { - // 同一实例可能同时以“正式接口 + 兼容别名接口”被注册;未冻结路径需去重以保持与冻结后的解析口径一致。 - if (seenInstances.Add(instance)) - result.Add(instance); - } - else if (descriptor.ImplementationFactory != null) - { - // 在未冻结状态下无法调用工厂方法,跳过 - } - else if (descriptor.ImplementationType != null) - { - // 在未冻结状态下无法创建实例,跳过 - } - } - - return result; + return CollectRegisteredImplementationInstances(typeof(T)).Cast().ToList(); } var services = _provider!.GetServices().ToList(); @@ -636,40 +619,17 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null) /// /// 服务类型 /// 只读的服务实例列表 - /// 当容器未冻结时抛出 + /// 时抛出 public IReadOnlyList GetAll(Type type) { + ArgumentNullException.ThrowIfNull(type); + _lock.EnterReadLock(); try { if (_provider == null) { - // 如果容器未冻结,从服务集合中获取已注册的实例 - var registeredServices = GetServicesUnsafe - .Where(s => s.ServiceType == type || type.IsAssignableFrom(s.ServiceType)) - .ToList(); - - var result = new List(); - var seenInstances = new HashSet(ReferenceEqualityComparer.Instance); - foreach (var descriptor in registeredServices) - { - if (descriptor.ImplementationInstance != null) - { - // 同一实例可能通过多个可赋值服务类型暴露;返回前按引用去重,避免兼容别名造成重复观察结果。 - if (seenInstances.Add(descriptor.ImplementationInstance)) - result.Add(descriptor.ImplementationInstance); - } - else if (descriptor.ImplementationFactory != null) - { - // 在未冻结状态下无法调用工厂方法,跳过 - } - else if (descriptor.ImplementationType != null) - { - // 在未冻结状态下无法创建实例,跳过 - } - } - - return result; + return CollectRegisteredImplementationInstances(type); } var services = _provider!.GetServices(type).ToList(); @@ -682,6 +642,108 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null) } } + /// + /// 在容器未冻结时,从服务描述符中收集当前可直接观察到的实例绑定。 + /// + /// 调用方请求的服务类型。 + /// 按当前未冻结语义可见的实例列表。 + /// + /// 该方法只读取 ,因为未冻结路径不会主动执行工厂方法, + /// 也不会提前构造 。 + /// 若同一实例同时经由多个可赋值的 暴露, + /// 这里会把它视为兼容别名并只保留一个规范服务类型对应的结果; + /// 但同一 的重复显式注册仍会完整保留,以维持注册顺序和多次注册语义。 + /// + private List CollectRegisteredImplementationInstances(Type requestedServiceType) + { + ArgumentNullException.ThrowIfNull(requestedServiceType); + + var matchingDescriptors = GetServicesUnsafe + .Where(descriptor => + descriptor.ServiceType == requestedServiceType || + requestedServiceType.IsAssignableFrom(descriptor.ServiceType)) + .ToList(); + + if (matchingDescriptors.Count == 0) + return []; + + var preferredServiceTypes = BuildPreferredVisibleServiceTypes(matchingDescriptors, requestedServiceType); + var result = new List(); + foreach (var descriptor in matchingDescriptors) + { + if (descriptor.ImplementationInstance is { } instance) + { + if (preferredServiceTypes.TryGetValue(instance, out var preferredServiceType) && + preferredServiceType == descriptor.ServiceType) + { + result.Add(instance); + } + } + else if (descriptor.ImplementationFactory != null) + { + // 在未冻结状态下无法调用工厂方法,跳过。 + } + else if (descriptor.ImplementationType != null) + { + // 在未冻结状态下无法创建实例,跳过。 + } + } + + return result; + } + + /// + /// 为每个可见实例选择一个规范服务类型,避免同一实例因兼容别名重复出现在未冻结查询结果中。 + /// + /// 已按请求类型过滤过的服务描述符集合。 + /// 调用方请求的服务类型。 + /// 实例到其规范服务类型的映射。 + private static Dictionary BuildPreferredVisibleServiceTypes( + IReadOnlyList matchingDescriptors, + Type requestedServiceType) + { + var preferredServiceTypes = new Dictionary(ReferenceEqualityComparer.Instance); + foreach (var instanceGroup in matchingDescriptors + .Where(static descriptor => descriptor.ImplementationInstance is not null) + .GroupBy(static descriptor => descriptor.ImplementationInstance!, + ReferenceEqualityComparer.Instance)) + { + preferredServiceTypes.Add( + instanceGroup.Key, + SelectPreferredVisibleServiceType(instanceGroup, requestedServiceType)); + } + + return preferredServiceTypes; + } + + /// + /// 在“同一实例被多个服务类型暴露”的场景下,选择未冻结查询结果应保留的规范服务类型。 + /// + /// 引用同一实例的服务描述符。 + /// 调用方请求的服务类型。 + /// 应在结果中保留的服务类型。 + private static Type SelectPreferredVisibleServiceType( + IEnumerable descriptorsForInstance, + Type requestedServiceType) + { + var serviceTypeGroups = descriptorsForInstance + .GroupBy(static descriptor => descriptor.ServiceType) + .Select((group, index) => new VisibleServiceTypeGroup(group.Key, group.Count(), index)) + .ToList(); + + // 若调用方请求的正是其中一个服务类型,优先保留它,使未冻结行为尽量贴近冻结后的精确服务解析口径。 + var requestedGroup = serviceTypeGroups.FirstOrDefault(group => group.ServiceType == requestedServiceType); + if (requestedGroup is not null) + return requestedGroup.ServiceType; + + // 否则优先保留“同一服务类型下注册次数最多”的那组,避免显式多次注册被较宽泛的别名折叠掉。 + return serviceTypeGroups + .OrderByDescending(static group => group.Count) + .ThenBy(static group => group.FirstIndex) + .First() + .ServiceType; + } + /// /// 获取并排序指定泛型类型的所有服务实例 /// 主要用于系统调度场景 diff --git a/GFramework.Cqrs.Abstractions/Cqrs/ICqrsRuntime.cs b/GFramework.Cqrs.Abstractions/Cqrs/ICqrsRuntime.cs index b8c85124..632af83e 100644 --- a/GFramework.Cqrs.Abstractions/Cqrs/ICqrsRuntime.cs +++ b/GFramework.Cqrs.Abstractions/Cqrs/ICqrsRuntime.cs @@ -15,6 +15,17 @@ public interface ICqrsRuntime /// 要分发的请求。 /// 取消令牌。 /// 请求响应。 + /// + /// 。 + /// + /// + /// 当前上下文无法满足运行时要求,例如未找到对应请求处理器,或请求处理链中的 + /// IContextAware 对象需要 IArchitectureContext 但当前 不提供该能力。 + /// + /// + /// 该契约允许调用方传入任意 , + /// 但默认运行时在需要向处理器或行为注入框架上下文时,仍要求该上下文同时实现 IArchitectureContext。 + /// ValueTask SendAsync( ICqrsContext context, IRequest request, @@ -28,6 +39,16 @@ public interface ICqrsRuntime /// 要发布的通知。 /// 取消令牌。 /// 表示通知分发完成的值任务。 + /// + /// 。 + /// + /// + /// 已解析到的通知处理器需要框架级上下文注入,但当前 不提供 + /// IArchitectureContext 能力。 + /// + /// + /// 默认实现允许零处理器场景静默完成;只有在处理器注入前置条件不满足时才会抛出异常。 + /// ValueTask PublishAsync( ICqrsContext context, TNotification notification, @@ -42,6 +63,17 @@ public interface ICqrsRuntime /// 流式请求。 /// 取消令牌。 /// 按需生成的异步响应序列。 + /// + /// 。 + /// + /// + /// 当前上下文无法满足运行时要求,例如未找到对应流式处理器,或流式处理链中的 + /// IContextAware 对象需要 IArchitectureContext 但当前 不提供该能力。 + /// + /// + /// 返回的异步序列在枚举前通常已完成处理器解析与上下文注入, + /// 因此调用方应把 视为整个枚举生命周期内的必需依赖。 + /// IAsyncEnumerable CreateStream( ICqrsContext context, IStreamRequest request, diff --git a/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs b/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs index 435c9cd5..867c6887 100644 --- a/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs +++ b/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs @@ -236,6 +236,8 @@ internal static class CqrsHandlerRegistrar Type handlerInterface, Type implementationType) { + // 这里保持线性扫描,避免为常见的小到中等规模程序集长期维护额外索引。 + // 若未来大型服务集合出现热点,可在更高层批处理中引入 HashSet<(Type, Type)> 做 O(1) 去重。 return services.Any(descriptor => descriptor.ServiceType == handlerInterface && descriptor.ImplementationType == implementationType); diff --git a/GFramework.Cqrs/Internal/DefaultCqrsRegistrationService.cs b/GFramework.Cqrs/Internal/DefaultCqrsRegistrationService.cs index 712ebea6..7993d748 100644 --- a/GFramework.Cqrs/Internal/DefaultCqrsRegistrationService.cs +++ b/GFramework.Cqrs/Internal/DefaultCqrsRegistrationService.cs @@ -1,4 +1,3 @@ -using System.Reflection; using GFramework.Core.Abstractions.Logging; using GFramework.Cqrs.Abstractions.Cqrs; @@ -10,6 +9,10 @@ namespace GFramework.Cqrs.Internal; /// /// 该实现把“按稳定程序集键去重”和“委托给 handler registrar 执行实际映射注册”收敛到 CQRS runtime 内部, /// 避免外层容器继续了解 handler 注册流水线的内部结构。 +/// +/// 该类型不是线程安全的。调用方应在外部同步边界内访问 , +/// 例如由容器写锁串行化程序集注册流程。 +/// /// internal sealed class DefaultCqrsRegistrationService(ICqrsHandlerRegistrar registrar, ILogger logger) : ICqrsRegistrationService