Skip to content
This repository has been archived by the owner on Nov 2, 2018. It is now read-only.

"Cannot instantiate implementation type" even when specifying generic type #531

Closed
thiagomajesk opened this issue Jun 1, 2017 · 10 comments

Comments

@thiagomajesk
Copy link

thiagomajesk commented Jun 1, 2017

Hello, I'm having trouble using the default container with @jbogard's MediatR.
I know that currently there are limitations, but I would appreciated if someone could bring any help.

I have the standard generic command:

public class Command<T> : IRequest<Command<T>> where T : class 
{ 
    public List<T> List { get; set; }
}

and its handler:

public class CommandHandler<T> : IRequestHandler<Command<T>, Command<T>> where T : class
{
    [...]
}

I was searching about it and saw that the user @dannyc02 managed to get this working with Structure Map by specifying the concrete types during registration (jbogard/MediatR#69). I've tried the same thing without success:

services.AddTransient(typeof(IRequestHandler<Command<string>, Command<string>>), typeof(CommandHandler<string>));

If its possible to register the concrete type like the example I know I could write a custom type scan to register everything to me, like this: https://gist.github.com/thiagomajesk/204ca7e602c3a1f1d5cc6f7b4ab42aa9. But right now I only get
Cannot instantiate implementation type.

@thiagomajesk thiagomajesk changed the title Cannot instantiate implementation type even when specifying generic type "Cannot instantiate implementation type" even when specifying generic type Jun 1, 2017
@pakrym
Copy link
Contributor

pakrym commented Jun 1, 2017

Can you provide a minimal repro please?

@thiagomajesk
Copy link
Author

@pakrym Of course, here it is: container_sample.zip

@pakrym
Copy link
Contributor

pakrym commented Jun 5, 2017

Exception is by design, services.AddMediatR(); registers open generic service of second order (where open generic part is type argument itself)

{MediatR.IRequestHandler`2[ContainerSample.Handlers.Command`1[T],ContainerSample.Handlers.Command`1[T]]}

and this is not supported by DI container. Only first order open generic registrations are supported.

@thiagomajesk
Copy link
Author

@pakrym I have to admit I don't quite understand why this scenario isn't supported though. It seems to me a common use case. Is there any recommendations on how to achieve similar behaviour (extending the default container or not) besides replacing it?

@pakrym
Copy link
Contributor

pakrym commented Jun 5, 2017

@thiagomajesk it creates a lot of complexities in resolution rules, imagine following registrations:

IRequestHandler<ICommand<Action<>>, ICommand<>>
IRequestHandler<ICommand<>, ICommand<string>>

Which one would you use to resolve IRequestHandler<ICommand<Action<string>, ICommand<string>> and why?

The only way to make it work with AspNetCore.DependencyInjection is too define interface ICommandHandler<T>: IRequestHandler<Command<T>, Command<T>> but then you need to inject ICommandHandler<T> that might not be applicable to your case.

@thiagomajesk
Copy link
Author

thiagomajesk commented Jun 6, 2017

@pakrym I see your point now, in that example the container doesn't know how to act because it can't tell the difference between ICommand<T> and ICommand<T<U>> because the whole thing inside the first <> IS a generic type. So I'll assume that the default behaviour of the DI is not to cascade scan the generic types (tree?), hence the error. Is that right?

So if I got it right and I still want this behaviour, I'd have to simplify the generic registration through the interface that has just one level of generics, like you've just demonstrated. So, for sake of visualization, something like this:

public class IGenericRequestHandler<T> : IRequestHandler<Command<T>, Command<T>> {}

public class CommandHandler<T> : IGenericRequestHandler<T> {}

public class Command<T> : IRequest<Command<T>> { }

services.AddTransient(typeof(IGenericRequestHandler<string>), typeof(CommandHandler<string>));

@pakrym
Copy link
Contributor

pakrym commented Jun 6, 2017

@thiagomajesk yes, we don't try to deconstruct generic type 'trees'.

In your example you can have services.AddTransient(typeof(IGenericRequestHandler<>), typeof(CommandHandler<>));

@thiagomajesk
Copy link
Author

thiagomajesk commented Jun 7, 2017

@pakrym Have you tested this workaround? It seems that even when using the interface approach it doesn't work. It throws the same error on Startup. Even if I try to 'trick' the container making simpler 'sub interfaces' like this:

public interface ICommandHandler<T> : IRequestHandler<Command<T>, Command<T>>
public interface ICommand<T> : IRequest<Command<T>>

public class Command<T> : ICommand<T>
{
    public List<T> List { get; set; }
}
public class CommandHandler<T> : ICommandHandler<T>
{
    public Command<T> Handle(Command<T> message)  { [...]  }
}

Any thoughts?

@tomasaschan
Copy link

it creates a lot of complexities in resolution rules, imagine following registrations:

IRequestHandler<ICommand<Action<>>, ICommand<>>
IRequestHandler<ICommand<>, ICommand<string>>

Which one would you use to resolve IRequestHandler<ICommand<Action<string>, ICommand<string>> and why?

This ambiguity situation could actually still be unsupported (and e.g. throw an error on startup, as today), while still supporting having just one of them registered. I stumbled here also from MediatR, and the case I want to get working is having the following interfaces and concrete implementations:

interface IRequest<T> : { }
interface IRequestHandler<TRequest, T> where TRequest : IRequest<T> { }

class Request<T> : IRequest<T> { }
class RequestHandler<T> : IRequestHandler<Request<T>, T> { }

Given an instance of Request<T>, I also know what T is - let's say it's a Request<string> instance I have. Then it's trivial to figure out that I'm looking for an IRequestHandler<Request<string>, string>, and that the corresponding implementation is a RequestHandler<string>. This solves my immediate use case.

Of course, it's possible to come up with situations that are close to this but ambiguous - but even with all its flexibility, the example above is not. (And I'm thinking that the generic type constraint on IRequestHandler<TRequest, T> makes it more difficult...)

@aspnet-hello
Copy link

This issue was moved to dotnet/aspnetcore#2341

@aspnet aspnet locked and limited conversation to collaborators Jan 1, 2018
@aspnet-hello aspnet-hello removed this from the Discussions milestone Jan 1, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants