Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ ai-libs/
.codex
# tool
.venv/
BenchmarkDotNet.Artifacts/
15 changes: 15 additions & 0 deletions GFramework.Core.Abstractions/Ioc/IIocContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,21 @@ void RegisterCqrsStreamPipelineBehavior<TBehavior>()
/// </remarks>
bool Contains<T>() where T : class;

/// <summary>
/// 检查容器中是否存在可赋值给指定服务类型的注册项,而不要求解析出实例。
/// </summary>
/// <param name="type">要检查的服务类型。</param>
/// <returns>若存在显式注册或开放泛型映射可满足该服务类型,则返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
/// <exception cref="ArgumentNullException">当 <paramref name="type" /> 为 <see langword="null" /> 时抛出。</exception>
/// <exception cref="ObjectDisposedException">当调用 <see cref="HasRegistration(Type)" /> 时容器已被释放时抛出。</exception>
/// <remarks>
/// 该入口面向“先判断是否值得解析实例”的热路径优化场景。
/// 与 <see cref="Contains{T}" /> 不同,它不会为了判断结果而激活服务实例,因此可避免把瞬态对象创建、
/// 多服务枚举或日志分配混入仅需存在性判断的调用链中。
/// 该方法按服务键与开放泛型映射判断可见性,不会把“仅以实现类型自身注册”的实例误判成其所有可赋值接口都已注册。
/// </remarks>
bool HasRegistration(Type type);
Comment thread
greptile-apps[bot] marked this conversation as resolved.

/// <summary>
/// 判断容器中是否包含某个具体的实例对象
/// </summary>
Expand Down
86 changes: 86 additions & 0 deletions GFramework.Core.Tests/Ioc/MicrosoftDiContainerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,47 @@ public void Contains_WithNoInstances_Should_ReturnFalse()
Assert.That(_container.Contains<TestService>(), Is.False);
}

/// <summary>
/// 测试显式服务不存在时,HasRegistration 应返回 false,且不会要求先冻结或解析实例。
/// </summary>
[Test]
public void HasRegistration_WithNoMatchingService_Should_ReturnFalse()
{
Assert.That(_container.HasRegistration(typeof(IPipelineBehavior<HasRegistrationRequest, int>)), Is.False);
}

/// <summary>
/// 测试 HasRegistration 能识别开放泛型 CQRS pipeline 行为对闭合请求/响应对的可见性。
/// </summary>
[Test]
public void HasRegistration_Should_ReturnTrue_For_Closed_Service_Satisfied_By_Open_Generic_Registration()
{
_container.GetServicesUnsafe.AddSingleton(
typeof(IPipelineBehavior<,>),
typeof(OpenGenericHasRegistrationBehavior<,>));

Assert.That(_container.HasRegistration(typeof(IPipelineBehavior<HasRegistrationRequest, int>)), Is.True);

_container.Freeze();

Assert.That(_container.HasRegistration(typeof(IPipelineBehavior<HasRegistrationRequest, int>)), Is.True);
}

/// <summary>
/// 测试 HasRegistration 不会把仅以具体实现类型自注册的服务误判成其接口服务键也已注册。
/// </summary>
[Test]
public void HasRegistration_Should_ReturnFalse_For_Interface_When_Only_Concrete_Service_Key_Is_Registered()
{
_container.GetServicesUnsafe.AddSingleton(typeof(SelfRegisteredConcreteBehavior), typeof(SelfRegisteredConcreteBehavior));

Assert.That(_container.HasRegistration(typeof(IPipelineBehavior<HasRegistrationRequest, int>)), Is.False);

_container.Freeze();

Assert.That(_container.HasRegistration(typeof(IPipelineBehavior<HasRegistrationRequest, int>)), Is.False);
}

/// <summary>
/// 测试当实例存在时检查实例包含关系应返回 true 的功能
/// </summary>
Expand Down Expand Up @@ -902,4 +943,49 @@ private static ReaderWriterLockSlim GetContainerLock(MicrosoftDiContainer contai
Assert.That(lockField, Is.Not.Null);
return (ReaderWriterLockSlim)lockField!.GetValue(container)!;
}

/// <summary>
/// 供 HasRegistration 回归使用的最小请求类型。
/// </summary>
private sealed class HasRegistrationRequest : IRequest<int>
{
}

/// <summary>
/// 供 HasRegistration 回归使用的开放泛型 pipeline 行为。
/// </summary>
/// <typeparam name="TRequest">请求类型。</typeparam>
/// <typeparam name="TResponse">响应类型。</typeparam>
private sealed class OpenGenericHasRegistrationBehavior<TRequest, TResponse> :
IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
/// <summary>
/// 透传到下一个 pipeline 节点,不额外改变请求语义。
/// </summary>
public ValueTask<TResponse> Handle(
TRequest request,
MessageHandlerDelegate<TRequest, TResponse> next,
CancellationToken cancellationToken)
{
return next(request, cancellationToken);
}
}

/// <summary>
/// 供 HasRegistration 服务键判定回归使用的最小封闭 pipeline 行为。
/// </summary>
private sealed class SelfRegisteredConcreteBehavior : IPipelineBehavior<HasRegistrationRequest, int>
{
/// <summary>
/// 透传到下一个 pipeline 节点,不额外改变请求语义。
/// </summary>
public ValueTask<int> Handle(
HasRegistrationRequest request,
MessageHandlerDelegate<HasRegistrationRequest, int> next,
CancellationToken cancellationToken)
{
return next(request, cancellationToken);
}
}
}
85 changes: 80 additions & 5 deletions GFramework.Core/Ioc/MicrosoftDiContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -706,9 +706,12 @@ private ICqrsRegistrationService ResolveCqrsRegistrationService()
}

var result = _provider!.GetService(type);
_logger.Debug(result != null
? $"Retrieved instance: {type.Name}"
: $"No instance found for type: {type.Name}");
if (_logger.IsDebugEnabled())
{
_logger.Debug(result != null
? $"Retrieved instance: {type.Name}"
: $"No instance found for type: {type.Name}");
}
return result;
}
finally
Expand Down Expand Up @@ -792,7 +795,10 @@ public IReadOnlyList<T> GetAll<T>() where T : class
}

var services = _provider!.GetServices<T>().ToList();
_logger.Debug($"Retrieved {services.Count} instances of {typeof(T).Name}");
if (_logger.IsDebugEnabled())
{
_logger.Debug($"Retrieved {services.Count} instances of {typeof(T).Name}");
}
return services;
}
finally
Expand Down Expand Up @@ -821,7 +827,10 @@ public IReadOnlyList<object> GetAll(Type type)
}

var services = _provider!.GetServices(type).ToList();
_logger.Debug($"Retrieved {services.Count} instances of {type.Name}");
if (_logger.IsDebugEnabled())
{
_logger.Debug($"Retrieved {services.Count} instances of {type.Name}");
}
return services.Where(o => o != null).Cast<object>().ToList();
}
finally
Expand Down Expand Up @@ -1023,6 +1032,26 @@ public bool Contains<T>() where T : class
}
}

/// <summary>
/// 检查容器中是否存在可赋值给指定服务类型的注册项,而不要求先解析实例。
/// </summary>
/// <param name="type">要检查的服务类型。</param>
/// <returns>若存在显式注册或开放泛型映射可满足该服务类型,则返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
public bool HasRegistration(Type type)
{
ArgumentNullException.ThrowIfNull(type);
ThrowIfDisposed();
EnterReadLockOrThrowDisposed();
try
{
return HasRegistrationCore(type);
}
finally
{
_lock.ExitReadLock();
}
}

/// <summary>
/// 判断容器中是否包含某个具体的实例对象
/// 通过已注册实例集合进行快速查找
Expand All @@ -1043,6 +1072,52 @@ public bool ContainsInstance(object instance)
}
}

/// <summary>
/// 在当前容器状态下检查指定服务类型是否存在可见注册。
/// </summary>
/// <param name="requestedType">要检查的服务类型。</param>
/// <returns>存在可满足该类型的注册时返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
/// <remarks>
/// 该检查只回答“是否可能解析到服务”,不会为了判断结果而激活实例。
/// 预冻结阶段只基于当前服务描述符推断;冻结后则同样只观察描述符,
/// 避免把瞬态/多实例解析成本混入热路径中的存在性判断。
/// </remarks>
private bool HasRegistrationCore(Type requestedType)
{
foreach (var descriptor in GetServicesUnsafe)
{
if (CanSatisfyServiceType(descriptor.ServiceType, requestedType))
{
return true;
}
}

return false;
}

/// <summary>
/// 判断某个服务描述符声明的服务类型是否能满足当前请求类型。
/// </summary>
/// <param name="registeredServiceType">注册时声明的服务类型。</param>
/// <param name="requestedType">调用方请求的服务类型。</param>
/// <returns>若当前注册可用于解析 <paramref name="requestedType" />,则返回 <see langword="true" />。</returns>
private static bool CanSatisfyServiceType(Type registeredServiceType, Type requestedType)
{
// 这里刻意与 Get/GetAll 的“按服务键解析”语义保持一致:
// 只有注册时声明的服务类型本身命中,或开放泛型服务键能闭合到请求类型时,才视为存在可见注册。
if (registeredServiceType == requestedType)
{
return true;
}

if (requestedType.IsConstructedGenericType && registeredServiceType.IsGenericTypeDefinition)
{
return requestedType.GetGenericTypeDefinition() == registeredServiceType;
}

return false;
}
Comment thread
greptile-apps[bot] marked this conversation as resolved.

/// <summary>
/// 清空容器中的所有实例和服务注册
/// 只有在容器未冻结状态下才能执行清空操作
Expand Down
5 changes: 5 additions & 0 deletions GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
<PackageReference Include="Mediator.Abstractions" Version="3.0.2" />
<PackageReference Include="Mediator.SourceGenerator" Version="3.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="MediatR" Version="13.1.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.7" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.7" />
Expand Down
18 changes: 18 additions & 0 deletions GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,24 @@ internal static ServiceProvider CreateMediatRServiceProvider(
return services.BuildServiceProvider();
}

/// <summary>
/// 创建承载 NuGet `Mediator` source-generated concrete mediator 的最小对照宿主。
/// </summary>
/// <param name="configure">补充当前场景的显式服务注册。</param>
/// <returns>可直接解析 generated `Mediator.Mediator` 的 DI 宿主。</returns>
/// <remarks>
/// 当前 benchmark 只把 `Mediator` 作为单例 steady-state 对照组接入,
/// 因为它的 lifetime 由 source generator 在编译期塑形;若后续需要 `Transient` / `Scoped` 矩阵,
/// 应按 `Mediator` 官方 benchmark 的做法拆成独立 build config,而不是在同一编译产物里混用多个 lifetime。
/// </remarks>
internal static ServiceProvider CreateMediatorServiceProvider(Action<IServiceCollection>? configure)
{
var services = new ServiceCollection();
configure?.Invoke(services);
services.AddMediator();
return services.BuildServiceProvider();
}

/// <summary>
/// 判断某个类型是否正好实现了指定的闭合或开放 MediatR 合同。
/// </summary>
Expand Down
39 changes: 33 additions & 6 deletions GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,22 @@
using GFramework.Cqrs.Abstractions.Cqrs;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using GeneratedMediator = Mediator.Mediator;

namespace GFramework.Cqrs.Benchmarks.Messaging;

/// <summary>
/// 对比单个 request 在直接调用、GFramework.CQRS runtime 与 MediatR 之间的 steady-state dispatch 开销。
/// 对比单个 request 在直接调用、GFramework.CQRS runtime、NuGet `Mediator` 与 MediatR 之间的 steady-state dispatch 开销。
/// </summary>
[Config(typeof(Config))]
public class RequestBenchmarks
{
private MicrosoftDiContainer _container = null!;
private ICqrsRuntime _runtime = null!;
private ServiceProvider _serviceProvider = null!;
private ServiceProvider _mediatrServiceProvider = null!;
private ServiceProvider _mediatorServiceProvider = null!;
private IMediator _mediatr = null!;
private GeneratedMediator _mediator = null!;
private BenchmarkRequestHandler _baselineHandler = null!;
private BenchmarkRequest _request = null!;

Expand Down Expand Up @@ -69,23 +72,26 @@ public void Setup()
_container,
LoggerFactoryResolver.Provider.CreateLogger(nameof(RequestBenchmarks)));

_serviceProvider = BenchmarkHostFactory.CreateMediatRServiceProvider(
_mediatrServiceProvider = BenchmarkHostFactory.CreateMediatRServiceProvider(
configure: null,
typeof(RequestBenchmarks),
static candidateType => candidateType == typeof(BenchmarkRequestHandler),
ServiceLifetime.Singleton);
_mediatr = _serviceProvider.GetRequiredService<IMediator>();
_mediatr = _mediatrServiceProvider.GetRequiredService<IMediator>();

_mediatorServiceProvider = BenchmarkHostFactory.CreateMediatorServiceProvider(configure: null);
_mediator = _mediatorServiceProvider.GetRequiredService<GeneratedMediator>();

_request = new BenchmarkRequest(Guid.NewGuid());
}

/// <summary>
/// 释放 MediatR 对照组使用的 DI 宿主。
/// 释放 MediatR 与 `Mediator` 对照组使用的 DI 宿主。
/// </summary>
[GlobalCleanup]
public void Cleanup()
{
BenchmarkCleanupHelper.DisposeAll(_container, _serviceProvider);
BenchmarkCleanupHelper.DisposeAll(_container, _mediatrServiceProvider, _mediatorServiceProvider);
}

/// <summary>
Expand Down Expand Up @@ -115,12 +121,22 @@ public Task<BenchmarkResponse> SendRequest_MediatR()
return _mediatr.Send(_request, CancellationToken.None);
}

/// <summary>
/// 通过 `ai-libs/Mediator` 的 source-generated concrete mediator 发送 request,作为高性能对照组。
/// </summary>
[Benchmark]
public ValueTask<BenchmarkResponse> SendRequest_Mediator()
{
return _mediator.Send(_request, CancellationToken.None);
}

/// <summary>
/// Benchmark request。
/// </summary>
/// <param name="Id">请求标识。</param>
public sealed record BenchmarkRequest(Guid Id) :
GFramework.Cqrs.Abstractions.Cqrs.IRequest<BenchmarkResponse>,
Mediator.IRequest<BenchmarkResponse>,
MediatR.IRequest<BenchmarkResponse>;

/// <summary>
Expand All @@ -134,6 +150,7 @@ public sealed record BenchmarkResponse(Guid Id);
/// </summary>
public sealed class BenchmarkRequestHandler :
GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<BenchmarkRequest, BenchmarkResponse>,
Mediator.IRequestHandler<BenchmarkRequest, BenchmarkResponse>,
MediatR.IRequestHandler<BenchmarkRequest, BenchmarkResponse>
{
/// <summary>
Expand All @@ -144,6 +161,16 @@ public ValueTask<BenchmarkResponse> Handle(BenchmarkRequest request, Cancellatio
return ValueTask.FromResult(new BenchmarkResponse(request.Id));
}

/// <summary>
/// 处理 NuGet `Mediator` request。
/// </summary>
ValueTask<BenchmarkResponse> Mediator.IRequestHandler<BenchmarkRequest, BenchmarkResponse>.Handle(
BenchmarkRequest request,
CancellationToken cancellationToken)
{
return Handle(request, cancellationToken);
}

/// <summary>
/// 处理 MediatR request。
/// </summary>
Expand Down
Loading
Loading