diff --git a/.vscode/tasks.json b/.vscode/tasks.json index d52dc9b5b..e2ca9ac64 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -3,6 +3,7 @@ { "args": [ "build", + "${workspaceFolder}/Autofac.sln", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary" ], @@ -17,6 +18,24 @@ }, "problemMatcher": "$msCompile", "type": "shell" + }, + { + "args": [ + "test", + "${workspaceFolder}/Autofac.sln", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary", + "--filter", + "FullyQualifiedName!~Benchmark" + ], + "command": "dotnet", + "group": { + "isDefault": true, + "kind": "test" + }, + "label": "test", + "problemMatcher": "$msCompile", + "type": "process" } ], "version": "2.0.0" diff --git a/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs b/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs index 951de798f..e7de4266f 100644 --- a/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs +++ b/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs @@ -173,7 +173,44 @@ public static bool TryBindOpenGenericDelegateService( if (!serviceType.IsInterface) { - return TryFindServiceArgumentsForImplementation(implementationType, serviceGenericArguments, serviceTypeDefinition.GetGenericArguments()); + /* Issue #1315: We walk backwards in the inheritance hierarchy to + * find the open generic because that's the only way to ensure the + * generic argument names match. + * + * If you have a class BaseClass and a derived class + * DerivedClass : BaseClass then then arguments need + * to line up name-wise like... + * A2 -> T1 + * A1 -> T2 + * + * Having those symbols aligned means that, later, we can do a + * name-based match to populate the generic arguments. + * + * If you do a typeof(BaseClass<,>) then the generic arguments are + * named T1 and T2, which doesn't help us line up the arguments in + * the derived class because the names and order have changed. + * However, if you start at the derived class and walk backwards up + * the inheritance hierarchy then instead of getting the original + * T1/T2 naming, we get the names A1/A2 - the symbols as lined up by + * the compiler. + * + * This is the same reason why Type.GetInterfaces() "just works" - + * it's the generic in relation to the derived/implementing type + * rather than just typeof(MyInterface<,>), so the names all line + * up. + */ + var baseType = GetGenericBaseType(implementationType, serviceTypeDefinition); + if (baseType == null) + { + // If it's not an interface, the implementation type MUST have derived from + // the generic service type at some point or there's no way to cast. + return Array.Empty(); + } + + return TryFindServiceArgumentsForImplementation( + implementationType, + serviceGenericArguments, + baseType.GenericTypeArguments); } var availableArguments = GetInterfaces(implementationType, serviceType) @@ -199,6 +236,22 @@ public static bool TryBindOpenGenericDelegateService( .ToArray(); } + private static Type? GetGenericBaseType(Type implementationType, Type serviceTypeDefinition) + { + var baseType = implementationType.BaseType; + while (baseType != null) + { + if (baseType.IsGenericType && baseType.GetGenericTypeDefinition() == serviceTypeDefinition) + { + break; + } + + baseType = baseType.BaseType; + } + + return baseType; + } + private static Type[] GetInterfaces(Type implementationType, Type serviceType) => implementationType.GetInterfaces() .Where(i => i.Name == serviceType.Name && i.Namespace == serviceType.Namespace) diff --git a/test/Autofac.Specification.Test/Registration/OpenGenericTests.cs b/test/Autofac.Specification.Test/Registration/OpenGenericTests.cs index 6717c6c97..a35541c78 100644 --- a/test/Autofac.Specification.Test/Registration/OpenGenericTests.cs +++ b/test/Autofac.Specification.Test/Registration/OpenGenericTests.cs @@ -1,7 +1,7 @@ // Copyright (c) Autofac Project. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. -using Autofac.Test.Scenarios.Graph1.GenericContraints; +using Autofac.Test.Scenarios.Graph1.GenericConstraints; namespace Autofac.Specification.Test.Registration; @@ -42,7 +42,47 @@ public void ResolveWithMultipleCandidatesLimitedByGenericConstraintsShouldSuccee Assert.NotNull(resolved); } + // Issue #1315: Class services fail to resolve if the names on the type + // arguments are changed. + [Fact] + public void ResolveClassWithRenamedTypeArguments() + { + var containerBuilder = new ContainerBuilder(); + containerBuilder.RegisterGeneric(typeof(DerivedRepository<,>)).As(typeof(BaseRepository<,>)); + + var container = containerBuilder.Build(); + var resolved = container.Resolve>(); + Assert.IsType>(resolved); + } + + // Issue #1315: Class services fail to resolve if the names on the type + // arguments are changed. We should be able to handle a deep inheritance chain. + [Fact] + public void ResolveClassTwoLevelsDownWithRenamedTypeArguments() + { + var containerBuilder = new ContainerBuilder(); + containerBuilder.RegisterGeneric(typeof(TwoLevelsDerivedRepository<,>)).As(typeof(BaseRepository<,>)); + + var container = containerBuilder.Build(); + var resolved = container.Resolve>(); + Assert.IsType>(resolved); + } + private class SelfComponent : IImplementedInterface { } + + private class BaseRepository + { + } + + // Issue #1315: Class services fail to resolve if the names on the type + // arguments are changed. + private class DerivedRepository : BaseRepository + { + } + + private class TwoLevelsDerivedRepository : DerivedRepository + { + } } diff --git a/test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/A.cs b/test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/A.cs similarity index 80% rename from test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/A.cs rename to test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/A.cs index abd1111d1..6d88e8590 100644 --- a/test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/A.cs +++ b/test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/A.cs @@ -1,7 +1,7 @@ // Copyright (c) Autofac Project. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. -namespace Autofac.Test.Scenarios.Graph1.GenericContraints; +namespace Autofac.Test.Scenarios.Graph1.GenericConstraints; public class A : IA { diff --git a/test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/ClassWithParameterlessButNotPublicConstructor.cs b/test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/ClassWithParameterlessButNotPublicConstructor.cs similarity index 82% rename from test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/ClassWithParameterlessButNotPublicConstructor.cs rename to test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/ClassWithParameterlessButNotPublicConstructor.cs index 1e031cfba..8195b6672 100644 --- a/test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/ClassWithParameterlessButNotPublicConstructor.cs +++ b/test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/ClassWithParameterlessButNotPublicConstructor.cs @@ -1,7 +1,7 @@ // Copyright (c) Autofac Project. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. -namespace Autofac.Test.Scenarios.Graph1.GenericContraints; +namespace Autofac.Test.Scenarios.Graph1.GenericConstraints; public class ClassWithParameterlessButNotPublicConstructor { diff --git a/test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/IA.cs b/test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/IA.cs similarity index 74% rename from test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/IA.cs rename to test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/IA.cs index 9077aba56..71189c6ae 100644 --- a/test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/IA.cs +++ b/test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/IA.cs @@ -1,7 +1,7 @@ // Copyright (c) Autofac Project. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. -namespace Autofac.Test.Scenarios.Graph1.GenericContraints; +namespace Autofac.Test.Scenarios.Graph1.GenericConstraints; public interface IA { diff --git a/test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/IB.cs b/test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/IB.cs similarity index 75% rename from test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/IB.cs rename to test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/IB.cs index ad7fd282b..cdaec1d56 100644 --- a/test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/IB.cs +++ b/test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/IB.cs @@ -1,7 +1,7 @@ // Copyright (c) Autofac Project. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. -namespace Autofac.Test.Scenarios.Graph1.GenericContraints; +namespace Autofac.Test.Scenarios.Graph1.GenericConstraints; public interface IB { diff --git a/test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/Required.cs b/test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/Required.cs similarity index 79% rename from test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/Required.cs rename to test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/Required.cs index d083a5fe3..cd426f378 100644 --- a/test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/Required.cs +++ b/test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/Required.cs @@ -1,7 +1,7 @@ // Copyright (c) Autofac Project. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. -namespace Autofac.Test.Scenarios.Graph1.GenericContraints; +namespace Autofac.Test.Scenarios.Graph1.GenericConstraints; public class Required : IB { diff --git a/test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/Unrelated.cs b/test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/Unrelated.cs similarity index 78% rename from test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/Unrelated.cs rename to test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/Unrelated.cs index ca934c3a1..3d6d0907a 100644 --- a/test/Autofac.Specification.Test/Resolution/Graph1/GenericContraints/Unrelated.cs +++ b/test/Autofac.Specification.Test/Resolution/Graph1/GenericConstraints/Unrelated.cs @@ -1,7 +1,7 @@ // Copyright (c) Autofac Project. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. -namespace Autofac.Test.Scenarios.Graph1.GenericContraints; +namespace Autofac.Test.Scenarios.Graph1.GenericConstraints; public class Unrelated : IB where T : class, new()