Unable to cast resolved object to generic abstraction autofac C#

Viewed 474

Hey I try to cast my object to interface which this object should implements. But during executing code I have following error:

Unable to cast object of type GetTreeHandler to IQueryHandler, IQuery<Tree>.

Why? Everything seems to be ok.

Any idea without using dynamic?

QueryDispatcher

    public async Task<TResult> ExecuteAsync<TResult>(IQuery<TResult> query)
    {
        if (query == null)
        {
            throw new ArgumentNullException("Query can not be null.");
        }

        var handlerType = typeof (IQueryHandler<,>).MakeGenericType(query.GetType(), typeof (TResult));
        var handler = _context.Resolve(handlerType);

        IQueryHandler<IQuery<TResult>, TResult> castedHandler = (IQueryHandler<IQuery<TResult>, TResult>) handler;

        return (TResult) await castedHandler.ExecuteAsync(query);    
    }   

GetTreeHandler

   public class GetTreeHandler : IQueryHandler<GetTree, Tree>
    {
        private readonly ProductContext _context;
        public string Name { get; set; }
        public GetTreeHandler(ProductContext context)
        {
            _context = context;
        }
        public async Task<Tree> ExecuteAsync(GetTree query)
            =>  await Task.FromResult(
                    _context.Trees.FirstOrDefault(x => x.Id == query.Id)
                );
    }

EDIT

My current solution(partially works):

    public async Task<TResult> ExecuteAsync<TResult>(IQuery query) where TResult : class
    {
        if (query == null)
        {
            throw new ArgumentNullException("Query can not be null.");
        }

        var handlerType = typeof (IQueryHandler<,>).MakeGenericType(query.GetType(), typeof (TResult));
        var handler = _context.Resolve(handlerType);

        return await (Task<TResult>)                        
            handler.GetType()                               
                   .GetMethod("ExecuteAsync")               
                   .Invoke(handler, new object[]{query});   
    }  

Why partially? Eg. handlers:

Doesn't work:

public class GetIndexesHandler : IQueryHandler<GetIndexes, IEnumerable<AssocIndexDto>>

Works:

public class GetIndexesHandler : IQueryHandler<GetIndexes,AssocIndexDto>

As you can see these two classes implements the same interface but with different second parameter type.

The problem is that Resolve method cannot find the registered service, but if I try to debug this code, I can see that the wanted service is registed correctly, but it can't be found.

Have you any idea how to solve it?

1 Answers

You are trying to cast an IQueryHandler<GetTree, Tree> to an IQueryHandler<IQuery<Tree>, Tree>, but that would only work when IQueryHandler<TQuery, TResult> was specified as:

interface IQueryHandler<out TQuery, TResult>

Notice the out argument here.

The out keyword marks TQuery as an output argument. In case a generic type argument is marked as output argument, it allows us to cast the interface to something more generic. An IEnumerable<string> for instance can be casted to IEnumerable<object> since IEnumerable<string> will return strings, and they can be represented as objects.

For in arguments however, the opposite holds. When looking at an Action<in T> for intance, we are allowed to cast an Action<BaseType> to Action<SubType>, since the action would always be able to process SubTypes as well. On the other hand, it would be impossible to cast Action<BaseType> to Action<object> since that would allow us to pass in a string to the action as well (since string is an object), but that would then obviously fail at runtime.

So casting an IQueryHandler<GetTree, Tree> to IQueryHandler<IQuery<Tree>, Tree> would only be possible if TQuery is marked with an out keyword, but clearly TQuery is an input argument. The CLR therefore forbids the conversion, since it could lead to errors at runtime.

In your case however, this problem would not exist, because you know that the passed in type always fits. But remember, the CLR is unable to check this, and is therefore blocking the conversion.

One solution is, as I described on my blog, to use the dynamic keyword as follows:

public async Task<TResult> ExecuteAsync<TResult>(IQuery<TResult> query)
{
    var handlerType = 
        typeof(IQueryHandler<,>).MakeGenericType(query.GetType(), typeof(TResult));
    dynamic handler = _context.Resolve(handlerType);
    return (TResult)await castedHandler.ExecuteAsync((dynamic)query);    
}

Another option is to specify and register a generic wrapper type that implements an interface that lacks the TQuery generic type. This way you can prevent using dynamic altogether:

public interface IWrapper<TResult>
{
    Task<TResult> ExecuteAsync(IQuery<TResult> query);
}

public async Task<TResult> ExecuteAsync<TResult>(IQuery<TResult> query)
{
    var wrapperType =
        typeof(Wrapper<,>).MakeGenericType(query.GetType(), typeof(TResult));
    var wrapper = (IWrapper<TResult>)_context.Resolve(wrapperType);
    return wrapper.ExecuteAsync(query);
}

// Don't forget to register this type by itself in Autofac.
public class Wrapper<TQuery, TResult> : IWrapper<TResult>
{
    private readonly IQueryHandler<TQuery, TResult> handler;
    public Wrapper(IQueryHandler<TQuery, TResult> handler) { this.handler = handler; }
    Task<TResult> IWrapper<TResult>.ExecuteAsync(IQuery<TResult> query) =>
        this.handler.ExecuteAsync((TQuery)query);
}
Related