< Summary

Information
Class: NexMediator.Core.Internal.NexNotificationExecutor
Assembly: NexMediator.Core
File(s): /home/runner/work/NexMediator/NexMediator/src/NexMediator.Core/Internal/NexNotificationExecutor.cs
Line coverage
98%
Covered lines: 62
Uncovered lines: 1
Coverable lines: 63
Total lines: 119
Line coverage: 98.4%
Branch coverage
75%
Covered branches: 3
Total branches: 4
Branch coverage: 75%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
Publish(...)50%2285.71%
CreateDispatcher(...)100%22100%
ForEach(...)100%11100%

File(s)

/home/runner/work/NexMediator/NexMediator/src/NexMediator.Core/Internal/NexNotificationExecutor.cs

#LineLine coverage
 1using Microsoft.Extensions.DependencyInjection;
 2using NexMediator.Abstractions.Interfaces;
 3using System.Collections.Concurrent;
 4using System.Linq.Expressions;
 5
 6namespace NexMediator.Core.Internal;
 7
 8/// <summary>
 9/// Builds and caches a dispatcher for each notification type.
 10/// The dispatcher:
 11/// 1. Gets all notification handlers.
 12/// 2. Calls each handler.Handle in parallel.
 13/// 3. Uses Task.WhenAll to wait for all handlers to finish.
 14/// </summary>
 15internal static class NexNotificationExecutor
 16{
 117    private static readonly ConcurrentDictionary<Type, Func<object, IServiceProvider, CancellationToken, Task>> _cache =
 18
 19    /// <summary>
 20    /// Publishes a notification by invoking all handlers in parallel.
 21    /// </summary>
 22    /// <typeparam name="TNotification">Notification type.</typeparam>
 23    /// <param name="notification">The notification to publish.</param>
 24    /// <param name="provider">Service provider to resolve handlers.</param>
 25    /// <param name="ct">Cancellation token.</param>
 26    /// <returns>A Task that completes when all handlers have run.</returns>
 27    /// <exception cref="ArgumentNullException">Thrown if notification is null.</exception>
 28    public static Task Publish<TNotification>(TNotification notification, IServiceProvider provider, CancellationToken c
 29        where TNotification : INexNotification
 230    {
 231        if (notification == null)
 032            throw new ArgumentNullException(nameof(notification));
 33
 234        var type = typeof(TNotification);
 235        var dispatcher = _cache.GetOrAdd(type, CreateDispatcher);
 236        return dispatcher(notification!, provider, ct);
 237    }
 38
 39    /// <summary>
 40    /// Creates a compiled dispatcher delegate for a notification type.
 41    /// </summary>
 42    private static Func<object, IServiceProvider, CancellationToken, Task> CreateDispatcher(Type type)
 143    {
 144        var handlerInterface = typeof(INexNotificationHandler<>).MakeGenericType(type);
 145        var method = handlerInterface.GetMethod(nameof(INexNotificationHandler<INexNotification>.Handle))!;
 46
 147        var notificationParam = Expression.Parameter(typeof(object), "notification");
 148        var providerParam = Expression.Parameter(typeof(IServiceProvider), "provider");
 149        var ctParam = Expression.Parameter(typeof(CancellationToken), "ct");
 50
 151        var castNotification = Expression.Convert(notificationParam, type);
 152        var getServices = typeof(ServiceProviderServiceExtensions)
 153            .GetMethods()
 454            .First(m => m.Name == nameof(ServiceProviderServiceExtensions.GetServices)
 455                        && m.IsGenericMethodDefinition)
 156            .MakeGenericMethod(handlerInterface);
 157        var handlersCall = Expression.Call(getServices, providerParam);
 58
 159        var handlersVar = Expression.Variable(typeof(IEnumerable<>).MakeGenericType(handlerInterface), "handlers");
 160        var tasksVar = Expression.Variable(typeof(List<Task>), "tasks");
 161        var handlerVar = Expression.Variable(handlerInterface, "handler");
 62
 163        var newList = Expression.New(typeof(List<Task>));
 164        var assignList = Expression.Assign(tasksVar, newList);
 165        var assignHandlers = Expression.Assign(handlersVar, handlersCall);
 66
 167        var callHandle = Expression.Call(handlerVar, method, castNotification, ctParam);
 168        var addCall = Expression.Call(tasksVar, nameof(List<Task>.Add), null, callHandle);
 169        var loop = ForEach(handlerInterface, handlersVar, handlerVar, addCall);
 170        var whenAll = Expression.Call(typeof(Task).GetMethod(nameof(Task.WhenAll), new[] { typeof(IEnumerable<Task>) })!
 71
 172        var body = Expression.Block(
 173            new[] { handlersVar, tasksVar, handlerVar },
 174            assignHandlers,
 175            assignList,
 176            loop,
 177            whenAll);
 78
 179        return Expression.Lambda<Func<object, IServiceProvider, CancellationToken, Task>>(
 180            body,
 181            notificationParam,
 182            providerParam,
 183            ctParam)
 184        .Compile();
 185    }
 86
 87    /// <summary>
 88    /// Helper to build a foreach over a collection expression.
 89    /// </summary>
 90    /// <param name="itemType">Type of items in the collection.</param>
 91    /// <param name="collection">Expression for the collection.</param>
 92    /// <param name="loopVar">Loop variable expression.</param>
 93    /// <param name="body">Loop body expression.</param>
 94    /// <returns>An expression representing the foreach loop.</returns>
 95    private static Expression ForEach(Type itemType, Expression collection, ParameterExpression loopVar, Expression body
 196    {
 197        var enumerableType = typeof(IEnumerable<>).MakeGenericType(itemType);
 198        var enumeratorType = typeof(IEnumerator<>).MakeGenericType(itemType);
 99
 1100        var enumVar = Expression.Variable(enumeratorType, "enumerator");
 1101        var getEnum = Expression.Call(collection, enumerableType.GetMethod(nameof(IEnumerable<object>.GetEnumerator))!);
 1102        var assignEnum = Expression.Assign(enumVar, getEnum);
 103
 1104        var moveNext = Expression.Call(enumVar, typeof(System.Collections.IEnumerator).GetMethod(nameof(System.Collectio
 1105        var breakLabel = Expression.Label("LoopBreak");
 106
 1107        var loop = Expression.Loop(
 1108            Expression.IfThenElse(
 1109                Expression.IsFalse(moveNext),
 1110                Expression.Break(breakLabel),
 1111                Expression.Block(
 1112                    new[] { loopVar },
 1113                    Expression.Assign(loopVar, Expression.Property(enumVar, nameof(IEnumerator<object>.Current))),
 1114                    body)),
 1115            breakLabel);
 116
 1117        return Expression.Block(new[] { enumVar }, assignEnum, loop);
 1118    }
 119}