diff --git a/GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs b/GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs index 82208f85..0a71dac9 100644 --- a/GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs +++ b/GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs @@ -903,7 +903,7 @@ internal sealed class CqrsDispatcherCacheTests Type responseType, int behaviorCount) { - var binding = GetPairCacheValue(streamBindings, requestType, responseType); + var binding = GetStreamDispatchBindingValue(streamBindings, requestType, responseType); return binding is null ? null : InvokeInstanceMethod(binding, "GetPipelineExecutorForTesting", behaviorCount); @@ -957,6 +957,32 @@ internal sealed class CqrsDispatcherCacheTests .Invoke(bindingBox, Array.Empty()); } + /// + /// 读取指定流式请求/响应类型对对应的强类型 stream dispatch binding。 + /// + /// dispatcher 内部的 stream binding 缓存对象。 + /// 要读取的流式请求运行时类型。 + /// 要读取的响应元素类型。 + /// 强类型 binding;若缓存尚未建立则返回 + private static object? GetStreamDispatchBindingValue(object streamBindings, Type requestType, Type responseType) + { + var bindingBox = GetPairCacheValue(streamBindings, requestType, responseType); + if (bindingBox is null) + { + return null; + } + + var method = bindingBox.GetType().GetMethod( + "Get", + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + + Assert.That(method, Is.Not.Null, $"Missing stream binding accessor on {bindingBox.GetType().FullName}."); + + return method! + .MakeGenericMethod(responseType) + .Invoke(bindingBox, Array.Empty()); + } + /// /// 获取 CQRS dispatcher 运行时类型。 /// diff --git a/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs b/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs index 15273d96..5c79175a 100644 --- a/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs +++ b/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs @@ -222,6 +222,49 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests }); } + /// + /// 验证 generated stream binding 与对应的 pipeline executor 在首次建流后会被缓存并复用, + /// 同时保持 generated invoker 的结果与行为执行语义不变。 + /// + [Test] + public async Task CreateStream_Should_Reuse_Cached_Generated_Stream_Binding_And_Pipeline_Executor() + { + var generatedAssembly = CreateGeneratedStreamInvokerAssembly(); + var container = new MicrosoftDiContainer(); + container.RegisterCqrsStreamPipelineBehavior(); + + CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object); + container.Freeze(); + + var streamBindings = GetDispatcherCacheField("StreamDispatchBindings"); + var requestType = typeof(GeneratedStreamInvokerRequest); + var responseType = typeof(int); + + Assert.That( + GetStreamPipelineExecutorValue(streamBindings, requestType, responseType, 1), + Is.Null); + + var context = new ArchitectureContext(container); + var firstResults = await DrainAsync(context.CreateStream(new GeneratedStreamInvokerRequest(3))).ConfigureAwait(false); + var bindingAfterFirstDispatch = GetPairCacheValue(streamBindings, requestType, responseType); + var executorAfterFirstDispatch = GetStreamPipelineExecutorValue(streamBindings, requestType, responseType, 1); + + var secondResults = await DrainAsync(context.CreateStream(new GeneratedStreamInvokerRequest(3))).ConfigureAwait(false); + var bindingAfterSecondDispatch = GetPairCacheValue(streamBindings, requestType, responseType); + var executorAfterSecondDispatch = GetStreamPipelineExecutorValue(streamBindings, requestType, responseType, 1); + + Assert.Multiple(() => + { + Assert.That(firstResults, Is.EqualTo([30, 31])); + Assert.That(secondResults, Is.EqualTo([30, 31])); + Assert.That(bindingAfterFirstDispatch, Is.Not.Null); + Assert.That(bindingAfterSecondDispatch, Is.SameAs(bindingAfterFirstDispatch)); + Assert.That(executorAfterFirstDispatch, Is.Not.Null); + Assert.That(executorAfterSecondDispatch, Is.SameAs(executorAfterFirstDispatch)); + Assert.That(GeneratedStreamPipelineTrackingBehavior.InvocationCount, Is.EqualTo(2)); + }); + } + /// /// 验证当实现类型隐藏、但 stream handler interface 仍可直接表达时, /// dispatcher 仍会消费 generated stream invoker descriptor。 @@ -959,6 +1002,65 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests .Invoke(cache, Array.Empty()); } + /// + /// 读取双键缓存中当前保存的对象。 + /// + private static object? GetPairCacheValue(object cache, Type primaryType, Type secondaryType) + { + return InvokeInstanceMethod(cache, "GetValueOrDefaultForTesting", primaryType, secondaryType); + } + + /// + /// 读取指定 stream dispatch binding 中当前缓存的 pipeline executor。 + /// + private static object? GetStreamPipelineExecutorValue( + object streamBindings, + Type requestType, + Type responseType, + int behaviorCount) + { + var binding = GetStreamDispatchBindingValue(streamBindings, requestType, responseType); + return binding is null + ? null + : InvokeInstanceMethod(binding, "GetPipelineExecutorForTesting", behaviorCount); + } + + /// + /// 读取指定流式请求/响应类型对对应的强类型 stream dispatch binding。 + /// + private static object? GetStreamDispatchBindingValue(object streamBindings, Type requestType, Type responseType) + { + var bindingBox = GetPairCacheValue(streamBindings, requestType, responseType); + if (bindingBox is null) + { + return null; + } + + var method = bindingBox.GetType().GetMethod( + "Get", + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + + Assert.That(method, Is.Not.Null, $"Missing stream binding accessor on {bindingBox.GetType().FullName}."); + + return method! + .MakeGenericMethod(responseType) + .Invoke(bindingBox, Array.Empty()); + } + + /// + /// 调用目标对象上的实例方法。 + /// + private static object? InvokeInstanceMethod(object target, string methodName, params object[] arguments) + { + var method = target.GetType().GetMethod( + methodName, + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + + Assert.That(method, Is.Not.Null, $"Missing method {target.GetType().FullName}.{methodName}."); + + return method!.Invoke(target, arguments); + } + /// /// 枚举并收集当前异步流中的全部元素,便于断言 generated stream invoker 的输出。 /// diff --git a/GFramework.Cqrs/Internal/CqrsDispatcher.cs b/GFramework.Cqrs/Internal/CqrsDispatcher.cs index a4891f1b..b9bab1db 100644 --- a/GFramework.Cqrs/Internal/CqrsDispatcher.cs +++ b/GFramework.Cqrs/Internal/CqrsDispatcher.cs @@ -45,8 +45,9 @@ internal sealed class CqrsDispatcher( private static readonly WeakKeyCache NotificationDispatchBindings = new(); - // 卸载安全的进程级缓存:请求/响应类型对采用弱键缓存,避免流式消息类型被静态字典永久保留。 - private static readonly WeakTypePairCache + // 卸载安全的进程级缓存:流式请求/响应类型对命中后复用强类型 dispatch binding 盒子, + // 避免 stream 响应元素在热路径上退化为 object 桥接,同时仍保持弱键卸载安全语义。 + private static readonly WeakTypePairCache StreamDispatchBindings = new(); // 卸载安全的进程级缓存:请求/响应类型对命中后复用强类型 dispatch binding; @@ -190,10 +191,7 @@ internal sealed class CqrsDispatcher( ArgumentNullException.ThrowIfNull(request); var requestType = request.GetType(); - var dispatchBinding = StreamDispatchBindings.GetOrAdd( - requestType, - typeof(TResponse), - static (requestType, responseType) => CreateStreamDispatchBinding(requestType, responseType)); + var dispatchBinding = GetStreamDispatchBinding(requestType); var handler = container.Get(dispatchBinding.HandlerType) ?? throw new InvalidOperationException( $"No CQRS stream handler registered for {requestType.FullName}."); @@ -201,7 +199,7 @@ internal sealed class CqrsDispatcher( PrepareHandler(handler, context); if (!HasStreamBehaviorRegistration(dispatchBinding.BehaviorType)) { - return (IAsyncEnumerable)dispatchBinding.StreamInvoker(handler, request, cancellationToken); + return dispatchBinding.StreamInvoker(handler, request, cancellationToken); } var behaviors = container.GetAll(dispatchBinding.BehaviorType); @@ -211,7 +209,7 @@ internal sealed class CqrsDispatcher( PrepareHandler(behavior, context); } - return (IAsyncEnumerable)dispatchBinding.GetPipelineExecutor(behaviors.Count) + return dispatchBinding.GetPipelineExecutor(behaviors.Count) .Invoke(handler, behaviors, dispatchBinding.StreamInvoker, request, cancellationToken); } @@ -399,75 +397,114 @@ internal sealed class CqrsDispatcher( /// /// 为指定流式请求类型构造完整分发绑定,把服务类型与调用委托聚合到同一缓存项。 /// - private static StreamDispatchBinding CreateStreamDispatchBinding(Type requestType, Type responseType) + private static StreamDispatchBinding CreateStreamDispatchBinding(Type requestType) { - var generatedDescriptor = TryGetGeneratedStreamInvokerDescriptor(requestType, responseType); + var generatedDescriptor = TryGetGeneratedStreamInvokerDescriptor(requestType); if (generatedDescriptor is not null) { var resolvedGeneratedDescriptor = generatedDescriptor.Value; - return new StreamDispatchBinding( + return new StreamDispatchBinding( resolvedGeneratedDescriptor.HandlerType, - typeof(IStreamPipelineBehavior<,>).MakeGenericType(requestType, responseType), + typeof(IStreamPipelineBehavior<,>).MakeGenericType(requestType, typeof(TResponse)), requestType, - responseType, resolvedGeneratedDescriptor.Invoker); } - return new StreamDispatchBinding( - typeof(IStreamRequestHandler<,>).MakeGenericType(requestType, responseType), - typeof(IStreamPipelineBehavior<,>).MakeGenericType(requestType, responseType), + return new StreamDispatchBinding( + typeof(IStreamRequestHandler<,>).MakeGenericType(requestType, typeof(TResponse)), + typeof(IStreamPipelineBehavior<,>).MakeGenericType(requestType, typeof(TResponse)), requestType, - responseType, - CreateStreamInvoker(requestType, responseType)); + CreateStreamInvoker(requestType)); + } + + /// + /// 获取指定流式请求/响应类型对的 dispatch binding;若缓存未命中则按当前加载状态创建。 + /// + /// 流式响应元素类型。 + /// 流式请求运行时类型。 + /// 当前请求/响应类型对对应的强类型 stream dispatch binding。 + private static StreamDispatchBinding GetStreamDispatchBinding(Type requestType) + { + var bindingBox = StreamDispatchBindings.GetOrAdd( + requestType, + typeof(TResponse), + static (cachedRequestType, cachedResponseType) => + CreateStreamDispatchBindingBox(cachedRequestType, cachedResponseType)); + return bindingBox.Get(); + } + + /// + /// 为弱键流式请求缓存创建强类型 binding 盒子,避免响应元素走 object 结果桥接。 + /// + /// 流式响应元素类型。 + /// 流式请求运行时类型。 + /// 缓存命中的响应运行时类型。 + /// 可放入弱键缓存的强类型 binding 盒子。 + private static StreamDispatchBindingBox CreateStreamDispatchBindingBox( + Type requestType, + Type responseType) + { + if (responseType != typeof(TResponse)) + { + throw new InvalidOperationException( + $"Stream dispatch binding cache expected response type {typeof(TResponse).FullName}, but received {responseType.FullName}."); + } + + return StreamDispatchBindingBox.Create(CreateStreamDispatchBinding(requestType)); } /// /// 尝试从容器已注册的 generated stream invoker provider 中获取指定流式请求/响应类型对的元数据。 /// + /// 流式响应元素类型。 /// 流式请求运行时类型。 - /// 流式响应元素类型。 /// 命中时返回强类型化后的描述符;否则返回 - private static StreamInvokerDescriptor? TryGetGeneratedStreamInvokerDescriptor(Type requestType, Type responseType) + private static StreamInvokerDescriptor? TryGetGeneratedStreamInvokerDescriptor(Type requestType) { - return GeneratedStreamInvokers.TryGetValue(requestType, responseType, out var metadata) && + return GeneratedStreamInvokers.TryGetValue(requestType, typeof(TResponse), out var metadata) && metadata is not null - ? CreateStreamInvokerDescriptor(requestType, responseType, metadata) + ? CreateStreamInvokerDescriptor(requestType, metadata) : null; } /// /// 把 provider 返回的弱类型描述符转换为 dispatcher 内部使用的 stream invoker 描述符。 /// + /// 流式响应元素类型。 /// 流式请求运行时类型。 - /// 流式响应元素类型。 /// provider 返回的弱类型描述符。 /// 可直接用于创建 stream dispatch binding 的描述符。 /// 当 provider 返回的委托签名与当前流式请求/响应类型对不匹配时抛出。 - private static StreamInvokerDescriptor CreateStreamInvokerDescriptor( + private static StreamInvokerDescriptor CreateStreamInvokerDescriptor( Type requestType, - Type responseType, GeneratedStreamInvokerMetadata descriptor) { if (!descriptor.InvokerMethod.IsStatic) { throw new InvalidOperationException( - $"Generated CQRS stream invoker provider returned a non-static invoker method for request type {requestType.FullName} and response type {responseType.FullName}."); + $"Generated CQRS stream invoker provider returned a non-static invoker method for request type {requestType.FullName} and response type {typeof(TResponse).FullName}."); } try { - if (Delegate.CreateDelegate(typeof(StreamInvoker), descriptor.InvokerMethod) is not StreamInvoker invoker) + if (Delegate.CreateDelegate(typeof(WeakStreamInvoker), descriptor.InvokerMethod) is not + WeakStreamInvoker weakInvoker) { throw new InvalidOperationException( - $"Generated CQRS stream invoker provider returned an incompatible invoker for request type {requestType.FullName} and response type {responseType.FullName}."); + $"Generated CQRS stream invoker provider returned an incompatible invoker for request type {requestType.FullName} and response type {typeof(TResponse).FullName}."); } - return new StreamInvokerDescriptor(descriptor.HandlerType, invoker); + // generated stream descriptor 的公开契约仍以 object 返回值暴露异步流; + // 这里在 binding 创建时只做一次适配,把后续 CreateStream 热路径保持为强类型调用。 + var adapter = new GeneratedStreamInvokerAdapter(weakInvoker); + StreamInvoker invoker = (handler, request, cancellationToken) => + adapter.Invoke(handler, request, cancellationToken); + return new StreamInvokerDescriptor(descriptor.HandlerType, invoker); } catch (ArgumentException exception) { throw new InvalidOperationException( - $"Generated CQRS stream invoker provider returned an incompatible invoker for request type {requestType.FullName} and response type {responseType.FullName}.", + $"Generated CQRS stream invoker provider returned an incompatible invoker for request type {requestType.FullName} and response type {typeof(TResponse).FullName}.", exception); } } @@ -539,11 +576,11 @@ internal sealed class CqrsDispatcher( /// /// 生成流式处理器调用委托,避免每次创建流都重复反射。 /// - private static StreamInvoker CreateStreamInvoker(Type requestType, Type responseType) + private static StreamInvoker CreateStreamInvoker(Type requestType) { var method = StreamHandlerInvokerMethodDefinition - .MakeGenericMethod(requestType, responseType); - return (StreamInvoker)Delegate.CreateDelegate(typeof(StreamInvoker), method); + .MakeGenericMethod(requestType, typeof(TResponse)); + return (StreamInvoker)Delegate.CreateDelegate(typeof(StreamInvoker), method); } /// @@ -594,7 +631,7 @@ internal sealed class CqrsDispatcher( /// /// 执行已强类型化的流式处理器调用。 /// - private static object InvokeStreamHandler( + private static IAsyncEnumerable InvokeStreamHandler( object handler, object request, CancellationToken cancellationToken) @@ -609,10 +646,10 @@ internal sealed class CqrsDispatcher( /// 执行指定行为数量的强类型 stream pipeline executor。 /// 该入口本身是缓存的固定 executor 形状;每次建流只绑定当前 handler 与 behaviors 实例。 /// - private static object InvokeStreamPipelineExecutor( + private static IAsyncEnumerable InvokeStreamPipelineExecutor( object handler, IReadOnlyList behaviors, - StreamInvoker streamInvoker, + StreamInvoker streamInvoker, object request, CancellationToken cancellationToken) where TRequest : IStreamRequest @@ -638,12 +675,17 @@ internal sealed class CqrsDispatcher( private delegate ValueTask NotificationInvoker(object handler, object notification, CancellationToken cancellationToken); - private delegate object StreamInvoker(object handler, object request, CancellationToken cancellationToken); + private delegate IAsyncEnumerable StreamInvoker( + object handler, + object request, + CancellationToken cancellationToken); - private delegate object StreamPipelineInvoker( + private delegate object WeakStreamInvoker(object handler, object request, CancellationToken cancellationToken); + + private delegate IAsyncEnumerable StreamPipelineInvoker( object handler, IReadOnlyList behaviors, - StreamInvoker streamInvoker, + StreamInvoker streamInvoker, object request, CancellationToken cancellationToken); @@ -692,6 +734,72 @@ internal sealed class CqrsDispatcher( } } + /// + /// 将不同响应类型的 stream dispatch binding 包装到统一弱缓存值中, + /// 同时保留强类型流式委托,避免响应元素退化为 object 桥接。 + /// + private abstract class StreamDispatchBindingBox + { + /// + /// 创建一个新的强类型 stream dispatch binding 盒子。 + /// + public static StreamDispatchBindingBox Create(StreamDispatchBinding binding) + { + ArgumentNullException.ThrowIfNull(binding); + return new StreamDispatchBindingBox(binding); + } + + /// + /// 读取指定响应类型的 stream dispatch binding。 + /// + public abstract StreamDispatchBinding Get(); + } + + /// + /// 保存特定响应类型的 stream dispatch binding。 + /// + /// 流式响应元素类型。 + private sealed class StreamDispatchBindingBox(StreamDispatchBinding binding) + : StreamDispatchBindingBox + { + private readonly StreamDispatchBinding _binding = binding; + + /// + /// 以原始强类型返回当前 binding;若请求的响应类型不匹配则抛出异常。 + /// + public override StreamDispatchBinding Get() + { + if (typeof(TRequestedResponse) != typeof(TResponse)) + { + throw new InvalidOperationException( + $"Cached stream dispatch binding for {typeof(TResponse).FullName} cannot be used as {typeof(TRequestedResponse).FullName}."); + } + + return (StreamDispatchBinding)(object)_binding; + } + } + + /// + /// 将 generated stream provider 的弱类型开放静态入口适配为 dispatcher 内部的强类型流式委托。 + /// 适配对象与 binding 同生命周期缓存,避免在每次建流时重复创建桥接闭包。 + /// + /// 流式响应元素类型。 + private sealed class GeneratedStreamInvokerAdapter(WeakStreamInvoker invoker) + { + private readonly WeakStreamInvoker _invoker = invoker; + + /// + /// 调用 generated provider 暴露的弱类型入口,并把返回结果物化为当前响应类型的异步流。 + /// + public IAsyncEnumerable Invoke( + object handler, + object request, + CancellationToken cancellationToken) + { + return (IAsyncEnumerable)_invoker(handler, request, cancellationToken); + } + } + /// /// 保存通知分发路径所需的服务类型与强类型调用委托。 /// 该绑定把“容器解析哪个服务类型”与“如何调用处理器”聚合到同一缓存项中。 @@ -722,17 +830,16 @@ internal sealed class CqrsDispatcher( /// 保存流式请求分发路径所需的服务类型与调用委托。 /// 该绑定让建流热路径只需一次缓存命中即可获得解析与调用所需元数据。 /// - private sealed class StreamDispatchBinding( + private sealed class StreamDispatchBinding( Type handlerType, Type behaviorType, Type requestType, - Type responseType, - StreamInvoker streamInvoker) + StreamInvoker streamInvoker) { // 线程安全:该缓存按 behaviorCount 复用 stream pipeline executor 形状,缓存项只保存委托与数量信息, // 不会跨建流缓存 handler 或 behavior 实例。若不同请求持续出现新的行为数量组合,字典会随之增长。 - private readonly ConcurrentDictionary _pipelineExecutors = new(); - private readonly StreamPipelineInvoker _pipelineInvoker = CreateStreamPipelineInvoker(requestType, responseType); + private readonly ConcurrentDictionary> _pipelineExecutors = new(); + private readonly StreamPipelineInvoker _pipelineInvoker = CreateStreamPipelineInvoker(requestType); /// /// 获取流式请求处理器在容器中的服务类型。 @@ -747,19 +854,19 @@ internal sealed class CqrsDispatcher( /// /// 获取执行流式请求处理器的调用委托。 /// - public StreamInvoker StreamInvoker { get; } = streamInvoker; + public StreamInvoker StreamInvoker { get; } = streamInvoker; /// /// 获取指定行为数量对应的 stream pipeline executor。 /// executor 形状会按行为数量缓存,但不会缓存 handler 或 behavior 实例。 /// - public StreamPipelineExecutor GetPipelineExecutor(int behaviorCount) + public StreamPipelineExecutor GetPipelineExecutor(int behaviorCount) { ArgumentOutOfRangeException.ThrowIfNegative(behaviorCount); - return _pipelineExecutors.GetOrAdd( + return _pipelineExecutors.GetOrAdd>( behaviorCount, - static (count, state) => new StreamPipelineExecutor(count, state.PipelineInvoker), - new StreamPipelineExecutorFactoryState(_pipelineInvoker)); + static (count, state) => CreateStreamPipelineExecutor(count, state.PipelineInvoker), + new StreamPipelineExecutorFactoryState(_pipelineInvoker)); } /// @@ -922,27 +1029,41 @@ internal sealed class CqrsDispatcher( /// /// 流式请求处理器在容器中的服务类型。 /// 执行流式请求处理器的调用委托。 - private readonly record struct StreamInvokerDescriptor( + private readonly record struct StreamInvokerDescriptor( Type HandlerType, - StreamInvoker Invoker); + StreamInvoker Invoker); /// /// 为指定流式请求类型创建可跨多个 behaviorCount 复用的 typed pipeline invoker。 /// - private static StreamPipelineInvoker CreateStreamPipelineInvoker(Type requestType, Type responseType) + private static StreamPipelineInvoker CreateStreamPipelineInvoker(Type requestType) { var method = StreamPipelineInvokerMethodDefinition - .MakeGenericMethod(requestType, responseType); - return (StreamPipelineInvoker)Delegate.CreateDelegate(typeof(StreamPipelineInvoker), method); + .MakeGenericMethod(requestType, typeof(TResponse)); + return (StreamPipelineInvoker)Delegate.CreateDelegate( + typeof(StreamPipelineInvoker), + method); + } + + /// + /// 为指定流式请求/响应类型与固定行为数量创建 pipeline executor。 + /// 行为数量用于表达缓存形状,实际建流仍会消费本次容器解析出的 handler 与 behaviors 实例。 + /// + private static StreamPipelineExecutor CreateStreamPipelineExecutor( + int behaviorCount, + StreamPipelineInvoker invoker) + { + ArgumentOutOfRangeException.ThrowIfNegative(behaviorCount); + return new StreamPipelineExecutor(behaviorCount, invoker); } /// /// 保存固定行为数量下的 typed stream pipeline executor 形状。 /// 该对象自身可跨建流复用,但每次调用都只绑定当前 handler 与 behavior 实例。 /// - private sealed class StreamPipelineExecutor( + private sealed class StreamPipelineExecutor( int behaviorCount, - StreamPipelineInvoker invoker) + StreamPipelineInvoker invoker) { /// /// 获取此 executor 预期处理的行为数量。 @@ -952,10 +1073,10 @@ internal sealed class CqrsDispatcher( /// /// 使用当前 handler / behaviors / request 执行缓存的 pipeline 形状。 /// - public object Invoke( + public IAsyncEnumerable Invoke( object handler, IReadOnlyList behaviors, - StreamInvoker streamInvoker, + StreamInvoker streamInvoker, object request, CancellationToken cancellationToken) { @@ -972,8 +1093,8 @@ internal sealed class CqrsDispatcher( /// /// 为 stream pipeline executor 缓存携带 typed pipeline invoker,避免按行为数量建缓存时创建闭包。 /// - private readonly record struct StreamPipelineExecutorFactoryState( - StreamPipelineInvoker PipelineInvoker); + private readonly record struct StreamPipelineExecutorFactoryState( + StreamPipelineInvoker PipelineInvoker); /// /// 供 registrar 在 generated registry 激活后登记 request invoker 元数据。 @@ -1111,12 +1232,12 @@ internal sealed class CqrsDispatcher( /// private sealed class StreamPipelineInvocation( IStreamRequestHandler handler, - StreamInvoker streamInvoker, + StreamInvoker streamInvoker, IReadOnlyList behaviors) where TRequest : IStreamRequest { private readonly IStreamRequestHandler _handler = handler; - private readonly StreamInvoker _streamInvoker = streamInvoker; + private readonly StreamInvoker _streamInvoker = streamInvoker; private readonly IReadOnlyList _behaviors = behaviors; private readonly StreamMessageHandlerDelegate?[] _continuations = new StreamMessageHandlerDelegate?[behaviors.Count + 1]; @@ -1167,7 +1288,7 @@ internal sealed class CqrsDispatcher( /// private IAsyncEnumerable InvokeHandler(TRequest request, CancellationToken cancellationToken) { - return (IAsyncEnumerable)_streamInvoker(_handler, request, cancellationToken); + return _streamInvoker(_handler, request, cancellationToken); } ///