Add ActivatorUtilities constructor attribute (#629)
This commit is contained in:
Родитель
ee6811d447
Коммит
5656e84f56
|
@ -0,0 +1,74 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection.Performance
|
||||
{
|
||||
public class ActivatorUtilitiesBenchmark
|
||||
{
|
||||
private ServiceProvider _serviceProvider;
|
||||
private ObjectFactory _factory;
|
||||
private object[] _factoryArguments;
|
||||
|
||||
[GlobalSetup]
|
||||
public void SetUp()
|
||||
{
|
||||
var collection = new ServiceCollection();
|
||||
collection.AddTransient<TypeToBeActivated>();
|
||||
collection.AddSingleton<DependencyA>();
|
||||
collection.AddSingleton<DependencyB>();
|
||||
collection.AddSingleton<DependencyC>();
|
||||
collection.AddTransient<TypeToBeActivated>();
|
||||
|
||||
_serviceProvider = collection.BuildServiceProvider();
|
||||
_factory = ActivatorUtilities.CreateFactory(typeof(TypeToBeActivated), new Type[] { typeof(DependencyB), typeof(DependencyC) });
|
||||
_factoryArguments = new object[] { new DependencyB(), new DependencyC() };
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void ServiceProvider()
|
||||
{
|
||||
_serviceProvider.GetService<TypeToBeActivated>();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Factory()
|
||||
{
|
||||
_ = (TypeToBeActivated)_factory(_serviceProvider, _factoryArguments);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void CreateInstance()
|
||||
{
|
||||
ActivatorUtilities.CreateInstance<TypeToBeActivated>(_serviceProvider, _factoryArguments);
|
||||
}
|
||||
|
||||
public class TypeToBeActivated
|
||||
{
|
||||
public TypeToBeActivated(int i)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public TypeToBeActivated(string s)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public TypeToBeActivated(object o)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public TypeToBeActivated(DependencyA a, DependencyB b, DependencyC c)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class DependencyA {}
|
||||
public class DependencyB {}
|
||||
public class DependencyC {}
|
||||
}
|
||||
}
|
|
@ -41,26 +41,41 @@ namespace Microsoft.Extensions.Internal
|
|||
public static object CreateInstance(IServiceProvider provider, Type instanceType, params object[] parameters)
|
||||
{
|
||||
int bestLength = -1;
|
||||
var seenPreferred = false;
|
||||
|
||||
ConstructorMatcher bestMatcher = null;
|
||||
|
||||
if (!instanceType.GetTypeInfo().IsAbstract)
|
||||
{
|
||||
foreach (var matcher in instanceType
|
||||
foreach (var constructor in instanceType
|
||||
.GetTypeInfo()
|
||||
.DeclaredConstructors
|
||||
.Where(c => !c.IsStatic && c.IsPublic)
|
||||
.Select(constructor => new ConstructorMatcher(constructor)))
|
||||
.Where(c => !c.IsStatic && c.IsPublic))
|
||||
{
|
||||
var matcher = new ConstructorMatcher(constructor);
|
||||
var isPreferred = constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute), false);
|
||||
var length = matcher.Match(parameters);
|
||||
if (length == -1)
|
||||
|
||||
if (isPreferred)
|
||||
{
|
||||
continue;
|
||||
if (seenPreferred)
|
||||
{
|
||||
ThrowMultipleCtorsMarkedWithAttributeException();
|
||||
}
|
||||
|
||||
if (length == -1)
|
||||
{
|
||||
ThrowMarkedCtorDoesNotTakeAllProvidedArguments();
|
||||
}
|
||||
}
|
||||
if (bestLength < length)
|
||||
|
||||
if (isPreferred || bestLength < length)
|
||||
{
|
||||
bestLength = length;
|
||||
bestMatcher = matcher;
|
||||
}
|
||||
|
||||
seenPreferred |= isPreferred;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -203,6 +218,21 @@ namespace Microsoft.Extensions.Internal
|
|||
matchingConstructor = null;
|
||||
parameterMap = null;
|
||||
|
||||
if (!TryFindPreferredConstructor(instanceType, argumentTypes, ref matchingConstructor, ref parameterMap) &&
|
||||
!TryFindMatchingConstructor(instanceType, argumentTypes, ref matchingConstructor, ref parameterMap))
|
||||
{
|
||||
var message = $"A suitable constructor for type '{instanceType}' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.";
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
}
|
||||
|
||||
// Tries to find constructor based on provided argument types
|
||||
private static bool TryFindMatchingConstructor(
|
||||
Type instanceType,
|
||||
Type[] argumentTypes,
|
||||
ref ConstructorInfo matchingConstructor,
|
||||
ref int?[] parameterMap)
|
||||
{
|
||||
foreach (var constructor in instanceType.GetTypeInfo().DeclaredConstructors)
|
||||
{
|
||||
if (constructor.IsStatic || !constructor.IsPublic)
|
||||
|
@ -222,11 +252,43 @@ namespace Microsoft.Extensions.Internal
|
|||
}
|
||||
}
|
||||
|
||||
if (matchingConstructor == null)
|
||||
return matchingConstructor != null;
|
||||
}
|
||||
|
||||
// Tries to find constructor marked with ActivatorUtilitiesConstructorAttribute
|
||||
private static bool TryFindPreferredConstructor(
|
||||
Type instanceType,
|
||||
Type[] argumentTypes,
|
||||
ref ConstructorInfo matchingConstructor,
|
||||
ref int?[] parameterMap)
|
||||
{
|
||||
var seenPreferred = false;
|
||||
foreach (var constructor in instanceType.GetTypeInfo().DeclaredConstructors)
|
||||
{
|
||||
var message = $"A suitable constructor for type '{instanceType}' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.";
|
||||
throw new InvalidOperationException(message);
|
||||
if (constructor.IsStatic || !constructor.IsPublic)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (constructor.IsDefined(typeof(ActivatorUtilitiesConstructorAttribute), false))
|
||||
{
|
||||
if (seenPreferred)
|
||||
{
|
||||
ThrowMultipleCtorsMarkedWithAttributeException();
|
||||
}
|
||||
|
||||
if (!TryCreateParameterMap(constructor.GetParameters(), argumentTypes, out int?[] tempParameterMap))
|
||||
{
|
||||
ThrowMarkedCtorDoesNotTakeAllProvidedArguments();
|
||||
}
|
||||
|
||||
matchingConstructor = constructor;
|
||||
parameterMap = tempParameterMap;
|
||||
seenPreferred = true;
|
||||
}
|
||||
}
|
||||
|
||||
return matchingConstructor != null;
|
||||
}
|
||||
|
||||
// Creates an injective parameterMap from givenParameterTypes to assignable constructorParameters.
|
||||
|
@ -353,5 +415,15 @@ namespace Microsoft.Extensions.Internal
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ThrowMultipleCtorsMarkedWithAttributeException()
|
||||
{
|
||||
throw new InvalidOperationException($"Multiple constructors were marked with {nameof(ActivatorUtilitiesConstructorAttribute)}.");
|
||||
}
|
||||
|
||||
private static void ThrowMarkedCtorDoesNotTakeAllProvidedArguments()
|
||||
{
|
||||
throw new InvalidOperationException($"Constructor marked with {nameof(ActivatorUtilitiesConstructorAttribute)} does not accept all given argument types.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
#if ActivatorUtilities_In_DependencyInjection
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
#else
|
||||
namespace Microsoft.Extensions.Internal
|
||||
#endif
|
||||
{
|
||||
/// <summary>
|
||||
/// Marks the constructor to be used when activating type using <see cref="ActivatorUtilities"/>.
|
||||
/// </summary>
|
||||
|
||||
#if ActivatorUtilities_In_DependencyInjection
|
||||
public
|
||||
#else
|
||||
// Do not take a dependency on this class unless you are explicitly trying to avoid taking a
|
||||
// dependency on Microsoft.AspNetCore.DependencyInjection.Abstractions.
|
||||
internal
|
||||
#endif
|
||||
class ActivatorUtilitiesConstructorAttribute: Attribute
|
||||
{
|
||||
}
|
||||
}
|
|
@ -205,6 +205,62 @@ namespace Microsoft.Extensions.DependencyInjection.Specification
|
|||
Assert.Equal(expectedMessage, ex.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("", "string")]
|
||||
[InlineData(5, "IFakeService, int")]
|
||||
public void TypeActivatorCreateInstanceUsesFirstMathchedConstructor(object value, string ctor)
|
||||
{
|
||||
// Arrange
|
||||
var serviceCollection = new TestServiceCollection();
|
||||
serviceCollection.AddSingleton<IFakeService, FakeService>();
|
||||
var serviceProvider = CreateServiceProvider(serviceCollection);
|
||||
var type = typeof(ClassWithAmbiguousCtors);
|
||||
|
||||
// Act
|
||||
var instance = ActivatorUtilities.CreateInstance(serviceProvider, type, value);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(ctor, ((ClassWithAmbiguousCtors)instance).CtorUsed);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(CreateInstanceFuncs))]
|
||||
public void TypeActivatorUsesMarkedConstructor(CreateInstanceFunc createFunc)
|
||||
{
|
||||
// Arrange
|
||||
var serviceCollection = new TestServiceCollection();
|
||||
serviceCollection.AddSingleton<IFakeService, FakeService>();
|
||||
var serviceProvider = CreateServiceProvider(serviceCollection);
|
||||
|
||||
// Act
|
||||
var instance = CreateInstance<ClassWithAmbiguousCtorsAndAttribute>(createFunc, serviceProvider, "hello");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("IFakeService, string", instance.CtorUsed);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(CreateInstanceFuncs))]
|
||||
public void TypeActivatorThrowsOnMultipleMarkedCtors(CreateInstanceFunc createFunc)
|
||||
{
|
||||
// Act
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => CreateInstance<ClassWithMultipleMarkedCtors>(createFunc, null, "hello"));
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Multiple constructors were marked with ActivatorUtilitiesConstructorAttribute.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(CreateInstanceFuncs))]
|
||||
public void TypeActivatorThrowsWhenMarkedCtorDoesntAcceptArguments(CreateInstanceFunc createFunc)
|
||||
{
|
||||
// Act
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => CreateInstance<ClassWithAmbiguousCtorsAndAttribute>(createFunc, null, 0, "hello"));
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Constructor marked with ActivatorUtilitiesConstructorAttribute does not accept all given argument types.", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetServiceOrCreateInstanceRegisteredServiceTransient()
|
||||
{
|
||||
|
|
|
@ -7,14 +7,17 @@ namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
|
|||
{
|
||||
public ClassWithAmbiguousCtors(string data)
|
||||
{
|
||||
CtorUsed = "string";
|
||||
}
|
||||
|
||||
public ClassWithAmbiguousCtors(IFakeService service, string data)
|
||||
{
|
||||
CtorUsed = "IFakeService, string";
|
||||
}
|
||||
|
||||
public ClassWithAmbiguousCtors(IFakeService service, int data)
|
||||
{
|
||||
CtorUsed = "IFakeService, int";
|
||||
}
|
||||
|
||||
public ClassWithAmbiguousCtors(IFakeService service, string data1, int data2)
|
||||
|
@ -22,6 +25,8 @@ namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
|
|||
FakeService = service;
|
||||
Data1 = data1;
|
||||
Data2 = data2;
|
||||
|
||||
CtorUsed = "IFakeService, string, string";
|
||||
}
|
||||
|
||||
public IFakeService FakeService { get; }
|
||||
|
@ -29,5 +34,6 @@ namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
|
|||
public string Data1 { get; }
|
||||
|
||||
public int Data2 { get; }
|
||||
public string CtorUsed { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
|
||||
{
|
||||
public class ClassWithAmbiguousCtorsAndAttribute
|
||||
{
|
||||
public ClassWithAmbiguousCtorsAndAttribute(string data)
|
||||
{
|
||||
CtorUsed = "string";
|
||||
}
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public ClassWithAmbiguousCtorsAndAttribute(IFakeService service, string data)
|
||||
{
|
||||
CtorUsed = "IFakeService, string";
|
||||
}
|
||||
|
||||
public ClassWithAmbiguousCtorsAndAttribute(IFakeService service, IFakeOuterService service2, string data)
|
||||
{
|
||||
CtorUsed = "IFakeService, IFakeService, string";
|
||||
}
|
||||
|
||||
public string CtorUsed { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection.Specification.Fakes
|
||||
{
|
||||
public class ClassWithMultipleMarkedCtors
|
||||
{
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public ClassWithMultipleMarkedCtors(string data)
|
||||
{
|
||||
}
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public ClassWithMultipleMarkedCtors(IFakeService service, string data)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче