-
-
Notifications
You must be signed in to change notification settings - Fork 837
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ComponentNotRegisteredException when type parameter name doesn't match the base class #1315
Comments
This is really weird and pretty interesting. I haven't dug in yet, but I admit, I'm intrigued as to why the name of the type parameter would matter. |
In var matchingRegularType = serviceArgumentDefinitionToArgument
.Where(argdef => !argdef.Key.IsGenericType && implementationGenericArgumentDefinition.Name == argdef.Key.Name) which can be changed to: var matchingRegularType = serviceArgumentDefinitionToArgument
.Where(argdef => !argdef.Key.IsGenericType && implementationGenericArgumentDefinition.GenericParameterPosition == argdef.Key.GenericParameterPosition) will map the type parameters on position instead of name. The good news is that all tests will pass after this change. public class Base<T1, T2>
{
}
public class Derived<T2, T1> : Base<T1, T2>
{
} and (2), it doesn't explain why it works for interfaces.
if (!serviceType.IsInterface)
{
return TryFindServiceArgumentsForImplementation(implementationType, serviceGenericArguments, serviceTypeDefinition.GetGenericArguments());
}
var availableArguments = GetInterfaces(implementationType, serviceType)
.Select(t => TryFindServiceArgumentsForImplementation(
implementationType,
serviceGenericArguments,
t.GenericTypeArguments))
.ToArray(); There are two differences between classes and interfaces.
private static Type[] GetInterfaces(Type implementationType, Type serviceType) =>
implementationType.GetInterfaces()
.Where(i => i.Name == serviceType.Name && i.Namespace == serviceType.Namespace)
.ToArray(); (Not sure why the namespace should match). |
I think this is to avoid a situation where you have |
😱 yeah, that is not great. |
It apparently works for interfaces because the [Fact]
public void InterfaceTypeParametersRenamed()
{
var interfaces = typeof(DerivedRepository<,>).GetInterfaces();
Assert.Single(interfaces);
// The interface itself is IRepository<T1, T2> but
// GetInterfaces will return IRepository<TFirst, TSecond>
// so the names match.
var args = interfaces[0].GetGenericArguments();
Assert.Equal("TFirst", args[0].Name);
Assert.Equal("TSecond", args[1].Name);
}
public interface IRepository<T1, T2>
{
}
public class Repository<T1, T2>
{
}
public class DerivedRepository<TSecond, TFirst> : Repository<TFirst, TSecond>, IRepository<TFirst, TSecond>
{
} That explains why name matching "just works" in that case, which is news to me. What I just figured out is that if you use [Fact]
public void BaseTypeParametersRenamed()
{
var baseType = typeof(DerivedRepository<,>).BaseType;
var args = baseType.GetGenericArguments();
Assert.Equal("TFirst", args[0].Name);
Assert.Equal("TSecond", args[1].Name);
} So now I'm wondering if a better approach might be:
I think the mismatch here is that we're handed I have to go do some other stuff for a bit, but that's where I'll probably start looking next. |
I was thinking along the same lines. Or meybe fix this a bit earlier when the component is registered. |
Super quick and dirty hack works: private static Type[] TryMapImplementationGenericArguments(Type implementationType, Type serviceType, Type serviceTypeDefinition, Type[] serviceGenericArguments)
{
if (serviceTypeDefinition == implementationType)
{
return serviceGenericArguments;
}
if (!serviceType.IsInterface)
{
var baseType = implementationType.BaseType;
while (baseType != null)
{
if (baseType.IsGenericType && baseType.GetGenericTypeDefinition() == serviceTypeDefinition)
{
break;
}
baseType = baseType.BaseType;
}
if (baseType == null)
{
// The original match-by-name-only algorithm
return TryFindServiceArgumentsForImplementation(implementationType, serviceGenericArguments, serviceTypeDefinition.GetGenericArguments());
}
var matchedArguments = TryFindServiceArgumentsForImplementation(
implementationType,
serviceGenericArguments,
baseType.GenericTypeArguments);
return matchedArguments;
}
var availableArguments = GetInterfaces(implementationType, serviceType)
.Select(t => TryFindServiceArgumentsForImplementation(
implementationType,
serviceGenericArguments,
t.GenericTypeArguments))
.ToArray();
var exactMatch = availableArguments.FirstOrDefault(a => a.SequenceEqual(serviceGenericArguments));
return exactMatch ?? availableArguments[0];
} Obviously needs to be really cleaned up but doing the thing where you walk back the inheritance chain until you find the matching base type does work and all the tests still pass. I'll see if I can clean it up and get a PR in there for folks to look at. |
I'll see it when it is done, merged and released (no hurry obviously). For now I know how to make my code work with the latest binary. It was interesting to learn something new about generics. Thanks for responding so quickly! |
OK, got #1316 in for this. All the tests pass including the new ones to verify reordering the parameters works. Nice catch! |
Describe the Bug
Registering a component with
RegisterGeneric
when the service is a class where the type parameter name is different,Autofac throws a
ComponentNotRegisteredException
, unless the component has been resolved explicitly first.With_as_self_and_explicit_resolve
succeeds becausecontainer.Resolve<NHibernateRepository<Task>>();
is called beforecontainer.Resolve<Repository<Task>>();
.If
Repository<T>
is an interface, then it succeeds.If the type parameter in
NHibernateRepository<TDomain>
is renamed to matchRepository<T>
, it also succeeds.Steps to Reproduce
Expected Behavior
Resolving
Repository<T>
should returnNHibernateRepository<TDomain>
in all 3 cases.Exception with Stack Trace
Dependency Versions
Autofac: 6.3.0
Autofac: The develop branch as of March 18th 2022
Additional Info
The text was updated successfully, but these errors were encountered: