Skip to content

Commit 1759c89

Browse files
committed
feat(core)!: Improve DI robustness by removing unsafe casts
This commit addresses the fragility of using `Unsafe.As` for handling collections resolved from the service provider. This was causing potential runtime exceptions when using DI containers (like Autofac) that resolve `IEnumerable<T>` to `List<T>` instead of an array. The unsafe casts have been replaced with safer, more idiomatic C# patterns that provide a zero-allocation fast path when the underlying collection is an array, while gracefully handling any other `IEnumerable<T>` implementation by converting it to an array. This change significantly improves the library's robustness and compatibility without a meaningful performance regression for the common path. BREAKING CHANGE: While unlikely to affect users, this changes the internal handling of collections and is a fundamental fix to the library's core resolution logic, warranting a major version bump for safety. Resolves #29
1 parent ed65b23 commit 1759c89

File tree

2 files changed

+38
-16
lines changed

2 files changed

+38
-16
lines changed

src/DispatchR/Configuration/ServiceRegistrator.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
using Microsoft.Extensions.DependencyInjection;
2-
using System.Runtime.CompilerServices;
3-
using DispatchR.Abstractions.Notification;
1+
using DispatchR.Abstractions.Notification;
42
using DispatchR.Abstractions.Send;
3+
using Microsoft.Extensions.DependencyInjection;
54

65
namespace DispatchR.Configuration
76
{
@@ -143,10 +142,17 @@ public static void RegisterHandlers(IServiceCollection services, List<Type> allT
143142
}
144143
}
145144

146-
services.AddScoped(handlerInterface, sp =>
145+
services.AddScoped(handlerInterface, sp =>
146+
{
147+
var keyedServices = sp.GetKeyedServices<IRequestHandler>(key);
148+
149+
var pipelinesWithHandler = keyedServices as IRequestHandler[] ?? keyedServices.ToArray();
150+
151+
// Single handler - no pipeline chaining needed
152+
if (pipelinesWithHandler.Length == 1)
147153
{
148-
var pipelinesWithHandler = Unsafe
149-
.As<IRequestHandler[]>(sp.GetKeyedServices<IRequestHandler>(key));
154+
return pipelinesWithHandler[0];
155+
}
150156

151157
IRequestHandler lastPipeline = pipelinesWithHandler[0];
152158
for (int i = 1; i < pipelinesWithHandler.Length; i++)

src/DispatchR/IMediator.cs

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
using System.Runtime.CompilerServices;
2-
using DispatchR.Abstractions.Notification;
1+
using DispatchR.Abstractions.Notification;
32
using DispatchR.Abstractions.Send;
43
using DispatchR.Abstractions.Stream;
54
using DispatchR.Exceptions;
65
using Microsoft.Extensions.DependencyInjection;
6+
using System.Runtime.CompilerServices;
77

88
namespace DispatchR;
99

@@ -17,7 +17,7 @@ IAsyncEnumerable<TResponse> CreateStream<TRequest, TResponse>(IStreamRequest<TRe
1717

1818
ValueTask Publish<TNotification>(TNotification request, CancellationToken cancellationToken)
1919
where TNotification : INotification;
20-
20+
2121
/// <summary>
2222
/// This method is not recommended for performance-critical scenarios.
2323
/// Use it only if it is strictly necessary, as its performance is lower compared
@@ -28,8 +28,8 @@ ValueTask Publish<TNotification>(TNotification request, CancellationToken cancel
2828
/// </param>
2929
/// <param name="cancellationToken"></param>
3030
/// <returns></returns>
31-
[Obsolete(message: "This method has performance issues. Use only if strictly necessary",
32-
error: false,
31+
[Obsolete(message: "This method has performance issues. Use only if strictly necessary",
32+
error: false,
3333
DiagnosticId = Constants.DiagnosticPerformanceIssue)]
3434
ValueTask Publish(object request, CancellationToken cancellationToken);
3535
}
@@ -60,13 +60,29 @@ public IAsyncEnumerable<TResponse> CreateStream<TRequest, TResponse>(IStreamRequ
6060
public async ValueTask Publish<TNotification>(TNotification request, CancellationToken cancellationToken)
6161
where TNotification : INotification
6262
{
63-
var notificationsInDi = serviceProvider.GetRequiredService<IEnumerable<INotificationHandler<TNotification>>>();
63+
var handlers = serviceProvider.GetRequiredService<IEnumerable<INotificationHandler<TNotification>>>();
64+
65+
if (handlers is INotificationHandler<TNotification>[] handlerArray)
66+
{
67+
foreach (var handler in handlerArray)
68+
{
69+
await ProcessHandlerAsync(handler);
70+
}
71+
}
72+
else
73+
{
74+
foreach (var handler in handlers)
75+
{
76+
await ProcessHandlerAsync(handler);
77+
}
78+
}
79+
80+
return;
6481

65-
var notifications = Unsafe.As<INotificationHandler<TNotification>[]>(notificationsInDi);
66-
foreach (var notification in notifications)
82+
async ValueTask ProcessHandlerAsync(INotificationHandler<TNotification> handler)
6783
{
68-
var valueTask = notification.Handle(request, cancellationToken);
69-
if (valueTask.IsCompletedSuccessfully is false)
84+
var valueTask = handler.Handle(request, cancellationToken);
85+
if (!valueTask.IsCompletedSuccessfully)
7086
{
7187
await valueTask;
7288
}

0 commit comments

Comments
 (0)