diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.cs b/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.cs index 8c8fcbd5f18b45..a7d8701061e5a1 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ActivatorUtilities.cs @@ -9,6 +9,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; +using System.Runtime.InteropServices; using Microsoft.Extensions.Internal; #if NETCOREAPP @@ -28,9 +29,7 @@ public static class ActivatorUtilities // Support caching of constructor metadata for types in collectible assemblies. private static readonly Lazy> s_collectibleConstructorInfos = new(); -#endif -#if NET8_0_OR_GREATER // Maximum number of fixed arguments for ConstructorInvoker.Invoke(arg1, etc). private const int FixedArgumentThreshold = 4; #endif @@ -66,10 +65,29 @@ public static object CreateInstance( { constructors = GetOrAddConstructors(instanceType); } + + // Attempt to use the stack allocated arg values if <= 4 ctor args. + Span values; + StackAllocatedObjects stackValues = default; + int maxArgs = GetMaxArgCount(); + if (maxArgs <= StackAllocatedObjects.MaxStackAllocArgCount / 2) + { + values = MemoryMarshal.CreateSpan(ref stackValues._args._arg0, maxArgs * 2); + } + else + { + values = new Span(new object?[maxArgs * 2], 0, maxArgs * 2); + } + + Span ctorArgs = values.Slice(0, maxArgs); + Span bestCtorArgs = values.Slice(maxArgs); #else constructors = CreateConstructorInfoExs(instanceType); + object?[]? ctorArgs = null; + object?[]? bestCtorArgs = null; #endif + ConstructorMatcher matcher = default; ConstructorInfoEx? constructor; IServiceProviderIsService? serviceProviderIsService = provider.GetService(); // if container supports using IServiceProviderIsService, we try to find the longest ctor that @@ -79,48 +97,70 @@ public static object CreateInstance( // instance if all parameters given to CreateInstance only match with a single ctor if (serviceProviderIsService != null) { - int bestLength = -1; - bool seenPreferred = false; - - ConstructorMatcher bestMatcher = default; - bool multipleBestLengthFound = false; - + // Handle the case where the attribute is used. for (int i = 0; i < constructors.Length; i++) { constructor = constructors[i]; - ConstructorMatcher matcher = new(constructor); - bool isPreferred = constructor.IsPreferred; - int length = matcher.Match(parameters, serviceProviderIsService); - if (isPreferred) + if (constructor.IsPreferred) { - if (seenPreferred) + for (int j = i + 1; j < constructors.Length; j++) { - ThrowMultipleCtorsMarkedWithAttributeException(); + if (constructors[j].IsPreferred) + { + ThrowMultipleCtorsMarkedWithAttributeException(); + } } - if (length == -1) + InitializeCtorArgValues(ref ctorArgs, constructor.Parameters.Length); + matcher = new ConstructorMatcher(constructor, ctorArgs); + if (matcher.Match(parameters, serviceProviderIsService) == -1) { ThrowMarkedCtorDoesNotTakeAllProvidedArguments(); } - seenPreferred = true; - bestLength = length; - bestMatcher = matcher; - multipleBestLengthFound = false; + return matcher.CreateInstance(provider); } - else if (!seenPreferred) + } + + int bestLength = -1; + ConstructorMatcher bestMatcher = default; + bool multipleBestLengthFound = false; + + // Find the constructor with the most matches. + for (int i = 0; i < constructors.Length; i++) + { + constructor = constructors[i]; + + InitializeCtorArgValues(ref ctorArgs, constructor.Parameters.Length); + matcher = new ConstructorMatcher(constructor, ctorArgs); + int length = matcher.Match(parameters, serviceProviderIsService); + + Debug.Assert(!constructor.IsPreferred); + + if (bestLength < length) { - if (bestLength < length) + bestLength = length; +#if NETCOREAPP + ctorArgs.CopyTo(bestCtorArgs); +#else + if (i == constructors.Length - 1) { - bestLength = length; - bestMatcher = matcher; - multipleBestLengthFound = false; + // We can prevent an alloc for the last case. + bestCtorArgs = ctorArgs; } - else if (bestLength == length) + else { - multipleBestLengthFound = true; + bestCtorArgs = new object?[length]; + ctorArgs.CopyTo(bestCtorArgs, 0); } +#endif + bestMatcher = new ConstructorMatcher(matcher.ConstructorInfo, bestCtorArgs); + multipleBestLengthFound = false; + } + else if (bestLength == length) + { + multipleBestLengthFound = true; } } @@ -149,24 +189,43 @@ public static object CreateInstance( } } - FindApplicableConstructor(instanceType, argumentTypes, out ConstructorInfo constructorInfo, out int?[] parameterMap); + FindApplicableConstructor(instanceType, argumentTypes, constructors, out ConstructorInfo constructorInfo, out int?[] parameterMap); + constructor = FindConstructorEx(constructorInfo, constructors); + + InitializeCtorArgValues(ref ctorArgs, constructor.Parameters.Length); + matcher = new ConstructorMatcher(constructor, ctorArgs); + matcher.MapParameters(parameterMap, parameters); + return matcher.CreateInstance(provider); - // Find the ConstructorInfoEx from the given constructorInfo. - constructor = null; - foreach (ConstructorInfoEx ctor in constructors) +#if NETCOREAPP + int GetMaxArgCount() { - if (ReferenceEquals(ctor.Info, constructorInfo)) + int max = 0; + for (int i = 0; i < constructors.Length; i++) { - constructor = ctor; - break; + max = int.Max(max, constructors[i].Parameters.Length); } - } - Debug.Assert(constructor != null); + return max; + } - var constructorMatcher = new ConstructorMatcher(constructor); - constructorMatcher.MapParameters(parameterMap, parameters); - return constructorMatcher.CreateInstance(provider); + static void InitializeCtorArgValues(ref Span ctorArgs, int _) + { + ctorArgs.Clear(); + } +#else + static void InitializeCtorArgValues(ref object[] ctorArgs, int length) + { + if (ctorArgs is not null && ctorArgs.Length == length) + { + Array.Clear(ctorArgs, 0, length); + } + else + { + ctorArgs = new object?[length]; + } + } +#endif } #if NETCOREAPP @@ -280,7 +339,7 @@ public static ObjectFactory private static void CreateFactoryInternal([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type instanceType, Type[] argumentTypes, out ParameterExpression provider, out ParameterExpression argumentArray, out Expression factoryExpressionBody) { - FindApplicableConstructor(instanceType, argumentTypes, out ConstructorInfo constructor, out int?[] parameterMap); + FindApplicableConstructor(instanceType, argumentTypes, constructors: null, out ConstructorInfo constructor, out int?[] parameterMap); provider = Expression.Parameter(typeof(IServiceProvider), "provider"); argumentArray = Expression.Parameter(typeof(object[]), "argumentArray"); @@ -401,10 +460,10 @@ private static ObjectFactory CreateFactoryReflection( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type instanceType, Type?[] argumentTypes) { - FindApplicableConstructor(instanceType, argumentTypes, out ConstructorInfo constructor, out int?[] parameterMap); + FindApplicableConstructor(instanceType, argumentTypes, constructors: null, out ConstructorInfo constructor, out int?[] parameterMap); Type declaringType = constructor.DeclaringType!; -#if NET8_0_OR_GREATER +#if NETCOREAPP ConstructorInvoker invoker = ConstructorInvoker.Create(constructor); ParameterInfo[] constructorParameters = constructor.GetParameters(); @@ -473,7 +532,7 @@ ObjectFactory InvokeCanonical() FactoryParameterContext[] parameters = GetFactoryParameterContext(); return (serviceProvider, arguments) => ReflectionFactoryCanonical(constructor, parameters, declaringType, serviceProvider, arguments); -#endif // NET8_0_OR_GREATER +#endif // NETCOREAPP FactoryParameterContext[] GetFactoryParameterContext() { @@ -518,13 +577,14 @@ public FactoryParameterContext(Type parameterType, bool hasDefaultValue, object? private static void FindApplicableConstructor( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type instanceType, Type?[] argumentTypes, + ConstructorInfoEx[]? constructors, out ConstructorInfo matchingConstructor, out int?[] matchingParameterMap) { ConstructorInfo? constructorInfo; int?[]? parameterMap; - if (!TryFindPreferredConstructor(instanceType, argumentTypes, out constructorInfo, out parameterMap) && + if (!TryFindPreferredConstructor(instanceType, argumentTypes, constructors, out constructorInfo, out parameterMap) && !TryFindMatchingConstructor(instanceType, argumentTypes, out constructorInfo, out parameterMap)) { throw new InvalidOperationException(SR.Format(SR.CtorNotLocated, instanceType)); @@ -534,6 +594,21 @@ private static void FindApplicableConstructor( matchingParameterMap = parameterMap; } + // Find the ConstructorInfoEx from the given constructorInfo. + private static ConstructorInfoEx FindConstructorEx(ConstructorInfo constructorInfo, ConstructorInfoEx[] constructorExs) + { + for (int i = 0; i < constructorExs.Length; i++) + { + if (ReferenceEquals(constructorExs[i].Info, constructorInfo)) + { + return constructorExs[i]; + } + } + + Debug.Assert(false); + return null!; + } + // Tries to find constructor based on provided argument types private static bool TryFindMatchingConstructor( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type instanceType, @@ -571,6 +646,7 @@ private static bool TryFindMatchingConstructor( private static bool TryFindPreferredConstructor( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type instanceType, Type?[] argumentTypes, + ConstructorInfoEx[]? constructors, [NotNullWhen(true)] out ConstructorInfo? matchingConstructor, [NotNullWhen(true)] out int?[]? parameterMap) { @@ -578,21 +654,33 @@ private static bool TryFindPreferredConstructor( matchingConstructor = null; parameterMap = null; - foreach (ConstructorInfo? constructor in instanceType.GetConstructors()) + if (constructors is null) { - if (constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute), false)) +#if NETCOREAPP + if (!s_constructorInfos.TryGetValue(instanceType, out constructors)) + { + constructors = GetOrAddConstructors(instanceType); + } +#else + constructors = CreateConstructorInfoExs(instanceType); +#endif + } + + foreach (ConstructorInfoEx constructor in constructors) + { + if (constructor.IsPreferred) { if (seenPreferred) { ThrowMultipleCtorsMarkedWithAttributeException(); } - if (!TryCreateParameterMap(constructor.GetParameters(), argumentTypes, out int?[] tempParameterMap)) + if (!TryCreateParameterMap(constructor.Info.GetParameters(), argumentTypes, out int?[] tempParameterMap)) { ThrowMarkedCtorDoesNotTakeAllProvidedArguments(); } - matchingConstructor = constructor; + matchingConstructor = constructor.Info; parameterMap = tempParameterMap; seenPreferred = true; } @@ -649,6 +737,17 @@ private sealed class ConstructorInfoEx public readonly ParameterInfo[] Parameters; public readonly bool IsPreferred; private readonly object?[]? _parameterKeys; +#if NETCOREAPP + public ConstructorInvoker? _invoker; + public ConstructorInvoker Invoker + { + get + { + _invoker ??= ConstructorInvoker.Create(Info); + return _invoker; + } + } +#endif public ConstructorInfoEx(ConstructorInfo constructor) { @@ -710,17 +809,24 @@ public bool IsService(IServiceProviderIsService serviceProviderIsService, int pa } } - private readonly struct ConstructorMatcher + private readonly ref struct ConstructorMatcher { private readonly ConstructorInfoEx _constructor; - private readonly object?[] _parameterValues; - public ConstructorMatcher(ConstructorInfoEx constructor) +#if NETCOREAPP + private readonly Span _parameterValues; + public ConstructorMatcher(ConstructorInfoEx constructor, Span parameterValues) +#else + private readonly object?[] _parameterValues; + public ConstructorMatcher(ConstructorInfoEx constructor, object?[] parameterValues) +#endif { _constructor = constructor; - _parameterValues = new object[constructor.Parameters.Length]; + _parameterValues = parameterValues; } + public ConstructorInfoEx ConstructorInfo => _constructor; + public int Match(object[] givenParameters, IServiceProviderIsService serviceProviderIsService) { for (int givenIndex = 0; givenIndex < givenParameters.Length; givenIndex++) @@ -790,7 +896,9 @@ public object CreateInstance(IServiceProvider provider) } } -#if NETFRAMEWORK || NETSTANDARD2_0 +#if NETCOREAPP + return _constructor.Invoker.Invoke(_parameterValues.Slice(0, _constructor.Parameters.Length)); +#else try { return _constructor.Info.Invoke(_parameterValues); @@ -801,8 +909,6 @@ public object CreateInstance(IServiceProvider provider) // The above line will always throw, but the compiler requires we throw explicitly. throw; } -#else - return _constructor.Info.Invoke(BindingFlags.DoNotWrapExceptions, binder: null, parameters: _parameterValues, culture: null); #endif } @@ -828,7 +934,7 @@ 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. +#if NETCOREAPP // Use the faster ConstructorInvoker which also has alloc-free APIs when <= 4 parameters. private static object ReflectionFactoryServiceOnlyFixed( ConstructorInvoker invoker, FactoryParameterContext[] parameters, @@ -1101,7 +1207,7 @@ private static object ReflectionFactoryCanonical( return constructor.Invoke(BindingFlags.DoNotWrapExceptions, binder: null, constructorArguments, culture: null); } -#endif // NET8_0_OR_GREATER +#endif #if NETCOREAPP internal static class ActivatorUtilitiesUpdateHandler @@ -1116,6 +1222,19 @@ public static void ClearCache(Type[]? _) } } } + + [StructLayout(LayoutKind.Sequential)] + private ref struct StackAllocatedObjects + { + internal const int MaxStackAllocArgCount = 8; + internal StackAllocatedObjectValues _args; + + [InlineArray(MaxStackAllocArgCount)] + internal struct StackAllocatedObjectValues + { + internal object? _arg0; + } + } #endif private static object? GetKeyedService(IServiceProvider provider, Type type, object? serviceKey)