You cannot cast Func<T, Task>
to Func<IMessage, Task>
like that, for several really good reasons. What you can do however is cast it to Delegate
and then cast back to the correct type on invocation. This is functionally equivalent to casting an instance to Object
and back again.
Here's a sample:
public class TaskFactory
{
private readonly Dictionary<Type, Delegate> _factories = new Dictionary<Type, Delegate>();
public void Register<T>(Func<T, Task> factory)
{
_factories[typeof(T)] = factory;
}
public Task Invoke<T>(T value)
{
if (_factories.TryGetValue(typeof(T), out var del) && del is Func<T, Task> fn)
return fn.Invoke(value);
return Task.CompletedTask;
}
}
This depends on the type being passed at compile time however, so if you're pass in an IMessage
then it will try to find a factory method for IMessage
instead of the actual concrete type.
To handle proper type determination at runtime you can either go the whole hog and construct the right kind of type to check against... or trust your code to not screw up the content of your task factory cache. Here's the trusting version:
public Task Invoke(object instance)
{
if (_factories.TryGetValue(instance?.GetType(), out var del))
return (Task)del.DynamicInvoke(instance);
return Task.CompletedTask;
}
Note that here we're using DynamicInvoke
to run the factory function, and casting the result back to Task
. As long as nothing changes the contents of your _factories
dictionary then you'll be fine. Honest.
For completeness here's the paranoid version. This one finds the instance type, fetches the factory method, then checks to make sure that the factory method matches the type signature we're expecting. If all that works out then it will use DynamicInvoke
to run the func.
public Task Invoke(object instance)
{
if (instance is null)
throw new ArgumentNullException(nameof(instance));
// Get the true type of the instance
var type = instance.GetType();
if (_factories.TryGetValue(type, out var del))
{
// Get the type for Func<T, Task> where T is the true type of the parameter
var deltype = typeof(Func<,>).MakeGenericType(type, typeof(Task));
// check that the delegate matches our expected delegate type then execute
if (deltype.IsAssignableFrom(del.GetType()))
return (Task)del.DynamicInvoke(instance);
}
return Task.CompletedTask;
}
I've basically ignored the IMessage
interface here but you can simply plug it into the appropriate places in the code.
On the topic of type filtering in your Subscribe
method, it seems a little odd. You require a class that implements IMessage
and has a default constructor... but you never construct an instance of the object anywhere in Subscribe
. In general you want to put only those limits that you actual require in the code, either right there or somewhere that might need those limitations later.