Skip to content

Commit d043619

Browse files
author
Bart Koelman
authored
Optimized/fixed TypeLocator.GetGenericInterfaceImplementation (#857)
This method was identified as a hot path in profiling JsonApiDotNetCoreExample tests. Type.GetGenericTypeDefinition() on the incoming parameter was called inside the loop, which is unneeded. And it compared generic type parameters before looking at the type itself, potentially evaluating too much. This change makes it run 30% faster. This change also improves input validation and fixes the bug where only the first type parameter was matched against found types.
1 parent 5a5f080 commit d043619

File tree

3 files changed

+35
-23
lines changed

3 files changed

+35
-23
lines changed

src/JsonApiDotNetCore/Configuration/ServiceDiscoveryFacade.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,10 +185,10 @@ private void AddResourceDefinitions(Assembly assembly, ResourceDescriptor resour
185185
private void RegisterImplementations(Assembly assembly, Type interfaceType, ResourceDescriptor resourceDescriptor)
186186
{
187187
var genericArguments = interfaceType.GetTypeInfo().GenericTypeParameters.Length == 2 ? new[] { resourceDescriptor.ResourceType, resourceDescriptor.IdType } : new[] { resourceDescriptor.ResourceType };
188-
var (implementation, registrationInterface) = TypeLocator.GetGenericInterfaceImplementation(assembly, interfaceType, genericArguments);
189-
190-
if (implementation != null)
188+
var result = TypeLocator.GetGenericInterfaceImplementation(assembly, interfaceType, genericArguments);
189+
if (result != null)
191190
{
191+
var (implementation, registrationInterface) = result.Value;
192192
_services.AddScoped(registrationInterface, implementation);
193193
}
194194
}

src/JsonApiDotNetCore/Configuration/TypeLocator.cs

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -38,41 +38,52 @@ public static ResourceDescriptor TryGetResourceDescriptor(Type type)
3838
}
3939

4040
/// <summary>
41-
/// Gets all implementations of the generic interface.
41+
/// Gets all implementations of a generic interface.
4242
/// </summary>
43-
/// <param name="assembly">The assembly to search.</param>
44-
/// <param name="openGenericInterface">The open generic type, e.g. `typeof(IResourceService&lt;&gt;)`.</param>
45-
/// <param name="genericInterfaceArguments">Parameters to the generic type.</param>
43+
/// <param name="assembly">The assembly to search in.</param>
44+
/// <param name="openGenericInterface">The open generic interface.</param>
45+
/// <param name="interfaceGenericTypeArguments">Generic type parameters to construct the generic interface.</param>
4646
/// <example>
4747
/// <code><![CDATA[
48-
/// GetGenericInterfaceImplementation(assembly, typeof(IResourceService<>), typeof(Article), typeof(Guid));
48+
/// GetGenericInterfaceImplementation(assembly, typeof(IResourceService<,>), typeof(Article), typeof(Guid));
4949
/// ]]></code>
5050
/// </example>
51-
public static (Type implementation, Type registrationInterface) GetGenericInterfaceImplementation(Assembly assembly, Type openGenericInterface, params Type[] genericInterfaceArguments)
51+
public static (Type implementation, Type registrationInterface)? GetGenericInterfaceImplementation(Assembly assembly, Type openGenericInterface, params Type[] interfaceGenericTypeArguments)
5252
{
5353
if (assembly == null) throw new ArgumentNullException(nameof(assembly));
5454
if (openGenericInterface == null) throw new ArgumentNullException(nameof(openGenericInterface));
55-
if (genericInterfaceArguments == null) throw new ArgumentNullException(nameof(genericInterfaceArguments));
56-
if (genericInterfaceArguments.Length == 0) throw new ArgumentException("No arguments supplied for the generic interface.", nameof(genericInterfaceArguments));
57-
if (!openGenericInterface.IsGenericType) throw new ArgumentException("Requested type is not a generic type.", nameof(openGenericInterface));
55+
if (interfaceGenericTypeArguments == null) throw new ArgumentNullException(nameof(interfaceGenericTypeArguments));
5856

59-
foreach (var type in assembly.GetTypes())
57+
if (!openGenericInterface.IsInterface || !openGenericInterface.IsGenericType ||
58+
openGenericInterface != openGenericInterface.GetGenericTypeDefinition())
59+
{
60+
throw new ArgumentException($"Specified type '{openGenericInterface.FullName}' is not an open generic interface.", nameof(openGenericInterface));
61+
}
62+
63+
if (interfaceGenericTypeArguments.Length != openGenericInterface.GetGenericArguments().Length)
6064
{
61-
var interfaces = type.GetInterfaces();
62-
foreach (var @interface in interfaces)
65+
throw new ArgumentException(
66+
$"Interface '{openGenericInterface.FullName}' requires {openGenericInterface.GetGenericArguments().Length} type parameters instead of {interfaceGenericTypeArguments.Length}.",
67+
nameof(interfaceGenericTypeArguments));
68+
}
69+
70+
foreach (var nextType in assembly.GetTypes())
71+
{
72+
foreach (var nextGenericInterface in nextType.GetInterfaces().Where(x => x.IsGenericType))
6373
{
64-
if (@interface.IsGenericType)
74+
var nextOpenGenericInterface = nextGenericInterface.GetGenericTypeDefinition();
75+
if (nextOpenGenericInterface == openGenericInterface)
6576
{
66-
var genericTypeDefinition = @interface.GetGenericTypeDefinition();
67-
if (@interface.GetGenericArguments().First() == genericInterfaceArguments.First() &&genericTypeDefinition == openGenericInterface.GetGenericTypeDefinition())
77+
var nextGenericArguments = nextGenericInterface.GetGenericArguments();
78+
if (nextGenericArguments.Length == interfaceGenericTypeArguments.Length && nextGenericArguments.SequenceEqual(interfaceGenericTypeArguments))
6879
{
69-
return (type, genericTypeDefinition.MakeGenericType(genericInterfaceArguments));
80+
return (nextType, nextOpenGenericInterface.MakeGenericType(interfaceGenericTypeArguments));
7081
}
7182
}
7283
}
7384
}
7485

75-
return (null, null);
86+
return null;
7687
}
7788

7889
/// <summary>

test/UnitTests/Graph/TypeLocator_Tests.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,16 @@ public void GetGenericInterfaceImplementation_Gets_Implementation()
1919
var expectedInterface = typeof(IGenericInterface<int>);
2020

2121
// Act
22-
var (implementation, registrationInterface) = TypeLocator.GetGenericInterfaceImplementation(
22+
var result = TypeLocator.GetGenericInterfaceImplementation(
2323
assembly,
2424
openGeneric,
2525
genericArg
2626
);
2727

2828
// Assert
29-
Assert.Equal(expectedImplementation, implementation);
30-
Assert.Equal(expectedInterface, registrationInterface);
29+
Assert.NotNull(result);
30+
Assert.Equal(expectedImplementation, result.Value.implementation);
31+
Assert.Equal(expectedInterface, result.Value.registrationInterface);
3132
}
3233

3334
[Fact]

0 commit comments

Comments
 (0)