Skip to content

Commit c1953f3

Browse files
committed
Optimization for service key parameter in keyed service resolution.
1 parent 376fde1 commit c1953f3

8 files changed

Lines changed: 205 additions & 23 deletions

src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public class ReflectionActivator : InstanceActivator, IInstanceActivator
1717
private readonly Type _implementationType;
1818
private readonly Parameter[] _configuredProperties;
1919
private readonly Parameter[] _defaultParameters;
20+
private readonly bool _requiresServiceKeyParameter;
2021

2122
private ConstructorBinder[]? _constructorBinders;
2223
private bool _anyRequiredMembers;
@@ -49,6 +50,9 @@ public ReflectionActivator(
4950
}
5051

5152
_implementationType = implementationType;
53+
_requiresServiceKeyParameter = ReflectionCacheSet.Shared.Internal.ServiceKeyUsageByType.GetOrAdd(
54+
_implementationType,
55+
static t => UsesServiceKeyAttribute(t));
5256
ConstructorFinder = constructorFinder ?? throw new ArgumentNullException(nameof(constructorFinder));
5357
ConstructorSelector = constructorSelector ?? throw new ArgumentNullException(nameof(constructorSelector));
5458
_configuredProperties = configuredProperties.ToArray();
@@ -65,6 +69,11 @@ public ReflectionActivator(
6569
/// </summary>
6670
public IConstructorSelector ConstructorSelector { get; }
6771

72+
/// <summary>
73+
/// Gets a value indicating whether the activation pipeline needs a keyed service parameter for this type.
74+
/// </summary>
75+
internal bool RequiresServiceKeyParameter => _requiresServiceKeyParameter;
76+
6877
/// <inheritdoc/>
6978
public void ConfigurePipeline(IComponentRegistryServices componentRegistryServices, IResolvePipelineBuilder pipelineBuilder)
7079
{
@@ -155,6 +164,30 @@ public void ConfigurePipeline(IComponentRegistryServices componentRegistryServic
155164
});
156165
}
157166

167+
private static bool UsesServiceKeyAttribute(Type implementationType)
168+
{
169+
foreach (var constructor in implementationType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
170+
{
171+
foreach (var parameter in constructor.GetParameters())
172+
{
173+
if (ServiceKeyAttributeCache.ParameterHasServiceKey(parameter))
174+
{
175+
return true;
176+
}
177+
}
178+
}
179+
180+
foreach (var property in implementationType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
181+
{
182+
if (property.CanWrite && ServiceKeyAttributeCache.PropertyHasServiceKey(property))
183+
{
184+
return true;
185+
}
186+
}
187+
188+
return false;
189+
}
190+
158191
private void UseSingleConstructorActivation(IResolvePipelineBuilder pipelineBuilder, ConstructorBinder singleConstructor)
159192
{
160193
if (singleConstructor.ParameterCount == 0)

src/Autofac/Core/InternalReflectionCaches.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ public InternalReflectionCaches(ReflectionCacheSet set)
3535
DefaultPublicConstructors = set.GetOrCreateCache<ReflectionCacheDictionary<Type, ConstructorInfo[]>>(nameof(DefaultPublicConstructors));
3636
GenericTypeDefinitionByType = set.GetOrCreateCache<ReflectionCacheDictionary<Type, Type>>(nameof(GenericTypeDefinitionByType));
3737
HasRequiredMemberAttribute = set.GetOrCreateCache<ReflectionCacheDictionary<Type, bool>>(nameof(HasRequiredMemberAttribute));
38+
ServiceKeyParameterAttributes = set.GetOrCreateCache<ReflectionCacheParameterDictionary<bool>>(nameof(ServiceKeyParameterAttributes));
39+
ServiceKeyPropertyAttributes = set.GetOrCreateCache<ReflectionCacheDictionary<PropertyInfo, bool>>(nameof(ServiceKeyPropertyAttributes));
40+
ServiceKeyUsageByType = set.GetOrCreateCache<ReflectionCacheDictionary<Type, bool>>(nameof(ServiceKeyUsageByType));
3841
}
3942

4043
/// <summary>
@@ -91,4 +94,19 @@ public InternalReflectionCaches(ReflectionCacheSet set)
9194
/// Gets a cache used by <see cref="ReflectionActivator"/>.
9295
/// </summary>
9396
public ReflectionCacheDictionary<Type, bool> HasRequiredMemberAttribute { get; }
97+
98+
/// <summary>
99+
/// Gets a cache used to track <see cref="ServiceKeyAttribute"/> usage on parameters.
100+
/// </summary>
101+
public ReflectionCacheParameterDictionary<bool> ServiceKeyParameterAttributes { get; }
102+
103+
/// <summary>
104+
/// Gets a cache used to track <see cref="ServiceKeyAttribute"/> usage on properties.
105+
/// </summary>
106+
public ReflectionCacheDictionary<PropertyInfo, bool> ServiceKeyPropertyAttributes { get; }
107+
108+
/// <summary>
109+
/// Gets a cache used to determine if a type uses <see cref="ServiceKeyAttribute"/>.
110+
/// </summary>
111+
public ReflectionCacheDictionary<Type, bool> ServiceKeyUsageByType { get; }
94112
}

src/Autofac/Core/KeyedServiceParameterInjector.cs

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,40 @@ public static IEnumerable<Parameter> AddKeyedServiceParameter(Service service, I
2929
return AddKeyedServiceParameter(keyedService.ServiceKey, parameters);
3030
}
3131

32+
/// <summary>
33+
/// Ensures keyed service requests carry their associated key with the parameter sequence.
34+
/// </summary>
35+
/// <param name="service">The service being resolved.</param>
36+
/// <param name="parameters">The parameters supplied by the caller.</param>
37+
/// <param name="registration">The component registration (if known).</param>
38+
/// <returns>An enumerable that exposes the keyed service key when appropriate.</returns>
39+
public static IEnumerable<Parameter> AddKeyedServiceParameter(Service service, IEnumerable<Parameter> parameters, IComponentRegistration? registration)
40+
{
41+
if (service == null)
42+
{
43+
throw new ArgumentNullException(nameof(service));
44+
}
45+
46+
if (parameters == null)
47+
{
48+
throw new ArgumentNullException(nameof(parameters));
49+
}
50+
51+
if (service is not KeyedService keyedService || KeyedService.IsAnyKey(keyedService.ServiceKey))
52+
{
53+
// It's not a keyed service OR it's registered with AnyKey.
54+
return parameters;
55+
}
56+
57+
if (registration?.Activator is Autofac.Core.Activators.Reflection.ReflectionActivator reflectionActivator &&
58+
!reflectionActivator.RequiresServiceKeyParameter)
59+
{
60+
return parameters;
61+
}
62+
63+
return AddKeyedServiceParameter(keyedService.ServiceKey, parameters);
64+
}
65+
3266
/// <summary>
3367
/// Ensures keyed service requests carry their associated key with the parameter sequence.
3468
/// </summary>
@@ -47,7 +81,12 @@ public static IEnumerable<Parameter> AddKeyedServiceParameter(object serviceKey,
4781
throw new ArgumentNullException(nameof(parameters));
4882
}
4983

50-
if (KeyedService.IsAnyKey(serviceKey) || HasKeyParameter(parameters, serviceKey))
84+
if (KeyedService.IsAnyKey(serviceKey))
85+
{
86+
return parameters;
87+
}
88+
89+
if (HasKeyParameter(parameters, serviceKey))
5190
{
5291
return parameters;
5392
}
@@ -56,7 +95,17 @@ public static IEnumerable<Parameter> AddKeyedServiceParameter(object serviceKey,
5695
}
5796

5897
private static bool HasKeyParameter(IEnumerable<Parameter> parameters, object serviceKey)
59-
=> parameters.OfType<KeyedServiceKeyParameter>().Any(p => Equals(p.ServiceKey, serviceKey));
98+
{
99+
foreach (var parameter in parameters)
100+
{
101+
if (parameter is KeyedServiceKeyParameter keyParameter && Equals(keyParameter.ServiceKey, serviceKey))
102+
{
103+
return true;
104+
}
105+
}
106+
107+
return false;
108+
}
60109

61110
private static IEnumerable<Parameter> AppendKeyParameter(IEnumerable<Parameter> parameters, object serviceKey)
62111
{

src/Autofac/Core/Resolving/Pipeline/DefaultResolveRequestContext.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ internal DefaultResolveRequestContext(
3131
{
3232
Operation = owningOperation;
3333
ActivationScope = scope;
34-
Parameters = KeyedServiceParameterInjector.AddKeyedServiceParameter(request.Service, request.Parameters);
34+
Parameters = KeyedServiceParameterInjector.AddKeyedServiceParameter(request.Service, request.Parameters, request.Registration);
3535
_resolveRequest = request;
3636
PhaseReached = PipelinePhase.ResolveRequestStart;
3737
DiagnosticSource = diagnosticSource;
@@ -87,7 +87,7 @@ public override void ChangeScope(ISharingLifetimeScope newScope) =>
8787

8888
/// <inheritdoc />
8989
public override void ChangeParameters(IEnumerable<Parameter> newParameters) =>
90-
Parameters = newParameters ?? throw new ArgumentNullException(nameof(newParameters));
90+
Parameters = KeyedServiceParameterInjector.AddKeyedServiceParameter(Service, newParameters ?? throw new ArgumentNullException(nameof(newParameters)), Registration);
9191

9292
/// <inheritdoc />
9393
public override object ResolveComponent(in ResolveRequest request) =>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright (c) Autofac Project. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
using System.Reflection;
5+
using Autofac.Util;
6+
7+
namespace Autofac.Core;
8+
9+
/// <summary>
10+
/// Caches lookups for <see cref="ServiceKeyAttribute"/> to avoid repeated reflection scans.
11+
/// </summary>
12+
internal static class ServiceKeyAttributeCache
13+
{
14+
/// <summary>
15+
/// Determines whether a parameter is decorated with <see cref="ServiceKeyAttribute"/>.
16+
/// </summary>
17+
/// <param name="parameter">The parameter to inspect.</param>
18+
/// <returns><see langword="true"/> when the attribute is present; otherwise <see langword="false"/>.</returns>
19+
public static bool ParameterHasServiceKey(ParameterInfo parameter)
20+
{
21+
if (parameter == null)
22+
{
23+
throw new ArgumentNullException(nameof(parameter));
24+
}
25+
26+
return ReflectionCacheSet.Shared.Internal.ServiceKeyParameterAttributes.GetOrAdd(
27+
parameter,
28+
static p =>
29+
{
30+
if (p.IsDefined(typeof(ServiceKeyAttribute), inherit: true))
31+
{
32+
return true;
33+
}
34+
35+
return p.TryGetDeclaringProperty(out PropertyInfo? property) && PropertyHasServiceKey(property);
36+
});
37+
}
38+
39+
/// <summary>
40+
/// Determines whether a property is decorated with <see cref="ServiceKeyAttribute"/>.
41+
/// </summary>
42+
/// <param name="property">The property to inspect.</param>
43+
/// <returns><see langword="true"/> when the attribute is present; otherwise <see langword="false"/>.</returns>
44+
public static bool PropertyHasServiceKey(PropertyInfo property)
45+
{
46+
if (property == null)
47+
{
48+
throw new ArgumentNullException(nameof(property));
49+
}
50+
51+
return ReflectionCacheSet.Shared.Internal.ServiceKeyPropertyAttributes.GetOrAdd(
52+
property,
53+
static p => p.IsDefined(typeof(ServiceKeyAttribute), inherit: true));
54+
}
55+
}

src/Autofac/KeyedServiceKeyParameter.cs

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using System.Reflection;
55
using Autofac.Core;
6-
using Autofac.Util;
76

87
namespace Autofac;
98

@@ -51,19 +50,6 @@ public override bool CanSupplyValue(ParameterInfo pi, IComponentContext context,
5150

5251
private static bool ShouldInject(ParameterInfo parameter)
5352
{
54-
if (parameter.IsDefined(typeof(ServiceKeyAttribute), inherit: true))
55-
{
56-
// It's a constructor parameter with the attribute.
57-
return true;
58-
}
59-
60-
if (parameter.TryGetDeclaringProperty(out PropertyInfo? property) &&
61-
property.IsDefined(typeof(ServiceKeyAttribute), inherit: true))
62-
{
63-
// It's a property setter parameter with the attribute on the property.
64-
return true;
65-
}
66-
67-
return false;
53+
return ServiceKeyAttributeCache.ParameterHasServiceKey(parameter);
6854
}
6955
}

src/Autofac/ResolutionExtensions.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,6 @@ public static TService ResolveKeyed<TService>(this IComponentContext context, ob
418418
where TService : notnull
419419
{
420420
EnsureAnyKeyUsageIsValid(serviceKey, typeof(TService));
421-
parameters = KeyedServiceParameterInjector.AddKeyedServiceParameter(serviceKey, parameters);
422421
return CastInstance<TService>(ResolveService(context, new KeyedService(serviceKey, typeof(TService)), parameters));
423422
}
424423

@@ -483,7 +482,6 @@ public static object ResolveKeyed(this IComponentContext context, object service
483482
public static object ResolveKeyed(this IComponentContext context, object serviceKey, Type serviceType, IEnumerable<Parameter> parameters)
484483
{
485484
EnsureAnyKeyUsageIsValid(serviceKey, serviceType);
486-
parameters = KeyedServiceParameterInjector.AddKeyedServiceParameter(serviceKey, parameters);
487485
return ResolveService(context, new KeyedService(serviceKey, serviceType), parameters);
488486
}
489487

@@ -1098,8 +1096,7 @@ public static bool TryResolveKeyed<T>(this IComponentContext context, object ser
10981096
public static bool TryResolveKeyed(this IComponentContext context, object serviceKey, Type serviceType, [NotNullWhen(returnValue: true)] out object? instance)
10991097
{
11001098
EnsureAnyKeyUsageIsValid(serviceKey, serviceType);
1101-
var parameters = KeyedServiceParameterInjector.AddKeyedServiceParameter(serviceKey, ResolveRequest.NoParameters);
1102-
return context.TryResolveService(new KeyedService(serviceKey, serviceType), parameters, out instance);
1099+
return context.TryResolveService(new KeyedService(serviceKey, serviceType), ResolveRequest.NoParameters, out instance);
11031100
}
11041101

11051102
/// <summary>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright (c) Autofac Project. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
using System.Collections.Concurrent;
5+
using System.Reflection;
6+
using Autofac.Core;
7+
8+
namespace Autofac.Util.Cache;
9+
10+
/// <summary>
11+
/// A reflection cache dictionary, keyed on a <see cref="ParameterInfo"/>.
12+
/// </summary>
13+
/// <typeparam name="TValue">The value type.</typeparam>
14+
public sealed class ReflectionCacheParameterDictionary<TValue>
15+
: ConcurrentDictionary<ParameterInfo, TValue>, IReflectionCache
16+
{
17+
/// <inheritdoc />
18+
public ReflectionCacheUsage Usage { get; set; } = ReflectionCacheUsage.All;
19+
20+
/// <inheritdoc />
21+
public void Clear(ReflectionCacheClearPredicate predicate)
22+
{
23+
if (predicate is null)
24+
{
25+
throw new ArgumentNullException(nameof(predicate));
26+
}
27+
28+
if (Count == 0)
29+
{
30+
return;
31+
}
32+
33+
var reusableAssemblySet = new HashSet<Assembly>();
34+
35+
foreach (var kvp in this)
36+
{
37+
var member = kvp.Key.Member;
38+
if (predicate(member, TypeAssemblyReferenceProvider.GetAllReferencedAssemblies(member, reusableAssemblySet)))
39+
{
40+
TryRemove(kvp.Key, out _);
41+
}
42+
}
43+
}
44+
}

0 commit comments

Comments
 (0)