зеркало из
1
0
Форкнуть 0

Add ActivatorUtilities constructor attribute (#629)

This commit is contained in:
Pavel Krymets 2018-03-14 10:41:22 -07:00 коммит произвёл GitHub
Родитель ee6811d447
Коммит 5656e84f56
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
7 изменённых файлов: 287 добавлений и 9 удалений

Просмотреть файл

@ -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)
{
}
}
}