diff --git a/src/libraries/Common/src/Extensions/ParameterDefaultValue/ParameterDefaultValue.netstandard.cs b/src/libraries/Common/src/Extensions/ParameterDefaultValue/ParameterDefaultValue.netstandard.cs index 78486aa1e37ef8..089c64afe03b00 100644 --- a/src/libraries/Common/src/Extensions/ParameterDefaultValue/ParameterDefaultValue.netstandard.cs +++ b/src/libraries/Common/src/Extensions/ParameterDefaultValue/ParameterDefaultValue.netstandard.cs @@ -2,7 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics.CodeAnalysis; using System.Reflection; +using System.Runtime.Serialization; namespace Microsoft.Extensions.Internal { diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.cs b/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.cs index 559095b73c9f4c..43e8f5627a2d28 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.cs @@ -17,11 +17,6 @@ namespace Microsoft.Extensions.DependencyInjection /// public static class ActivatorUtilities { -#if NET8_0_OR_GREATER - // Maximum number of fixed arguments for ConstructorInvoker.Invoke(arg1, etc). - private const int FixedArgumentThreshold = 4; -#endif - private static readonly MethodInfo GetServiceInfo = GetMethodInfo>((sp, t, r, c) => GetService(sp, t, r, c)); @@ -145,6 +140,7 @@ public static ObjectFactory CreateFactory( return CreateFactoryReflection(instanceType, argumentTypes); } #endif + CreateFactoryInternal(instanceType, argumentTypes, out ParameterExpression provider, out ParameterExpression argumentArray, out Expression factoryExpressionBody); var factoryLambda = Expression.Lambda>( @@ -178,6 +174,7 @@ public static ObjectFactory return (serviceProvider, arguments) => (T)factory(serviceProvider, arguments); } #endif + CreateFactoryInternal(typeof(T), argumentTypes, out ParameterExpression provider, out ParameterExpression argumentArray, out Expression factoryExpressionBody); var factoryLambda = Expression.Lambda>( @@ -238,22 +235,16 @@ private static MethodInfo GetMethodInfo(Expression expr) return mc.Method; } - private static object? GetService(IServiceProvider sp, Type type, Type requiredBy, bool hasDefaultValue) + private static object? GetService(IServiceProvider sp, Type type, Type requiredBy, bool isDefaultParameterRequired) { object? service = sp.GetService(type); - if (service is null && !hasDefaultValue) + if (service == null && !isDefaultParameterRequired) { - ThrowHelperUnableToResolveService(type, requiredBy); + throw new InvalidOperationException(SR.Format(SR.UnableToResolveService, type, requiredBy)); } return service; } - [DoesNotReturn] - private static void ThrowHelperUnableToResolveService(Type type, Type requiredBy) - { - throw new InvalidOperationException(SR.Format(SR.UnableToResolveService, type, requiredBy)); - } - private static BlockExpression BuildFactoryExpression( ConstructorInfo constructor, int?[] parameterMap, @@ -298,114 +289,53 @@ private static BlockExpression BuildFactoryExpression( } #if NETSTANDARD2_1_OR_GREATER || NETCOREAPP - [DoesNotReturn] - private static void ThrowHelperArgumentNullExceptionServiceProvider() - { - throw new ArgumentNullException("serviceProvider"); - } - private static ObjectFactory CreateFactoryReflection( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type instanceType, Type?[] argumentTypes) { FindApplicableConstructor(instanceType, argumentTypes, out ConstructorInfo constructor, out int?[] parameterMap); - Type declaringType = constructor.DeclaringType!; - -#if NET8_0_OR_GREATER - ConstructorInvoker invoker = ConstructorInvoker.Create(constructor); ParameterInfo[] constructorParameters = constructor.GetParameters(); if (constructorParameters.Length == 0) { return (IServiceProvider serviceProvider, object?[]? arguments) => - invoker.Invoke(); + constructor.Invoke(BindingFlags.DoNotWrapExceptions, binder: null, parameters: null, culture: null); } - // Gather some metrics to determine what fast path to take, if any. - bool useFixedValues = constructorParameters.Length <= FixedArgumentThreshold; - bool hasAnyDefaultValues = false; - int matchedArgCount = 0; - int matchedArgCountWithMap = 0; + FactoryParameterContext[] parameters = new FactoryParameterContext[constructorParameters.Length]; for (int i = 0; i < constructorParameters.Length; i++) { - hasAnyDefaultValues |= constructorParameters[i].HasDefaultValue; - - if (parameterMap[i] is not null) - { - matchedArgCount++; - if (parameterMap[i] == i) - { - matchedArgCountWithMap++; - } - } - } - - // No fast path; contains default values or arg mapping. - if (hasAnyDefaultValues || matchedArgCount != matchedArgCountWithMap) - { - return InvokeCanonical(); - } - - if (matchedArgCount == 0) - { - // All injected; use a fast path. - Type[] types = GetParameterTypes(); - return useFixedValues ? - (serviceProvider, arguments) => ReflectionFactoryServiceOnlyFixed(invoker, types, declaringType, serviceProvider) : - (serviceProvider, arguments) => ReflectionFactoryServiceOnlySpan(invoker, types, declaringType, serviceProvider); - } - - if (matchedArgCount == constructorParameters.Length) - { - // All direct with no mappings; use a fast path. - return (serviceProvider, arguments) => ReflectionFactoryDirect(invoker, serviceProvider, arguments); - } - - return InvokeCanonical(); + ParameterInfo constructorParameter = constructorParameters[i]; + bool hasDefaultValue = ParameterDefaultValue.TryGetDefaultValue(constructorParameter, out object? defaultValue); - ObjectFactory InvokeCanonical() - { - FactoryParameterContext[] parameters = GetFactoryParameterContext(); - return useFixedValues ? - (serviceProvider, arguments) => ReflectionFactoryCanonicalFixed(invoker, parameters, declaringType, serviceProvider, arguments) : - (serviceProvider, arguments) => ReflectionFactoryCanonicalSpan(invoker, parameters, declaringType, serviceProvider, arguments); + parameters[i] = new FactoryParameterContext(constructorParameter.ParameterType, hasDefaultValue, defaultValue, parameterMap[i] ?? -1); } + Type declaringType = constructor.DeclaringType!; - Type[] GetParameterTypes() + return (IServiceProvider serviceProvider, object?[]? arguments) => { - Type[] types = new Type[constructorParameters.Length]; - for (int i = 0; i < constructorParameters.Length; i++) + if (serviceProvider is null) { - types[i] = constructorParameters[i].ParameterType; + throw new ArgumentNullException(nameof(serviceProvider)); } - return types; - } -#else - ParameterInfo[] constructorParameters = constructor.GetParameters(); - if (constructorParameters.Length == 0) - { - return (IServiceProvider serviceProvider, object?[]? arguments) => - constructor.Invoke(BindingFlags.DoNotWrapExceptions, binder: null, parameters: null, culture: null); - } - - FactoryParameterContext[] parameters = GetFactoryParameterContext(); - return (serviceProvider, arguments) => ReflectionFactoryCanonical(constructor, parameters, declaringType, serviceProvider, arguments); -#endif // NET8_0_OR_GREATER - FactoryParameterContext[] GetFactoryParameterContext() - { - FactoryParameterContext[] parameters = new FactoryParameterContext[constructorParameters.Length]; - for (int i = 0; i < constructorParameters.Length; i++) + object?[] constructorArguments = new object?[parameters.Length]; + for (int i = 0; i < parameters.Length; i++) { - ParameterInfo constructorParameter = constructorParameters[i]; - bool hasDefaultValue = ParameterDefaultValue.TryGetDefaultValue(constructorParameter, out object? defaultValue); - parameters[i] = new FactoryParameterContext(constructorParameter.ParameterType, hasDefaultValue, defaultValue, parameterMap[i] ?? -1); + ref FactoryParameterContext parameter = ref parameters[i]; + constructorArguments[i] = ((parameter.ArgumentIndex != -1) + // Throws an NullReferenceException if arguments is null. Consistent with expression-based factory. + ? arguments![parameter.ArgumentIndex] + : GetService( + serviceProvider, + parameter.ParameterType, + declaringType, + parameter.HasDefaultValue)) ?? parameter.DefaultValue; } - return parameters; - } + return constructor.Invoke(BindingFlags.DoNotWrapExceptions, binder: null, constructorArguments, culture: null); + }; } -#endif // NETSTANDARD2_1_OR_GREATER || NETCOREAPP private readonly struct FactoryParameterContext { @@ -422,6 +352,7 @@ public FactoryParameterContext(Type parameterType, bool hasDefaultValue, object? public object? DefaultValue { get; } public int ArgumentIndex { get; } } +#endif private static void FindApplicableConstructor( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type instanceType, @@ -429,11 +360,11 @@ private static void FindApplicableConstructor( out ConstructorInfo matchingConstructor, out int?[] matchingParameterMap) { - ConstructorInfo? constructorInfo; - int?[]? parameterMap; + ConstructorInfo? constructorInfo = null; + int?[]? parameterMap = null; - if (!TryFindPreferredConstructor(instanceType, argumentTypes, out constructorInfo, out parameterMap) && - !TryFindMatchingConstructor(instanceType, argumentTypes, out constructorInfo, out parameterMap)) + if (!TryFindPreferredConstructor(instanceType, argumentTypes, ref constructorInfo, ref parameterMap) && + !TryFindMatchingConstructor(instanceType, argumentTypes, ref constructorInfo, ref parameterMap)) { throw new InvalidOperationException(SR.Format(SR.CtorNotLocated, instanceType)); } @@ -446,12 +377,9 @@ private static void FindApplicableConstructor( private static bool TryFindMatchingConstructor( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type instanceType, Type?[] argumentTypes, - [NotNullWhen(true)] out ConstructorInfo? matchingConstructor, - [NotNullWhen(true)] out int?[]? parameterMap) + [NotNullWhen(true)] ref ConstructorInfo? matchingConstructor, + [NotNullWhen(true)] ref int?[]? parameterMap) { - matchingConstructor = null; - parameterMap = null; - foreach (ConstructorInfo? constructor in instanceType.GetConstructors()) { if (TryCreateParameterMap(constructor.GetParameters(), argumentTypes, out int?[] tempParameterMap)) @@ -479,13 +407,10 @@ private static bool TryFindMatchingConstructor( private static bool TryFindPreferredConstructor( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type instanceType, Type?[] argumentTypes, - [NotNullWhen(true)] out ConstructorInfo? matchingConstructor, - [NotNullWhen(true)] out int?[]? parameterMap) + [NotNullWhen(true)] ref ConstructorInfo? matchingConstructor, + [NotNullWhen(true)] ref int?[]? parameterMap) { bool seenPreferred = false; - matchingConstructor = null; - parameterMap = null; - foreach (ConstructorInfo? constructor in instanceType.GetConstructors()) { if (constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute), false)) @@ -717,204 +642,5 @@ private static void ThrowMarkedCtorDoesNotTakeAllProvidedArguments() { throw new InvalidOperationException(SR.Format(SR.MarkedCtorMissingArgumentTypes, nameof(ActivatorUtilitiesConstructorAttribute))); } - -#if NET8_0_OR_GREATER // Use the faster ConstructorInvoker which also has alloc-free APIs when <= 4 parameters. - private static object ReflectionFactoryServiceOnlyFixed( - ConstructorInvoker invoker, - Type[] parameterTypes, - Type declaringType, - IServiceProvider serviceProvider) - { - Debug.Assert(parameterTypes.Length >= 1 && parameterTypes.Length <= FixedArgumentThreshold); - Debug.Assert(FixedArgumentThreshold == 4); - - if (serviceProvider is null) - ThrowHelperArgumentNullExceptionServiceProvider(); - - object? arg1 = null; - object? arg2 = null; - object? arg3 = null; - object? arg4 = null; - - switch (parameterTypes.Length) - { - case 4: - arg4 = GetService(serviceProvider, parameterTypes[3], declaringType, false); - goto case 3; - case 3: - arg3 = GetService(serviceProvider, parameterTypes[2], declaringType, false); - goto case 2; - case 2: - arg2 = GetService(serviceProvider, parameterTypes[1], declaringType, false); - goto case 1; - case 1: - arg1 = GetService(serviceProvider, parameterTypes[0], declaringType, false); - break; - } - - return invoker.Invoke(arg1, arg2, arg3, arg4); - } - - private static object ReflectionFactoryServiceOnlySpan( - ConstructorInvoker invoker, - Type[] parameterTypes, - Type declaringType, - IServiceProvider serviceProvider) - { - if (serviceProvider is null) - ThrowHelperArgumentNullExceptionServiceProvider(); - - object?[] arguments = new object?[parameterTypes.Length]; - for (int i = 0; i < parameterTypes.Length; i++) - { - arguments[i] = GetService(serviceProvider, parameterTypes[i], declaringType, false); - } - - return invoker.Invoke(arguments.AsSpan()); - } - - private static object ReflectionFactoryCanonicalFixed( - ConstructorInvoker invoker, - FactoryParameterContext[] parameters, - Type declaringType, - IServiceProvider serviceProvider, - object?[]? arguments) - { - Debug.Assert(parameters.Length >= 1 && parameters.Length <= FixedArgumentThreshold); - Debug.Assert(FixedArgumentThreshold == 4); - - if (serviceProvider is null) - ThrowHelperArgumentNullExceptionServiceProvider(); - - object? arg1 = null; - object? arg2 = null; - object? arg3 = null; - object? arg4 = null; - - switch (parameters.Length) - { - case 4: - ref FactoryParameterContext parameter4 = ref parameters[3]; - arg4 = ((parameter4.ArgumentIndex != -1) - // Throws a NullReferenceException if arguments is null. Consistent with expression-based factory. - ? arguments![parameter4.ArgumentIndex] - : GetService( - serviceProvider, - parameter4.ParameterType, - declaringType, - parameter4.HasDefaultValue)) ?? parameter4.DefaultValue; - goto case 3; - case 3: - ref FactoryParameterContext parameter3 = ref parameters[2]; - arg3 = ((parameter3.ArgumentIndex != -1) - ? arguments![parameter3.ArgumentIndex] - : GetService( - serviceProvider, - parameter3.ParameterType, - declaringType, - parameter3.HasDefaultValue)) ?? parameter3.DefaultValue; - goto case 2; - case 2: - ref FactoryParameterContext parameter2 = ref parameters[1]; - arg2 = ((parameter2.ArgumentIndex != -1) - ? arguments![parameter2.ArgumentIndex] - : GetService( - serviceProvider, - parameter2.ParameterType, - declaringType, - parameter2.HasDefaultValue)) ?? parameter2.DefaultValue; - goto case 1; - case 1: - ref FactoryParameterContext parameter1 = ref parameters[0]; - arg1 = ((parameter1.ArgumentIndex != -1) - ? arguments![parameter1.ArgumentIndex] - : GetService( - serviceProvider, - parameter1.ParameterType, - declaringType, - parameter1.HasDefaultValue)) ?? parameter1.DefaultValue; - break; - } - - return invoker.Invoke(arg1, arg2, arg3, arg4); - } - - private static object ReflectionFactoryCanonicalSpan( - ConstructorInvoker invoker, - FactoryParameterContext[] parameters, - Type declaringType, - IServiceProvider serviceProvider, - object?[]? arguments) - { - if (serviceProvider is null) - ThrowHelperArgumentNullExceptionServiceProvider(); - - object?[] constructorArguments = new object?[parameters.Length]; - for (int i = 0; i < parameters.Length; i++) - { - ref FactoryParameterContext parameter = ref parameters[i]; - constructorArguments[i] = ((parameter.ArgumentIndex != -1) - // Throws a NullReferenceException if arguments is null. Consistent with expression-based factory. - ? arguments![parameter.ArgumentIndex] - : GetService( - serviceProvider, - parameter.ParameterType, - declaringType, - parameter.HasDefaultValue)) ?? parameter.DefaultValue; - } - - return invoker.Invoke(constructorArguments.AsSpan()); - } - - private static object ReflectionFactoryDirect( - ConstructorInvoker invoker, - IServiceProvider serviceProvider, - object?[]? arguments) - { - if (serviceProvider is null) - ThrowHelperArgumentNullExceptionServiceProvider(); - - if (arguments is null) - ThrowHelperNullReferenceException(); //AsSpan() will not throw NullReferenceException. - - return invoker.Invoke(arguments.AsSpan()); - } - - /// - /// For consistency with the expression-based factory, throw NullReferenceException. - /// - [DoesNotReturn] - private static void ThrowHelperNullReferenceException() - { - throw new NullReferenceException(); - } -#elif NETSTANDARD2_1_OR_GREATER || NETCOREAPP - private static object ReflectionFactoryCanonical( - ConstructorInfo constructor, - FactoryParameterContext[] parameters, - Type declaringType, - IServiceProvider serviceProvider, - object?[]? arguments) - { - if (serviceProvider is null) - ThrowHelperArgumentNullExceptionServiceProvider(); - - object?[] constructorArguments = new object?[parameters.Length]; - for (int i = 0; i < parameters.Length; i++) - { - ref FactoryParameterContext parameter = ref parameters[i]; - constructorArguments[i] = ((parameter.ArgumentIndex != -1) - // Throws a NullReferenceException if arguments is null. Consistent with expression-based factory. - ? arguments![parameter.ArgumentIndex] - : GetService( - serviceProvider, - parameter.ParameterType, - declaringType, - parameter.HasDefaultValue)) ?? parameter.DefaultValue; - } - - return constructor.Invoke(BindingFlags.DoNotWrapExceptions, binder: null, constructorArguments, culture: null); - } -#endif // NET8_0_OR_GREATER } } diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection.Specification.Tests/src/ActivatorUtilitiesTests.cs b/src/libraries/Microsoft.Extensions.DependencyInjection.Specification.Tests/src/ActivatorUtilitiesTests.cs index dcc847052b2547..860768f0e4612c 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection.Specification.Tests/src/ActivatorUtilitiesTests.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection.Specification.Tests/src/ActivatorUtilitiesTests.cs @@ -195,7 +195,7 @@ public void TypeActivatorRethrowsOriginalExceptionFromConstructor(CreateInstance CreateInstance(createFunc, provider: serviceProvider)); var ex2 = Assert.Throws(() => - CreateInstance(createFunc, provider: serviceProvider, args: new object[] { new FakeService() })); + CreateInstance(createFunc, provider: serviceProvider, args: new[] { new FakeService() })); // Assert Assert.Equal(nameof(ClassWithThrowingEmptyCtor), ex1.Message); diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ActivatorUtilitiesTests.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ActivatorUtilitiesTests.cs index 7572e6977a4c49..4c065b61bb2856 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ActivatorUtilitiesTests.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ActivatorUtilitiesTests.cs @@ -94,8 +94,7 @@ public void TypeActivatorThrowsOnNullProvider() public void FactoryActivatorThrowsOnNullProvider() { var f = ActivatorUtilities.CreateFactory(typeof(ClassWithA), new Type[0]); - Exception ex = Assert.Throws(() => f(serviceProvider: null, null)); - Assert.Contains("serviceProvider", ex.ToString()); + Assert.Throws(() => f(serviceProvider: null, null)); } [Fact] @@ -180,7 +179,7 @@ public void CreateInstance_ClassWithABC_MultipleCtorsWithSameLength_ThrowsAmbigu } [Fact] - public void CreateFactory_CreatesFactoryMethod_4Types_3Injected() + public void CreateFactory_CreatesFactoryMethod() { var factory1 = ActivatorUtilities.CreateFactory(typeof(ClassWithABCS), new Type[] { typeof(B) }); var factory2 = ActivatorUtilities.CreateFactory(new Type[] { typeof(B) }); @@ -195,42 +194,9 @@ public void CreateFactory_CreatesFactoryMethod_4Types_3Injected() Assert.IsType(factory1); Assert.IsType(item1); - ClassWithABCS obj = (ClassWithABCS)item1; - Assert.NotNull(obj.A); - Assert.NotNull(obj.B); - Assert.NotNull(obj.C); - Assert.NotNull(obj.S); Assert.IsType>(factory2); Assert.IsType(item2); - - Assert.NotNull(item2.A); - Assert.NotNull(item2.B); - Assert.NotNull(item2.C); - Assert.NotNull(item2.S); - } - - [Fact] - public void CreateFactory_CreatesFactoryMethod_5Types_5Injected() - { - // Inject 5 types which is a threshold for whether fixed or Span<> invoker args are used by reflection. - var factory = ActivatorUtilities.CreateFactory(Type.EmptyTypes); - - var services = new ServiceCollection(); - services.AddSingleton(new A()); - services.AddSingleton(new B()); - services.AddSingleton(new C()); - services.AddSingleton(new S()); - services.AddSingleton(new Z()); - using var provider = services.BuildServiceProvider(); - ClassWithABCSZ item = factory(provider, null); - - Assert.IsType>(factory); - Assert.NotNull(item.A); - Assert.NotNull(item.B); - Assert.NotNull(item.C); - Assert.NotNull(item.S); - Assert.NotNull(item.Z); } [ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] @@ -399,7 +365,6 @@ internal class A { } internal class B { } internal class C { } internal class S { } - internal class Z { } internal class ClassWithABCS : ClassWithABC { @@ -408,12 +373,6 @@ internal class ClassWithABCS : ClassWithABC public ClassWithABCS(A a, C c, S s) : this(a, null, c, s) { } } - internal class ClassWithABCSZ : ClassWithABCS - { - public Z Z { get; } - public ClassWithABCSZ(A a, B b, C c, S s, Z z) : base(a, b, c, s) { Z = z; } - } - internal class ClassWithABC_FirstConstructorWithAttribute : ClassWithABC { [ActivatorUtilitiesConstructor]