4
\$\begingroup\$

Here is the sender and handler interfaces:

public interface ISender
{
    Task SendAsync(object e);
}

public interface IHandler<in TEvent>
{
    Task HandleAsync(TEvent e);
}

So I register in IoC container a sender service implementation, which dispatches events to all the compatible IHandler<in T> implementations. I use Autofac with a contravariance source, but there could be something else:

[Service]
public class Sender : ISender
{
    public Sender(IServiceProvider provider) => Provider = provider;
    IServiceProvider Provider { get; }

    public async Task SendAsync(object e)
    {
        var eventType = e.GetType();
        var handlerType = typeof(IHandler<>).MakeGenericType(eventType);
        var handlerListType = typeof(IEnumerable<>).MakeGenericType(handlerType);
        var method = handlerType.GetMethod("HandleAsync", new[] { eventType });
        var handlers = ((IEnumerable)Provider.GetService(handlerListType)).OfType<object>();
        await Task.WhenAll(
            handlers.Select(h =>
                Task.Run(() => (Task)method.Invoke(h, new[] { e }))
                    .ContinueWith(_ => { })));
    }
}
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

I think the code would be more readable and easier to maintain if there was a method that handled the main calling of the other handlers.

Unless there is a reason for provider to be a property I would make it a readonly field instead.

public class Sender : ISender
{
    private readonly MethodInfo handlerMethodInfo;
    private readonly IServiceProvider provider;
   
    public Sender(IServiceProvider provider)
    {
        this.provider = provider;
        Func<Task, object> handlerMethod = HandleAsync<object>;
        handlerMethodInfo = handlerMethod.Method.GetGenericMethodDefinition(); 
    }

    private async Task HandleAsync<TEvent>(TEvent e)
    {
        var handlers = provider.GetService<IEnumerable<IHandler<TEvent>>>();
        await Task.WhenAll(handlers.Select(h => h.HandleAsync(e)));
    }

    public async Task SendAsync(object e)
    {
        // Call the HandleAsync method
        var eventType = e.GetType();
        var method = handlerMethodInfo.MakeGenericMethod(eventType);
        await (Task)method.Invoke(this, new object[] { e });
     }
}

In the constructor just getting a reference to the method needed to call and in a way that's type safe. Then in the SendAsync we will use reflection to call the private method to call the other classes. Now the reflection code is a lot smaller but the main logic is still in "normal" code in the HandleAsync method.

We could turn the SendAsync into ExpressionTrees to only require reflection once but they come with a high price. Typically code would need to be called ~500 to make up the speed hit of compiling the expression vs reflection.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.