Skip to content

Commit 02682ad

Browse files
JamesNKstephentoubjkotas
authored
Add reflection path for ActivatorUtilities.CreateFactory (#81262)
Co-authored-by: Stephen Toub <[email protected]> Co-authored-by: Jan Kotas <[email protected]> Fixes #81258
1 parent a342915 commit 02682ad

File tree

2 files changed

+253
-0
lines changed

2 files changed

+253
-0
lines changed

src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.cs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Diagnostics.CodeAnalysis;
77
using System.Linq.Expressions;
88
using System.Reflection;
9+
using System.Runtime.CompilerServices;
910
using System.Runtime.ExceptionServices;
1011
using Microsoft.Extensions.Internal;
1112

@@ -127,6 +128,15 @@ public static ObjectFactory CreateFactory(
127128
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type instanceType,
128129
Type[] argumentTypes)
129130
{
131+
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP
132+
if (!RuntimeFeature.IsDynamicCodeSupported)
133+
{
134+
// Create a reflection-based factory when dynamic code isn't supported, e.g. app is published with NativeAOT.
135+
// Reflection-based factory is faster than interpreted expressions and doesn't pull in System.Linq.Expressions dependency.
136+
return CreateFactoryReflection(instanceType, argumentTypes);
137+
}
138+
#endif
139+
130140
CreateFactoryInternal(instanceType, argumentTypes, out ParameterExpression provider, out ParameterExpression argumentArray, out Expression factoryExpressionBody);
131141

132142
var factoryLambda = Expression.Lambda<Func<IServiceProvider, object?[]?, object>>(
@@ -152,6 +162,16 @@ public static ObjectFactory<T>
152162
CreateFactory<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(
153163
Type[] argumentTypes)
154164
{
165+
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP
166+
if (!RuntimeFeature.IsDynamicCodeSupported)
167+
{
168+
// Create a reflection-based factory when dynamic code isn't supported, e.g. app is published with NativeAOT.
169+
// Reflection-based factory is faster than interpreted expressions and doesn't pull in System.Linq.Expressions dependency.
170+
var factory = CreateFactoryReflection(typeof(T), argumentTypes);
171+
return (serviceProvider, arguments) => (T)factory(serviceProvider, arguments);
172+
}
173+
#endif
174+
155175
CreateFactoryInternal(typeof(T), argumentTypes, out ParameterExpression provider, out ParameterExpression argumentArray, out Expression factoryExpressionBody);
156176

157177
var factoryLambda = Expression.Lambda<Func<IServiceProvider, object?[]?, T>>(
@@ -264,6 +284,67 @@ private static NewExpression BuildFactoryExpression(
264284
return Expression.New(constructor, constructorArguments);
265285
}
266286

287+
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP
288+
private static ObjectFactory CreateFactoryReflection(
289+
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type instanceType,
290+
Type?[] argumentTypes)
291+
{
292+
FindApplicableConstructor(instanceType, argumentTypes, out ConstructorInfo constructor, out int?[] parameterMap);
293+
294+
ParameterInfo[] constructorParameters = constructor.GetParameters();
295+
if (constructorParameters.Length == 0)
296+
{
297+
return (IServiceProvider serviceProvider, object?[]? arguments) =>
298+
constructor.Invoke(BindingFlags.DoNotWrapExceptions, binder: null, parameters: null, culture: null);
299+
}
300+
301+
FactoryParameterContext[] parameters = new FactoryParameterContext[constructorParameters.Length];
302+
for (int i = 0; i < constructorParameters.Length; i++)
303+
{
304+
ParameterInfo constructorParameter = constructorParameters[i];
305+
bool hasDefaultValue = ParameterDefaultValue.TryGetDefaultValue(constructorParameter, out object? defaultValue);
306+
307+
parameters[i] = new FactoryParameterContext(constructorParameter.ParameterType, hasDefaultValue, defaultValue, parameterMap[i] ?? -1);
308+
}
309+
Type declaringType = constructor.DeclaringType!;
310+
311+
return (IServiceProvider serviceProvider, object?[]? arguments) =>
312+
{
313+
object?[] constructorArguments = new object?[parameters.Length];
314+
for (int i = 0; i < parameters.Length; i++)
315+
{
316+
ref FactoryParameterContext parameter = ref parameters[i];
317+
constructorArguments[i] = ((parameter.ArgumentIndex != -1)
318+
// Throws an NullReferenceException if arguments is null. Consistent with expression-based factory.
319+
? arguments![parameter.ArgumentIndex]
320+
: GetService(
321+
serviceProvider,
322+
parameter.ParameterType,
323+
declaringType,
324+
parameter.HasDefaultValue)) ?? parameter.DefaultValue;
325+
}
326+
327+
return constructor.Invoke(BindingFlags.DoNotWrapExceptions, binder: null, constructorArguments, culture: null);
328+
};
329+
}
330+
331+
private readonly struct FactoryParameterContext
332+
{
333+
public FactoryParameterContext(Type parameterType, bool hasDefaultValue, object? defaultValue, int argumentIndex)
334+
{
335+
ParameterType = parameterType;
336+
HasDefaultValue = hasDefaultValue;
337+
DefaultValue = defaultValue;
338+
ArgumentIndex = argumentIndex;
339+
}
340+
341+
public Type ParameterType { get; }
342+
public bool HasDefaultValue { get; }
343+
public object? DefaultValue { get; }
344+
public int ArgumentIndex { get; }
345+
}
346+
#endif
347+
267348
private static void FindApplicableConstructor(
268349
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type instanceType,
269350
Type?[] argumentTypes,

src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ActivatorUtilitiesTests.cs

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System;
5+
using Microsoft.DotNet.RemoteExecutor;
56
using Xunit;
67
using static Microsoft.Extensions.DependencyInjection.Tests.AsyncServiceScopeTests;
78

@@ -191,6 +192,159 @@ public void CreateFactory_CreatesFactoryMethod()
191192
Assert.IsType<ObjectFactory<ClassWithABCS>>(factory2);
192193
Assert.IsType<ClassWithABCS>(item2);
193194
}
195+
196+
[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
197+
[InlineData(true)]
198+
#if NETCOREAPP
199+
[InlineData(false)]
200+
#endif
201+
public void CreateFactory_RemoteExecutor_CreatesFactoryMethod(bool isDynamicCodeSupported)
202+
{
203+
var options = new RemoteInvokeOptions();
204+
if (!isDynamicCodeSupported)
205+
{
206+
options.RuntimeConfigurationOptions.Add("System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported", "false");
207+
}
208+
209+
using var remoteHandle = RemoteExecutor.Invoke(static () =>
210+
{
211+
var factory1 = ActivatorUtilities.CreateFactory(typeof(ClassWithABCS), new Type[] { typeof(B) });
212+
var factory2 = ActivatorUtilities.CreateFactory<ClassWithABCS>(new Type[] { typeof(B) });
213+
214+
var services = new ServiceCollection();
215+
services.AddSingleton(new A());
216+
services.AddSingleton(new C());
217+
services.AddSingleton(new S());
218+
using var provider = services.BuildServiceProvider();
219+
object item1 = factory1(provider, new[] { new B() });
220+
var item2 = factory2(provider, new[] { new B() });
221+
222+
Assert.IsType<ObjectFactory>(factory1);
223+
Assert.IsType<ClassWithABCS>(item1);
224+
225+
Assert.IsType<ObjectFactory<ClassWithABCS>>(factory2);
226+
Assert.IsType<ClassWithABCS>(item2);
227+
}, options);
228+
}
229+
230+
[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
231+
[InlineData(true)]
232+
#if NETCOREAPP
233+
[InlineData(false)]
234+
#endif
235+
public void CreateFactory_RemoteExecutor_NullArguments_Throws(bool isDynamicCodeSupported)
236+
{
237+
var options = new RemoteInvokeOptions();
238+
if (!isDynamicCodeSupported)
239+
{
240+
options.RuntimeConfigurationOptions.Add("System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported", "false");
241+
}
242+
243+
using var remoteHandle = RemoteExecutor.Invoke(static () =>
244+
{
245+
var factory1 = ActivatorUtilities.CreateFactory(typeof(ClassWithA), new Type[] { typeof(A) });
246+
247+
var services = new ServiceCollection();
248+
using var provider = services.BuildServiceProvider();
249+
Assert.Throws<NullReferenceException>(() => factory1(provider, null));
250+
}, options);
251+
}
252+
253+
[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
254+
[InlineData(true)]
255+
#if NETCOREAPP
256+
[InlineData(false)]
257+
#endif
258+
public void CreateFactory_RemoteExecutor_NoArguments_UseNullDefaultValue(bool isDynamicCodeSupported)
259+
{
260+
var options = new RemoteInvokeOptions();
261+
if (!isDynamicCodeSupported)
262+
{
263+
options.RuntimeConfigurationOptions.Add("System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported", "false");
264+
}
265+
266+
using var remoteHandle = RemoteExecutor.Invoke(static () =>
267+
{
268+
var factory1 = ActivatorUtilities.CreateFactory(typeof(ClassWithADefaultValue), new Type[0]);
269+
270+
var services = new ServiceCollection();
271+
using var provider = services.BuildServiceProvider();
272+
var item = (ClassWithADefaultValue)factory1(provider, null);
273+
Assert.Null(item.A);
274+
}, options);
275+
}
276+
277+
[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
278+
[InlineData(true)]
279+
#if NETCOREAPP
280+
[InlineData(false)]
281+
#endif
282+
public void CreateFactory_RemoteExecutor_NoArguments_ThrowRequiredValue(bool isDynamicCodeSupported)
283+
{
284+
var options = new RemoteInvokeOptions();
285+
if (!isDynamicCodeSupported)
286+
{
287+
options.RuntimeConfigurationOptions.Add("System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported", "false");
288+
}
289+
290+
using var remoteHandle = RemoteExecutor.Invoke(static () =>
291+
{
292+
var factory1 = ActivatorUtilities.CreateFactory(typeof(ClassWithA), new Type[0]);
293+
294+
var services = new ServiceCollection();
295+
using var provider = services.BuildServiceProvider();
296+
var ex = Assert.Throws<InvalidOperationException>(() => factory1(provider, null));
297+
Assert.Equal($"Unable to resolve service for type '{typeof(A).FullName}' while attempting to activate '{typeof(ClassWithA).FullName}'.", ex.Message);
298+
}, options);
299+
}
300+
301+
[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
302+
[InlineData(true)]
303+
#if NETCOREAPP
304+
[InlineData(false)]
305+
#endif
306+
public void CreateFactory_RemoteExecutor_NullArgument_UseDefaultValue(bool isDynamicCodeSupported)
307+
{
308+
var options = new RemoteInvokeOptions();
309+
if (!isDynamicCodeSupported)
310+
{
311+
options.RuntimeConfigurationOptions.Add("System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported", "false");
312+
}
313+
314+
using var remoteHandle = RemoteExecutor.Invoke(static () =>
315+
{
316+
var factory1 = ActivatorUtilities.CreateFactory(typeof(ClassWithStringDefaultValue), new[] { typeof(string) });
317+
318+
var services = new ServiceCollection();
319+
using var provider = services.BuildServiceProvider();
320+
var item = (ClassWithStringDefaultValue)factory1(provider, new object[] { null });
321+
Assert.Equal("DEFAULT", item.Text);
322+
}, options);
323+
}
324+
325+
[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
326+
[InlineData(true)]
327+
#if NETCOREAPP
328+
[InlineData(false)]
329+
#endif
330+
public void CreateFactory_RemoteExecutor_NoParameters_Success(bool isDynamicCodeSupported)
331+
{
332+
var options = new RemoteInvokeOptions();
333+
if (!isDynamicCodeSupported)
334+
{
335+
options.RuntimeConfigurationOptions.Add("System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported", "false");
336+
}
337+
338+
using var remoteHandle = RemoteExecutor.Invoke(static () =>
339+
{
340+
var factory1 = ActivatorUtilities.CreateFactory(typeof(A), new Type[0]);
341+
342+
var services = new ServiceCollection();
343+
using var provider = services.BuildServiceProvider();
344+
var item = (A)factory1(provider, null);
345+
Assert.NotNull(item);
346+
}, options);
347+
}
194348
}
195349

196350
internal class A { }
@@ -265,6 +419,15 @@ public ClassWithA(A a)
265419
}
266420
}
267421

422+
internal class ClassWithADefaultValue
423+
{
424+
public A A { get; }
425+
public ClassWithADefaultValue(A a = null)
426+
{
427+
A = a;
428+
}
429+
}
430+
268431
internal class ABCS
269432
{
270433
public A A { get; }
@@ -354,4 +517,13 @@ public ClassWithABC_DefaultConstructorLast(A a, B b) : base (a, b) { }
354517
public ClassWithABC_DefaultConstructorLast(A a) : base(a) { }
355518
public ClassWithABC_DefaultConstructorLast() : base() { }
356519
}
520+
521+
internal class ClassWithStringDefaultValue
522+
{
523+
public string Text { get; set; }
524+
public ClassWithStringDefaultValue(string text = "DEFAULT")
525+
{
526+
Text = text;
527+
}
528+
}
357529
}

0 commit comments

Comments
 (0)