-
-
Notifications
You must be signed in to change notification settings - Fork 837
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
ParameterCompatibleWithTypeConstraint returns true instead of false when resolving open generic service #794
Comments
I got it working by replacing the private static bool ParameterEqualsConstraint(Type parameter, Type constraint)
{
var genericArguments = parameter.GetTypeInfo().GenericTypeArguments;
if (genericArguments.Length > 0 && constraint.GetTypeInfo().IsGenericType)
{
var typeDefinition = constraint.GetGenericTypeDefinition();
if (typeDefinition.GetTypeInfo().GenericTypeParameters.Length == genericArguments.Length)
{
try
{
var genericType = typeDefinition.MakeGenericType(genericArguments);
var constraintArguments = constraint.GetTypeInfo().GenericTypeArguments;
for (int i = 0; i < constraintArguments.Count(); i++)
{
var constraintArgument = constraintArguments[i].GetTypeInfo();
if (!constraintArgument.IsGenericParameter && !constraintArgument.IsAssignableFrom(genericArguments[i].GetTypeInfo())) return false;
}
return genericType == parameter;
}
catch (Exception)
{
return false;
}
}
}
return false;
} The public interface IMultitenant
{
}
public interface IMultitenantOptional
{
}
public interface IRequest<TResponse> { }
public interface IResponseHandler<in TRequest, TResponse>
{
}
public class Game : IMultitenant
{
}
public class Trophy : IMultitenantOptional
{
}
public class MultiTenantHandler<TRequest, TResponse> : IResponseHandler<TRequest, TResponse>
where TResponse : IQueryable<IMultitenant>
{
}
public class MultiTenantOptionalHandler<TRequest, TResponse> : IResponseHandler<TRequest, TResponse>
where TResponse : IQueryable<IMultitenantOptional>
{
}
[Fact]
public void CanResolveComponentUsingInterfaceWhenConstraintAndClassUseInterfaces()
{
var builder = new ContainerBuilder();
builder.RegisterGeneric(typeof(MultiTenantHandler<,>))
.As(typeof(IResponseHandler<,>));
builder.RegisterGeneric(typeof(MultiTenantOptionalHandler<,>))
.As(typeof(IResponseHandler<,>));
var container = builder.Build();
// These simple concrete implementations lookup fail as per issue #794
Assert.True(container.IsRegistered<IResponseHandler<int, IQueryable<Game>>>());
Assert.True(container.IsRegistered<IResponseHandler<int, IQueryable<Trophy>>>());
// These a little bit more complex concrete implementations lookup fail as well as per issue #794
Assert.True(container.IsRegistered<IResponseHandler<IRequest<IQueryable<Game>>, IQueryable<Game>>>());
Assert.True(container.IsRegistered<IResponseHandler<IRequest<IQueryable<Trophy>>, IQueryable<Trophy>>>());
// These should resolve, but per issue #794 they throw exceptions
container.Resolve<IResponseHandler<IRequest<IQueryable<Game>>, IQueryable<Game>>>();
container.Resolve<IResponseHandler<IRequest<IQueryable<Game>>, IQueryable<Trophy>>>();
} That's the "easy" part. Now, I've got a funny feeling that this is not going to be that easy and I want to discuss this before posting a PR to the repo. Especially this |
We really appreciate you taking the time to walk through this. I am excited to see a potential PR for it. Sorry to be slow in responding - you posted your SO question near the end of the day on a long holiday weekend for the US. 😉 I don't recall personally working on the generic type stuff there. Given some of it's been around for a while, it may be that some things have gotten easier. If you can make the existing tests continue to pass but also make your new tests pass, hey, that sounds great to me. I admit I'm personally a bit swamped with some other projects that fell by the wayside during the .NET Core conversion that ate our time for the past year, give or take. If a PR comes in, I'd love to check it out and see about pulling it in; otherwise it may be a bit of time before we can take a crack at this ourselves. |
Thanks @tillig for reaching out. I will definitely test this much more, but so far it looks like I should be able to submit a PR soon. I can confirm that both the existing tests as well as my new tests pass with flying colors. It's all green with the changes in and working as expected in the real project scenario when using modified Autofac package. So, long story short, please look out for a PR in a nearest future. Frankly, if not for the vague comment in the I will keep you posted. |
…hen resolving open generic service autofac#794 issue fixed
Pulled! Truly, this is awesome. Getting help with these sometimes really hard to lock down issues is amazing. Thanks so much for the PR! |
Fix included in v4.1.1, pushed to NuGet. |
I have a simple case of an open generic type with type constraint, like:
https://gist.github.com/sebekz/4c658a5c7551ba5a2b3fd81488ea3ee7 holds an console app sample for this particular case.
I have logged a StackOverflow question http://stackoverflow.com/questions/39291046/autofac-issues-when-resolving-generic-types-implementing-the-same-interface-nee which led to a hint of potential bug/issue in Autofac itself, therefore I am logging an issue here.
The problem is, that when two (or more) open generic types are registered in the container
an attempt to resolve it will fail n-1 times and succeed afterwards, where n equals number of implementations of
IResponseHandler<,>
. So if we had one moreclass MultiTenantAnotherHandler
with yet anotherwhere
type constraint, it would fail to.Resolve()
2 times and succeed each time after that. For a full issue description, please refer to StackOverflow question mentioned above.Further investigation has led to a call stack:
IsCompatibleWithGenericParameterConstraints
method call ensures that the type parameters are compatible with the given type constraints. It uses the methodParameterCompatibleWithTypeConstraint
to check for that and that metod usesIsAssignableFrom
in its implementation. So far so good, sinceIsAssignableFrom
works as expected, but in the case it returnsfalse
for all its checksParameterCompatibleWithTypeConstraint
checks if it can create a generic type with the base type of the constraint and the generic argument of the parameter.And Since this is always possible, the method returns
true
for all checks, which is incorrect, e.g.:true
is always returned because forIQueryable<Game>
the call toTraverse.Across(parameter, p => p.GetTypeInfo().BaseType)
returns one element of typeIQueryable<Game>
, and this resolves itself into an alwaystrue
ParameterEqualsConstraint(typeof(IQueryable<Game>), typeof(IQueryable<T>))
.It's always
true
, becauseParameterEqualsConstraint
is always able to create an instance ofIQueryable<Game>
fromIQueryable<T>
generic type definition, for example.It looks like a bug, if there may be a valid reason for this I would appreciate an explanation and perhaps we could work on a solution which would satisfy both my particular use case (which I think is perfectly valid) as well.
The text was updated successfully, but these errors were encountered: