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;