Skip to content

Commit 9071c4d

Browse files
committed
Improve property injection performance.
1 parent 10d0e02 commit 9071c4d

File tree

3 files changed

+131
-20
lines changed

3 files changed

+131
-20
lines changed

bench/Autofac.Benchmarks/Harness.cs

+3
Original file line numberDiff line numberDiff line change
@@ -101,5 +101,8 @@ public void EnumerableResolve()
101101
{
102102
BenchmarkRunner.Run<EnumerableResolveBenchmark>();
103103
}
104+
105+
[Fact]
106+
public void PropertyInjection() => BenchmarkRunner.Run<PropertyInjectionBenchmark>();
104107
}
105108
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using System;
2+
using BenchmarkDotNet.Attributes;
3+
4+
namespace Autofac.Benchmarks
5+
{
6+
public class PropertyInjectionBenchmark
7+
{
8+
private IContainer _container;
9+
10+
[GlobalSetup]
11+
public void Setup()
12+
{
13+
var builder = new ContainerBuilder();
14+
builder.RegisterType<A>().PropertiesAutowired();
15+
builder.RegisterType<B1>().PropertiesAutowired();
16+
builder.RegisterType<B2>().PropertiesAutowired();
17+
builder.RegisterType<C1>().PropertiesAutowired();
18+
builder.RegisterType<C2>().PropertiesAutowired();
19+
builder.RegisterType<D1>();
20+
builder.RegisterType<D2>();
21+
_container = builder.Build();
22+
}
23+
24+
[Benchmark]
25+
public void Resolve()
26+
{
27+
var instance = _container.Resolve<A>();
28+
GC.KeepAlive(instance);
29+
}
30+
31+
internal class A
32+
{
33+
public B1 B1 { get; set; }
34+
35+
public B2 B2 { get; set; }
36+
}
37+
38+
internal class B1
39+
{
40+
public C1 C1 { get; set; }
41+
}
42+
43+
internal class B2
44+
{
45+
public C2 C2 { get; set; }
46+
}
47+
48+
internal class C1
49+
{
50+
public D1 D1 { get; set; }
51+
}
52+
53+
internal class C2
54+
{
55+
public D2 D2 { get; set; }
56+
}
57+
58+
internal class D1 { }
59+
60+
internal class D2 { }
61+
}
62+
}

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

+66-20
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,27 @@
2424
// OTHER DEALINGS IN THE SOFTWARE.
2525

2626
using System;
27+
using System.Collections.Concurrent;
2728
using System.Collections.Generic;
2829
using System.Linq;
2930
using System.Reflection;
3031
using Autofac.Util;
3132

3233
namespace Autofac.Core.Activators.Reflection
3334
{
34-
internal class AutowiringPropertyInjector
35+
internal static class AutowiringPropertyInjector
3536
{
3637
public const string InstanceTypeNamedParameter = "Autofac.AutowiringPropertyInjector.InstanceType";
3738

39+
private static readonly ConcurrentDictionary<PropertyInfo, Action<object, object>> PropertySetters =
40+
new ConcurrentDictionary<PropertyInfo, Action<object, object>>();
41+
42+
private static readonly ConcurrentDictionary<Type, PropertyInfo[]> InjectableProperties =
43+
new ConcurrentDictionary<Type, PropertyInfo[]>();
44+
45+
private static readonly MethodInfo CallPropertySetterOpenGenericMethod =
46+
typeof(AutowiringPropertyInjector).GetTypeInfo().GetDeclaredMethod(nameof(CallPropertySetter));
47+
3848
public static void InjectProperties(IComponentContext context, object instance, IPropertySelector propertySelector, IEnumerable<Parameter> parameters)
3949
{
4050
if (context == null)
@@ -57,56 +67,92 @@ public static void InjectProperties(IComponentContext context, object instance,
5767
throw new ArgumentNullException(nameof(parameters));
5868
}
5969

70+
var resolveParameters = parameters as Parameter[] ?? parameters.ToArray();
71+
6072
var instanceType = instance.GetType();
73+
var injectableProperties = InjectableProperties.GetOrAdd(instanceType, type => GetInjectableProperties(type).ToArray());
6174

62-
foreach (var property in instanceType
63-
.GetRuntimeProperties()
64-
.Where(pi => pi.CanWrite))
75+
for (var index = 0; index < injectableProperties.Length; index++)
6576
{
66-
var propertyType = property.PropertyType;
77+
var property = injectableProperties[index];
6778

68-
if (propertyType.GetTypeInfo().IsValueType && !propertyType.GetTypeInfo().IsEnum)
79+
if (!propertySelector.InjectProperty(property, instance))
6980
{
7081
continue;
7182
}
7283

73-
if (propertyType.IsArray && propertyType.GetElementType().GetTypeInfo().IsValueType)
84+
var setParameter = property.SetMethod.GetParameters()[0];
85+
var valueProvider = (Func<object>)null;
86+
var parameter = resolveParameters.FirstOrDefault(p => p.CanSupplyValue(setParameter, context, out valueProvider));
87+
if (parameter != null)
7488
{
89+
var setter = PropertySetters.GetOrAdd(property, MakeFastPropertySetter);
90+
setter(instance, valueProvider());
7591
continue;
7692
}
7793

78-
if (propertyType.IsGenericEnumerableInterfaceType() && propertyType.GetTypeInfo().GenericTypeArguments[0].GetTypeInfo().IsValueType)
94+
var propertyService = new TypedService(property.PropertyType);
95+
var instanceTypeParameter = new NamedParameter(InstanceTypeNamedParameter, instanceType);
96+
if (context.TryResolveService(propertyService, new Parameter[] { instanceTypeParameter }, out var propertyValue))
97+
{
98+
var setter = PropertySetters.GetOrAdd(property, MakeFastPropertySetter);
99+
setter(instance, propertyValue);
100+
}
101+
}
102+
}
103+
104+
private static IEnumerable<PropertyInfo> GetInjectableProperties(Type instanceType)
105+
{
106+
foreach (var property in instanceType.GetRuntimeProperties())
107+
{
108+
if (!property.CanWrite)
79109
{
80110
continue;
81111
}
82112

83-
if (property.GetIndexParameters().Length != 0)
113+
var propertyType = property.PropertyType;
114+
115+
if (propertyType.GetTypeInfo().IsValueType && !propertyType.GetTypeInfo().IsEnum)
84116
{
85117
continue;
86118
}
87119

88-
if (!propertySelector.InjectProperty(property, instance))
120+
if (propertyType.IsArray && propertyType.GetElementType().GetTypeInfo().IsValueType)
89121
{
90122
continue;
91123
}
92124

93-
var setParameter = property.SetMethod.GetParameters().First();
94-
var valueProvider = (Func<object>)null;
95-
var parameter = parameters.FirstOrDefault(p => p.CanSupplyValue(setParameter, context, out valueProvider));
96-
if (parameter != null)
125+
if (propertyType.IsGenericEnumerableInterfaceType() && propertyType.GetTypeInfo().GenericTypeArguments[0].GetTypeInfo().IsValueType)
97126
{
98-
property.SetValue(instance, valueProvider(), null);
99127
continue;
100128
}
101129

102-
object propertyValue;
103-
var propertyService = new TypedService(propertyType);
104-
var instanceTypeParameter = new NamedParameter(InstanceTypeNamedParameter, instanceType);
105-
if (context.TryResolveService(propertyService, new Parameter[] { instanceTypeParameter }, out propertyValue))
130+
if (property.GetIndexParameters().Length != 0)
106131
{
107-
property.SetValue(instance, propertyValue, null);
132+
continue;
108133
}
134+
135+
yield return property;
109136
}
110137
}
138+
139+
private static Action<object, object> MakeFastPropertySetter(PropertyInfo propertyInfo)
140+
{
141+
var setMethod = propertyInfo.SetMethod;
142+
var typeInput = setMethod.DeclaringType;
143+
var parameters = setMethod.GetParameters();
144+
var parameterType = parameters[0].ParameterType;
145+
146+
// Create a delegate TDeclaringType -> { TDeclaringType.Property = TValue; }
147+
var propertySetterAsAction = setMethod.CreateDelegate(typeof(Action<,>).MakeGenericType(typeInput, parameterType));
148+
var callPropertySetterClosedGenericMethod = CallPropertySetterOpenGenericMethod.MakeGenericMethod(typeInput, parameterType);
149+
var callPropertySetterDelegate = callPropertySetterClosedGenericMethod.CreateDelegate(typeof(Action<object, object>), propertySetterAsAction);
150+
151+
return (Action<object, object>)callPropertySetterDelegate;
152+
}
153+
154+
private static void CallPropertySetter<TDeclaringType, TValue>(
155+
Action<TDeclaringType, TValue> setter, object target, object value) =>
156+
setter((TDeclaringType)target, (TValue)value);
111157
}
112158
}

0 commit comments

Comments
 (0)