diff --git a/src/Merq.Benchmarks/MerqVsMediatR.cs b/src/Merq.Benchmarks/MerqVsMediatR.cs index 14c23d4..e3e0fcd 100644 --- a/src/Merq.Benchmarks/MerqVsMediatR.cs +++ b/src/Merq.Benchmarks/MerqVsMediatR.cs @@ -1,10 +1,10 @@ -using BenchmarkDotNet.Attributes; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Diagnostics.Windows.Configs; using MediatR; using Microsoft.Extensions.DependencyInjection; -using System.Diagnostics; -using BenchmarkDotNet.Diagnostics.Windows.Configs; namespace Merq.MerqVsMediatR; diff --git a/src/Merq.Core/Merq.Core.csproj b/src/Merq.Core/Merq.Core.csproj index 5a32248..7240ab3 100644 --- a/src/Merq.Core/Merq.Core.csproj +++ b/src/Merq.Core/Merq.Core.csproj @@ -10,6 +10,7 @@ $(Title) Only the main application assembly needs to reference this package. Components and extensions can simply reference the interfaces in Merq. + $(DefineConstants);DYNAMIC_DISPATCH diff --git a/src/Merq.Core/MessageBus.cs b/src/Merq.Core/MessageBus.cs index 95ba24c..5c23036 100644 --- a/src/Merq.Core/MessageBus.cs +++ b/src/Merq.Core/MessageBus.cs @@ -73,7 +73,7 @@ public class MessageBus : IMessageBus readonly IServiceProvider services; readonly IServiceCollection? collection; - // These executors are needed when the commadn types involved are not public. + // These executors are needed when the command types involved are not public. // For the public cases, we just rely on the built-in dynamic dispatching readonly ConcurrentDictionary voidExecutors = new(); readonly ConcurrentDictionary voidAsyncExecutors = new(); @@ -194,15 +194,18 @@ public void Execute(ICommand command, [CallerMemberName] string? callerName = de try { - if (type.IsPublic) +#if DYNAMIC_DISPATCH + + if (type.IsPublic || type.IsNestedPublic) // For public types, we can use the faster dynamic dispatch approach ExecuteCore((dynamic)command); else +#endif voidExecutors.GetOrAdd(type, type - => (VoidDispatcher)Activator.CreateInstance( - typeof(VoidDispatcher<>).MakeGenericType(type), - this)!) - .Execute(command); + => (VoidDispatcher)Activator.CreateInstance( + typeof(VoidDispatcher<>).MakeGenericType(type), + this)!) + .Execute(command); } catch (Exception e) { @@ -221,9 +224,11 @@ public TResult Execute(ICommand command, [CallerMemberName] st try { - if (type.IsPublic) +#if DYNAMIC_DISPATCH + if (type.IsPublic || type.IsNestedPublic) // For public types, we can use the faster dynamic dispatch approach return WithResult().Execute((dynamic)command); +#endif return (TResult)resultExecutors.GetOrAdd(type, type => (ResultDispatcher)Activator.CreateInstance( @@ -248,10 +253,11 @@ public Task ExecuteAsync(IAsyncCommand command, CancellationToken cancellation = try { - if (type.IsPublic) +#if DYNAMIC_DISPATCH + if (type.IsPublic || type.IsNestedPublic) // For public types, we can use the faster dynamic dispatch approach return ExecuteAsyncCore((dynamic)command, cancellation); - +#endif return voidAsyncExecutors.GetOrAdd(type, type => (VoidAsyncDispatcher)Activator.CreateInstance( typeof(VoidAsyncDispatcher<>).MakeGenericType(type), @@ -275,10 +281,11 @@ public Task ExecuteAsync(IAsyncCommand command, Cance try { - if (type.IsPublic) +#if DYNAMIC_DISPATCH + if (type.IsPublic || type.IsNestedPublic) // For public types, we can use the faster dynamic dispatch approach return WithResult().ExecuteAsync((dynamic)command, cancellation); - +#endif return (Task)resultAsyncExecutors.GetOrAdd(type, type => (ResultAsyncDispatcher)Activator.CreateInstance( typeof(ResultAsyncDispatcher<,>).MakeGenericType(type, typeof(TResult)), @@ -303,10 +310,11 @@ public IAsyncEnumerable ExecuteStream(IStreamCommand try { +#if DYNAMIC_DISPATCH if (type.IsPublic || type.IsNestedPublic) // For public types, we can use the faster dynamic dispatch approach return WithResult().ExecuteStream((dynamic)command, cancellation); - +#endif return (IAsyncEnumerable)resultAsyncExecutors.GetOrAdd(type, type => (ResultAsyncDispatcher)Activator.CreateInstance( typeof(ResultStreamDispatcher<,>).MakeGenericType(type, typeof(TResult)), @@ -734,7 +742,8 @@ abstract class VoidDispatcher class VoidDispatcher(MessageBus bus) : VoidDispatcher where TCommand : ICommand { - public override void Execute(IExecutable command) => bus.ExecuteCore((TCommand)command); + public override void Execute(IExecutable command) + => bus.ExecuteCore((TCommand)command); } abstract class ResultDispatcher @@ -744,7 +753,8 @@ abstract class ResultDispatcher class ResultDispatcher(MessageBus bus) : ResultDispatcher where TCommand : ICommand { - public override object? Execute(IExecutable command) => bus.ExecuteCore((TCommand)command); + public override object? Execute(IExecutable command) + => bus.ExecuteCore((TCommand)command); } abstract class VoidAsyncDispatcher @@ -754,7 +764,8 @@ abstract class VoidAsyncDispatcher class VoidAsyncDispatcher(MessageBus bus) : VoidAsyncDispatcher where TCommand : IAsyncCommand { - public override Task ExecuteAsync(IExecutable command, CancellationToken cancellation) => bus.ExecuteAsyncCore((TCommand)command, cancellation); + public override Task ExecuteAsync(IExecutable command, CancellationToken cancellation) + => bus.ExecuteAsyncCore((TCommand)command, cancellation); } abstract class ResultAsyncDispatcher diff --git a/src/Merq.Tests/MessageBusSpec.cs b/src/Merq.Tests/MessageBusSpec.cs index 843ac13..71e39c8 100644 --- a/src/Merq.Tests/MessageBusSpec.cs +++ b/src/Merq.Tests/MessageBusSpec.cs @@ -467,6 +467,22 @@ public void when_executing_non_public_command_then_invokes_handler() bus.Execute(new NonPublicCommand()); } + [Fact] + public void when_executing_nested_public_command_then_invokes_handler() + { + var handler = new Mock>(); + var bus = new MessageBus(new ServiceCollection() + .AddSingleton(handler.Object) + .AddSingleton>(handler.Object) + .BuildServiceProvider()); + + var command = new NestedPublicCommand(); + + bus.Execute(command); + + handler.Verify(x => x.Execute(command)); + } + [Fact] public void when_executing_non_public_command_result_then_invokes_handler() { @@ -570,6 +586,8 @@ public void when_execute_throws_activity_has_error_status() class NestedEvent { } + public class NestedPublicCommand : ICommand { } + #if NET6_0_OR_GREATER public record StreamCommand(int Count) : IStreamCommand;