Make lifetime a callsite property and add stackguards (#638)
This commit is contained in:
Родитель
ca90dc6871
Коммит
d77b090567
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
@ -14,12 +15,16 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
{
|
||||
internal class CallSiteFactory
|
||||
{
|
||||
private const int DefaultSlot = 0;
|
||||
private readonly List<ServiceDescriptor> _descriptors;
|
||||
private readonly Dictionary<Type, IServiceCallSite> _callSiteCache = new Dictionary<Type, IServiceCallSite>();
|
||||
private readonly ConcurrentDictionary<Type, ServiceCallSite> _callSiteCache = new ConcurrentDictionary<Type, ServiceCallSite>();
|
||||
private readonly Dictionary<Type, ServiceDescriptorCacheItem> _descriptorLookup = new Dictionary<Type, ServiceDescriptorCacheItem>();
|
||||
|
||||
private readonly StackGuard _stackGuard;
|
||||
|
||||
public CallSiteFactory(IEnumerable<ServiceDescriptor> descriptors)
|
||||
{
|
||||
_stackGuard = new StackGuard();
|
||||
_descriptors = descriptors.ToList();
|
||||
Populate(descriptors);
|
||||
}
|
||||
|
@ -66,57 +71,63 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
}
|
||||
}
|
||||
|
||||
internal IServiceCallSite CreateCallSite(Type serviceType, CallSiteChain callSiteChain)
|
||||
internal ServiceCallSite GetCallSite(Type serviceType, CallSiteChain callSiteChain)
|
||||
{
|
||||
lock (_callSiteCache)
|
||||
{
|
||||
if (_callSiteCache.TryGetValue(serviceType, out var cachedCallSite))
|
||||
{
|
||||
return cachedCallSite;
|
||||
}
|
||||
|
||||
IServiceCallSite callSite;
|
||||
try
|
||||
{
|
||||
callSiteChain.CheckCircularDependency(serviceType);
|
||||
|
||||
callSite = TryCreateExact(serviceType, callSiteChain) ??
|
||||
TryCreateOpenGeneric(serviceType, callSiteChain) ??
|
||||
TryCreateEnumerable(serviceType, callSiteChain);
|
||||
}
|
||||
finally
|
||||
{
|
||||
callSiteChain.Remove(serviceType);
|
||||
}
|
||||
|
||||
_callSiteCache[serviceType] = callSite;
|
||||
|
||||
return callSite;
|
||||
}
|
||||
#if NETCOREAPP2_0
|
||||
return _callSiteCache.GetOrAdd(serviceType, (type, chain) => CreateCallSite(type, chain), callSiteChain);
|
||||
#else
|
||||
return _callSiteCache.GetOrAdd(serviceType, type => CreateCallSite(type, callSiteChain));
|
||||
#endif
|
||||
}
|
||||
|
||||
private IServiceCallSite TryCreateExact(Type serviceType, CallSiteChain callSiteChain)
|
||||
private ServiceCallSite CreateCallSite(Type serviceType, CallSiteChain callSiteChain)
|
||||
{
|
||||
if (!_stackGuard.TryEnterOnCurrentStack())
|
||||
{
|
||||
return _stackGuard.RunOnEmptyStack((type, chain) => CreateCallSite(type, chain), serviceType, callSiteChain);
|
||||
}
|
||||
|
||||
ServiceCallSite callSite;
|
||||
try
|
||||
{
|
||||
callSiteChain.CheckCircularDependency(serviceType);
|
||||
|
||||
callSite = TryCreateExact(serviceType, callSiteChain) ??
|
||||
TryCreateOpenGeneric(serviceType, callSiteChain) ??
|
||||
TryCreateEnumerable(serviceType, callSiteChain);
|
||||
}
|
||||
finally
|
||||
{
|
||||
callSiteChain.Remove(serviceType);
|
||||
}
|
||||
|
||||
_callSiteCache[serviceType] = callSite;
|
||||
|
||||
return callSite;
|
||||
}
|
||||
|
||||
private ServiceCallSite TryCreateExact(Type serviceType, CallSiteChain callSiteChain)
|
||||
{
|
||||
if (_descriptorLookup.TryGetValue(serviceType, out var descriptor))
|
||||
{
|
||||
return TryCreateExact(descriptor.Last, serviceType, callSiteChain);
|
||||
return TryCreateExact(descriptor.Last, serviceType, callSiteChain, DefaultSlot);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private IServiceCallSite TryCreateOpenGeneric(Type serviceType, CallSiteChain callSiteChain)
|
||||
private ServiceCallSite TryCreateOpenGeneric(Type serviceType, CallSiteChain callSiteChain)
|
||||
{
|
||||
if (serviceType.IsConstructedGenericType
|
||||
&& _descriptorLookup.TryGetValue(serviceType.GetGenericTypeDefinition(), out var descriptor))
|
||||
{
|
||||
return TryCreateOpenGeneric(descriptor.Last, serviceType, callSiteChain);
|
||||
return TryCreateOpenGeneric(descriptor.Last, serviceType, callSiteChain, DefaultSlot);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private IServiceCallSite TryCreateEnumerable(Type serviceType, CallSiteChain callSiteChain)
|
||||
private ServiceCallSite TryCreateEnumerable(Type serviceType, CallSiteChain callSiteChain)
|
||||
{
|
||||
if (serviceType.IsConstructedGenericType &&
|
||||
serviceType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
|
||||
|
@ -124,7 +135,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
var itemType = serviceType.GenericTypeArguments.Single();
|
||||
callSiteChain.Add(serviceType);
|
||||
|
||||
var callSites = new List<IServiceCallSite>();
|
||||
var callSites = new List<ServiceCallSite>();
|
||||
|
||||
// If item type is not generic we can safely use descriptor cache
|
||||
if (!itemType.IsConstructedGenericType &&
|
||||
|
@ -134,8 +145,10 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
{
|
||||
var descriptor = descriptors[i];
|
||||
|
||||
// Last service should get slot 0
|
||||
var slot = descriptors.Count - i - 1;
|
||||
// There may not be any open generics here
|
||||
var callSite = TryCreateExact(descriptor, itemType, callSiteChain);
|
||||
var callSite = TryCreateExact(descriptor, itemType, callSiteChain, slot);
|
||||
Debug.Assert(callSite != null);
|
||||
|
||||
callSites.Add(callSite);
|
||||
|
@ -143,17 +156,21 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
}
|
||||
else
|
||||
{
|
||||
foreach (var descriptor in _descriptors)
|
||||
var slot = 0;
|
||||
// We are going in reverse so the last service in descriptor list gets slot 0
|
||||
for (var i = _descriptors.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var callSite = TryCreateExact(descriptor, itemType, callSiteChain) ??
|
||||
TryCreateOpenGeneric(descriptor, itemType, callSiteChain);
|
||||
|
||||
var descriptor = _descriptors[i];
|
||||
var callSite = TryCreateExact(descriptor, itemType, callSiteChain, slot) ??
|
||||
TryCreateOpenGeneric(descriptor, itemType, callSiteChain, slot);
|
||||
slot++;
|
||||
if (callSite != null)
|
||||
{
|
||||
callSites.Add(callSite);
|
||||
}
|
||||
}
|
||||
|
||||
callSites.Reverse();
|
||||
}
|
||||
|
||||
return new IEnumerableCallSite(itemType, callSites.ToArray());
|
||||
|
@ -162,71 +179,51 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
return null;
|
||||
}
|
||||
|
||||
private IServiceCallSite TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain)
|
||||
private ServiceCallSite TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, int slot)
|
||||
{
|
||||
if (serviceType == descriptor.ServiceType)
|
||||
{
|
||||
IServiceCallSite callSite;
|
||||
ServiceCallSite callSite;
|
||||
var lifetime = new ResultCache(descriptor.Lifetime, serviceType, slot);
|
||||
if (descriptor.ImplementationInstance != null)
|
||||
{
|
||||
callSite = new ConstantCallSite(descriptor.ServiceType, descriptor.ImplementationInstance);
|
||||
}
|
||||
else if (descriptor.ImplementationFactory != null)
|
||||
{
|
||||
callSite = new FactoryCallSite(descriptor.ServiceType, descriptor.ImplementationFactory);
|
||||
callSite = new FactoryCallSite(lifetime, descriptor.ServiceType, descriptor.ImplementationFactory);
|
||||
}
|
||||
else if (descriptor.ImplementationType != null)
|
||||
{
|
||||
callSite = CreateConstructorCallSite(descriptor.ServiceType, descriptor.ImplementationType, callSiteChain);
|
||||
callSite = CreateConstructorCallSite(lifetime, descriptor.ServiceType, descriptor.ImplementationType, callSiteChain);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Invalid service descriptor");
|
||||
}
|
||||
|
||||
return ApplyLifetime(callSite, descriptor, descriptor.Lifetime);
|
||||
return callSite;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private IServiceCallSite TryCreateOpenGeneric(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain)
|
||||
private ServiceCallSite TryCreateOpenGeneric(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, int slot)
|
||||
{
|
||||
if (serviceType.IsConstructedGenericType &&
|
||||
serviceType.GetGenericTypeDefinition() == descriptor.ServiceType)
|
||||
{
|
||||
Debug.Assert(descriptor.ImplementationType != null, "descriptor.ImplementationType != null");
|
||||
|
||||
var lifetime = new ResultCache(descriptor.Lifetime, serviceType, slot);
|
||||
var closedType = descriptor.ImplementationType.MakeGenericType(serviceType.GenericTypeArguments);
|
||||
var constructorCallSite = CreateConstructorCallSite(serviceType, closedType, callSiteChain);
|
||||
|
||||
return ApplyLifetime(constructorCallSite, Tuple.Create(descriptor, serviceType), descriptor.Lifetime);
|
||||
return CreateConstructorCallSite(lifetime, serviceType, closedType, callSiteChain);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private IServiceCallSite ApplyLifetime(IServiceCallSite serviceCallSite, object cacheKey, ServiceLifetime descriptorLifetime)
|
||||
{
|
||||
if (serviceCallSite is ConstantCallSite)
|
||||
{
|
||||
return serviceCallSite;
|
||||
}
|
||||
|
||||
switch (descriptorLifetime)
|
||||
{
|
||||
case ServiceLifetime.Transient:
|
||||
return new TransientCallSite(serviceCallSite);
|
||||
case ServiceLifetime.Scoped:
|
||||
return new ScopedCallSite(serviceCallSite, cacheKey);
|
||||
case ServiceLifetime.Singleton:
|
||||
return new SingletonCallSite(serviceCallSite, cacheKey);
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(descriptorLifetime));
|
||||
}
|
||||
}
|
||||
|
||||
private IServiceCallSite CreateConstructorCallSite(Type serviceType, Type implementationType, CallSiteChain callSiteChain)
|
||||
private ServiceCallSite CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType,
|
||||
CallSiteChain callSiteChain)
|
||||
{
|
||||
callSiteChain.Add(serviceType, implementationType);
|
||||
|
||||
|
@ -235,7 +232,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
.Where(constructor => constructor.IsPublic)
|
||||
.ToArray();
|
||||
|
||||
IServiceCallSite[] parameterCallSites = null;
|
||||
ServiceCallSite[] parameterCallSites = null;
|
||||
|
||||
if (constructors.Length == 0)
|
||||
{
|
||||
|
@ -247,7 +244,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
var parameters = constructor.GetParameters();
|
||||
if (parameters.Length == 0)
|
||||
{
|
||||
return new CreateInstanceCallSite(serviceType, implementationType);
|
||||
return new ConstructorCallSite(lifetime, serviceType, constructor);
|
||||
}
|
||||
|
||||
parameterCallSites = CreateArgumentCallSites(
|
||||
|
@ -257,7 +254,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
parameters,
|
||||
throwIfCallSiteNotFound: true);
|
||||
|
||||
return new ConstructorCallSite(serviceType, constructor, parameterCallSites);
|
||||
return new ConstructorCallSite(lifetime, serviceType, constructor, parameterCallSites);
|
||||
}
|
||||
|
||||
Array.Sort(constructors,
|
||||
|
@ -316,23 +313,21 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
else
|
||||
{
|
||||
Debug.Assert(parameterCallSites != null);
|
||||
return parameterCallSites.Length == 0 ?
|
||||
(IServiceCallSite)new CreateInstanceCallSite(serviceType, implementationType) :
|
||||
new ConstructorCallSite(serviceType, bestConstructor, parameterCallSites);
|
||||
return new ConstructorCallSite(lifetime, serviceType, bestConstructor, parameterCallSites);
|
||||
}
|
||||
}
|
||||
|
||||
private IServiceCallSite[] CreateArgumentCallSites(
|
||||
private ServiceCallSite[] CreateArgumentCallSites(
|
||||
Type serviceType,
|
||||
Type implementationType,
|
||||
CallSiteChain callSiteChain,
|
||||
ParameterInfo[] parameters,
|
||||
bool throwIfCallSiteNotFound)
|
||||
{
|
||||
var parameterCallSites = new IServiceCallSite[parameters.Length];
|
||||
var parameterCallSites = new ServiceCallSite[parameters.Length];
|
||||
for (var index = 0; index < parameters.Length; index++)
|
||||
{
|
||||
var callSite = CreateCallSite(parameters[index].ParameterType, callSiteChain);
|
||||
var callSite = GetCallSite(parameters[index].ParameterType, callSiteChain);
|
||||
|
||||
if (callSite == null && ParameterDefaultValue.TryGetDefaultValue(parameters[index], out var defaultValue))
|
||||
{
|
||||
|
@ -358,7 +353,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
}
|
||||
|
||||
|
||||
public void Add(Type type, IServiceCallSite serviceCallSite)
|
||||
public void Add(Type type, ServiceCallSite serviceCallSite)
|
||||
{
|
||||
_callSiteCache[type] = serviceCallSite;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
// 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.ServiceLookup
|
||||
{
|
||||
internal enum CallSiteResultCacheLocation
|
||||
{
|
||||
Root,
|
||||
Scope,
|
||||
Dispose,
|
||||
None
|
||||
}
|
||||
}
|
|
@ -1,27 +1,38 @@
|
|||
using System;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
||||
{
|
||||
internal class CallSiteRuntimeResolver : CallSiteVisitor<ServiceProviderEngineScope, object>
|
||||
internal sealed class CallSiteRuntimeResolver : CallSiteVisitor<RuntimeResolverContext, object>
|
||||
{
|
||||
public object Resolve(IServiceCallSite callSite, ServiceProviderEngineScope scope)
|
||||
public object Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
|
||||
{
|
||||
return VisitCallSite(callSite, scope);
|
||||
}
|
||||
|
||||
protected override object VisitTransient(TransientCallSite transientCallSite, ServiceProviderEngineScope scope)
|
||||
{
|
||||
return scope.CaptureDisposable(
|
||||
VisitCallSite(transientCallSite.ServiceCallSite, scope));
|
||||
}
|
||||
|
||||
protected override object VisitConstructor(ConstructorCallSite constructorCallSite, ServiceProviderEngineScope scope)
|
||||
{
|
||||
object[] parameterValues = new object[constructorCallSite.ParameterCallSites.Length];
|
||||
for (var index = 0; index < parameterValues.Length; index++)
|
||||
return VisitCallSite(callSite, new RuntimeResolverContext
|
||||
{
|
||||
parameterValues[index] = VisitCallSite(constructorCallSite.ParameterCallSites[index], scope);
|
||||
Scope = scope
|
||||
});
|
||||
}
|
||||
|
||||
protected override object VisitDisposeCache(ServiceCallSite transientCallSite, RuntimeResolverContext context)
|
||||
{
|
||||
return context.Scope.CaptureDisposable(VisitCallSiteMain(transientCallSite, context));
|
||||
}
|
||||
|
||||
protected override object VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
|
||||
{
|
||||
object[] parameterValues;
|
||||
if (constructorCallSite.ParameterCallSites.Length == 0)
|
||||
{
|
||||
parameterValues = Array.Empty<object>();
|
||||
}
|
||||
else
|
||||
{
|
||||
parameterValues = new object[constructorCallSite.ParameterCallSites.Length];
|
||||
for (var index = 0; index < parameterValues.Length; index++)
|
||||
{
|
||||
parameterValues[index] = VisitCallSite(constructorCallSite.ParameterCallSites[index], context);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
|
@ -36,55 +47,77 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
}
|
||||
}
|
||||
|
||||
protected override object VisitSingleton(SingletonCallSite singletonCallSite, ServiceProviderEngineScope scope)
|
||||
protected override object VisitRootCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
|
||||
{
|
||||
return VisitScoped(singletonCallSite, scope.Engine.Root);
|
||||
return VisitCache(singletonCallSite, context, context.Scope.Engine.Root, RuntimeResolverLock.Root);
|
||||
}
|
||||
|
||||
protected override object VisitScoped(ScopedCallSite scopedCallSite, ServiceProviderEngineScope scope)
|
||||
protected override object VisitScopeCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
|
||||
{
|
||||
lock (scope.ResolvedServices)
|
||||
// Check if we are in the situation where scoped service was promoted to singleton
|
||||
// and we need to lock the root
|
||||
var requiredScope = context.Scope == context.Scope.Engine.Root ?
|
||||
RuntimeResolverLock.Root :
|
||||
RuntimeResolverLock.Scope;
|
||||
|
||||
return VisitCache(singletonCallSite, context, context.Scope, requiredScope);
|
||||
}
|
||||
|
||||
private object VisitCache(ServiceCallSite scopedCallSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
|
||||
{
|
||||
bool lockTaken = false;
|
||||
var resolvedServices = serviceProviderEngine.ResolvedServices;
|
||||
|
||||
// Taking locks only once allows us to fork resolution process
|
||||
// on another thread without causing the deadlock because we
|
||||
// always know that we are going to wait the other thread to finish before
|
||||
// releasing the lock
|
||||
if ((context.AcquiredLocks & lockType) == 0)
|
||||
{
|
||||
if (!scope.ResolvedServices.TryGetValue(scopedCallSite.CacheKey, out var resolved))
|
||||
Monitor.Enter(resolvedServices, ref lockTaken);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!resolvedServices.TryGetValue(scopedCallSite.Cache.Key, out var resolved))
|
||||
{
|
||||
resolved = VisitCallSite(scopedCallSite.ServiceCallSite, scope);
|
||||
scope.CaptureDisposable(resolved);
|
||||
scope.ResolvedServices.Add(scopedCallSite.CacheKey, resolved);
|
||||
resolved = VisitCallSiteMain(scopedCallSite, new RuntimeResolverContext
|
||||
{
|
||||
Scope = serviceProviderEngine,
|
||||
AcquiredLocks = context.AcquiredLocks | lockType
|
||||
});
|
||||
|
||||
serviceProviderEngine.CaptureDisposable(resolved);
|
||||
resolvedServices.Add(scopedCallSite.Cache.Key, resolved);
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken)
|
||||
{
|
||||
Monitor.Exit(resolvedServices);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override object VisitConstant(ConstantCallSite constantCallSite, ServiceProviderEngineScope scope)
|
||||
protected override object VisitConstant(ConstantCallSite constantCallSite, RuntimeResolverContext context)
|
||||
{
|
||||
return constantCallSite.DefaultValue;
|
||||
}
|
||||
|
||||
protected override object VisitCreateInstance(CreateInstanceCallSite createInstanceCallSite, ServiceProviderEngineScope scope)
|
||||
protected override object VisitServiceProvider(ServiceProviderCallSite serviceProviderCallSite, RuntimeResolverContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Activator.CreateInstance(createInstanceCallSite.ImplementationType);
|
||||
}
|
||||
catch (Exception ex) when (ex.InnerException != null)
|
||||
{
|
||||
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
|
||||
// The above line will always throw, but the compiler requires we throw explicitly.
|
||||
throw;
|
||||
}
|
||||
return context.Scope;
|
||||
}
|
||||
|
||||
protected override object VisitServiceProvider(ServiceProviderCallSite serviceProviderCallSite, ServiceProviderEngineScope scope)
|
||||
protected override object VisitServiceScopeFactory(ServiceScopeFactoryCallSite serviceScopeFactoryCallSite, RuntimeResolverContext context)
|
||||
{
|
||||
return scope;
|
||||
return context.Scope.Engine;
|
||||
}
|
||||
|
||||
protected override object VisitServiceScopeFactory(ServiceScopeFactoryCallSite serviceScopeFactoryCallSite, ServiceProviderEngineScope scope)
|
||||
{
|
||||
return scope.Engine;
|
||||
}
|
||||
|
||||
protected override object VisitIEnumerable(IEnumerableCallSite enumerableCallSite, ServiceProviderEngineScope scope)
|
||||
protected override object VisitIEnumerable(IEnumerableCallSite enumerableCallSite, RuntimeResolverContext context)
|
||||
{
|
||||
var array = Array.CreateInstance(
|
||||
enumerableCallSite.ItemType,
|
||||
|
@ -92,15 +125,29 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
|
||||
for (var index = 0; index < enumerableCallSite.ServiceCallSites.Length; index++)
|
||||
{
|
||||
var value = VisitCallSite(enumerableCallSite.ServiceCallSites[index], scope);
|
||||
var value = VisitCallSite(enumerableCallSite.ServiceCallSites[index], context);
|
||||
array.SetValue(value, index);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
protected override object VisitFactory(FactoryCallSite factoryCallSite, ServiceProviderEngineScope scope)
|
||||
protected override object VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
|
||||
{
|
||||
return factoryCallSite.Factory(scope);
|
||||
return factoryCallSite.Factory(context.Scope);
|
||||
}
|
||||
}
|
||||
|
||||
internal struct RuntimeResolverContext
|
||||
{
|
||||
public ServiceProviderEngineScope Scope { get; set; }
|
||||
|
||||
public RuntimeResolverLock AcquiredLocks { get; set; }
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum RuntimeResolverLock
|
||||
{
|
||||
Scope = 1,
|
||||
Root = 2
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
// Keys are services being resolved via GetService, values - first scoped service in their call site tree
|
||||
private readonly ConcurrentDictionary<Type, Type> _scopedServices = new ConcurrentDictionary<Type, Type>();
|
||||
|
||||
public void ValidateCallSite(IServiceCallSite callSite)
|
||||
public void ValidateCallSite(ServiceCallSite callSite)
|
||||
{
|
||||
var scoped = VisitCallSite(callSite, default);
|
||||
if (scoped != null)
|
||||
|
@ -40,11 +40,6 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
}
|
||||
}
|
||||
|
||||
protected override Type VisitTransient(TransientCallSite transientCallSite, CallSiteValidatorState state)
|
||||
{
|
||||
return VisitCallSite(transientCallSite.ServiceCallSite, state);
|
||||
}
|
||||
|
||||
protected override Type VisitConstructor(ConstructorCallSite constructorCallSite, CallSiteValidatorState state)
|
||||
{
|
||||
Type result = null;
|
||||
|
@ -74,16 +69,16 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
return result;
|
||||
}
|
||||
|
||||
protected override Type VisitSingleton(SingletonCallSite singletonCallSite, CallSiteValidatorState state)
|
||||
protected override Type VisitRootCache(ServiceCallSite singletonCallSite, CallSiteValidatorState state)
|
||||
{
|
||||
state.Singleton = singletonCallSite;
|
||||
return VisitCallSite(singletonCallSite.ServiceCallSite, state);
|
||||
return VisitCallSiteMain(singletonCallSite, state);
|
||||
}
|
||||
|
||||
protected override Type VisitScoped(ScopedCallSite scopedCallSite, CallSiteValidatorState state)
|
||||
protected override Type VisitScopeCache(ServiceCallSite scopedCallSite, CallSiteValidatorState state)
|
||||
{
|
||||
// We are fine with having ServiceScopeService requested by singletons
|
||||
if (scopedCallSite.ServiceCallSite is ServiceScopeFactoryCallSite)
|
||||
if (scopedCallSite is ServiceScopeFactoryCallSite)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -97,14 +92,12 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
));
|
||||
}
|
||||
|
||||
VisitCallSite(scopedCallSite.ServiceCallSite, state);
|
||||
VisitCallSiteMain(scopedCallSite, state);
|
||||
return scopedCallSite.ServiceType;
|
||||
}
|
||||
|
||||
protected override Type VisitConstant(ConstantCallSite constantCallSite, CallSiteValidatorState state) => null;
|
||||
|
||||
protected override Type VisitCreateInstance(CreateInstanceCallSite createInstanceCallSite, CallSiteValidatorState state) => null;
|
||||
|
||||
protected override Type VisitServiceProvider(ServiceProviderCallSite serviceProviderCallSite, CallSiteValidatorState state) => null;
|
||||
|
||||
protected override Type VisitServiceScopeFactory(ServiceScopeFactoryCallSite serviceScopeFactoryCallSite, CallSiteValidatorState state) => null;
|
||||
|
@ -113,7 +106,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
|
||||
internal struct CallSiteValidatorState
|
||||
{
|
||||
public SingletonCallSite Singleton { get; set; }
|
||||
public ServiceCallSite Singleton { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,40 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
||||
{
|
||||
internal abstract class CallSiteVisitor<TArgument, TResult>
|
||||
{
|
||||
protected virtual TResult VisitCallSite(IServiceCallSite callSite, TArgument argument)
|
||||
private readonly StackGuard _stackGuard;
|
||||
|
||||
protected CallSiteVisitor()
|
||||
{
|
||||
_stackGuard = new StackGuard();
|
||||
}
|
||||
|
||||
protected virtual TResult VisitCallSite(ServiceCallSite callSite, TArgument argument)
|
||||
{
|
||||
if (!_stackGuard.TryEnterOnCurrentStack())
|
||||
{
|
||||
return _stackGuard.RunOnEmptyStack((c, a) => VisitCallSite(c, a), callSite, argument);
|
||||
}
|
||||
|
||||
switch (callSite.Cache.Location)
|
||||
{
|
||||
case CallSiteResultCacheLocation.Root:
|
||||
return VisitRootCache(callSite, argument);
|
||||
case CallSiteResultCacheLocation.Scope:
|
||||
return VisitScopeCache(callSite, argument);
|
||||
case CallSiteResultCacheLocation.Dispose:
|
||||
return VisitDisposeCache(callSite, argument);
|
||||
case CallSiteResultCacheLocation.None:
|
||||
return VisitNoCache(callSite, argument);
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual TResult VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
|
||||
{
|
||||
switch (callSite.Kind)
|
||||
{
|
||||
|
@ -14,16 +44,8 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
return VisitIEnumerable((IEnumerableCallSite)callSite, argument);
|
||||
case CallSiteKind.Constructor:
|
||||
return VisitConstructor((ConstructorCallSite)callSite, argument);
|
||||
case CallSiteKind.Transient:
|
||||
return VisitTransient((TransientCallSite)callSite, argument);
|
||||
case CallSiteKind.Singleton:
|
||||
return VisitSingleton((SingletonCallSite)callSite, argument);
|
||||
case CallSiteKind.Scope:
|
||||
return VisitScoped((ScopedCallSite)callSite, argument);
|
||||
case CallSiteKind.Constant:
|
||||
return VisitConstant((ConstantCallSite)callSite, argument);
|
||||
case CallSiteKind.CreateInstance:
|
||||
return VisitCreateInstance((CreateInstanceCallSite)callSite, argument);
|
||||
case CallSiteKind.ServiceProvider:
|
||||
return VisitServiceProvider((ServiceProviderCallSite)callSite, argument);
|
||||
case CallSiteKind.ServiceScopeFactory:
|
||||
|
@ -33,18 +55,30 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
}
|
||||
}
|
||||
|
||||
protected abstract TResult VisitTransient(TransientCallSite transientCallSite, TArgument argument);
|
||||
protected virtual TResult VisitNoCache(ServiceCallSite callSite, TArgument argument)
|
||||
{
|
||||
return VisitCallSiteMain(callSite, argument);
|
||||
}
|
||||
|
||||
protected virtual TResult VisitDisposeCache(ServiceCallSite callSite, TArgument argument)
|
||||
{
|
||||
return VisitCallSiteMain(callSite, argument);
|
||||
}
|
||||
|
||||
protected virtual TResult VisitRootCache(ServiceCallSite callSite, TArgument argument)
|
||||
{
|
||||
return VisitCallSiteMain(callSite, argument);
|
||||
}
|
||||
|
||||
protected virtual TResult VisitScopeCache(ServiceCallSite callSite, TArgument argument)
|
||||
{
|
||||
return VisitCallSiteMain(callSite, argument);
|
||||
}
|
||||
|
||||
protected abstract TResult VisitConstructor(ConstructorCallSite constructorCallSite, TArgument argument);
|
||||
|
||||
protected abstract TResult VisitSingleton(SingletonCallSite singletonCallSite, TArgument argument);
|
||||
|
||||
protected abstract TResult VisitScoped(ScopedCallSite scopedCallSite, TArgument argument);
|
||||
|
||||
protected abstract TResult VisitConstant(ConstantCallSite constantCallSite, TArgument argument);
|
||||
|
||||
protected abstract TResult VisitCreateInstance(CreateInstanceCallSite createInstanceCallSite, TArgument argument);
|
||||
|
||||
protected abstract TResult VisitServiceProvider(ServiceProviderCallSite serviceProviderCallSite, TArgument argument);
|
||||
|
||||
protected abstract TResult VisitServiceScopeFactory(ServiceScopeFactoryCallSite serviceScopeFactoryCallSite, TArgument argument);
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
ExpressionResolverBuilder = new ExpressionResolverBuilder(RuntimeResolver, this, Root);
|
||||
}
|
||||
|
||||
protected override Func<ServiceProviderEngineScope, object> RealizeService(IServiceCallSite callSite)
|
||||
protected override Func<ServiceProviderEngineScope, object> RealizeService(ServiceCallSite callSite)
|
||||
{
|
||||
var realizedService = ExpressionResolverBuilder.Build(callSite);
|
||||
RealizedServices[callSite.ServiceType] = realizedService;
|
||||
|
|
|
@ -5,17 +5,17 @@ using System;
|
|||
|
||||
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
||||
{
|
||||
internal class ConstantCallSite : IServiceCallSite
|
||||
internal class ConstantCallSite : ServiceCallSite
|
||||
{
|
||||
internal object DefaultValue { get; }
|
||||
|
||||
public ConstantCallSite(Type serviceType, object defaultValue)
|
||||
public ConstantCallSite(Type serviceType, object defaultValue): base(ResultCache.None)
|
||||
{
|
||||
DefaultValue = defaultValue;
|
||||
}
|
||||
|
||||
public Type ServiceType => DefaultValue.GetType();
|
||||
public Type ImplementationType => DefaultValue.GetType();
|
||||
public CallSiteKind Kind { get; } = CallSiteKind.Constant;
|
||||
public override Type ServiceType => DefaultValue.GetType();
|
||||
public override Type ImplementationType => DefaultValue.GetType();
|
||||
public override CallSiteKind Kind { get; } = CallSiteKind.Constant;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,21 +6,25 @@ using System.Reflection;
|
|||
|
||||
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
||||
{
|
||||
internal class ConstructorCallSite : IServiceCallSite
|
||||
internal class ConstructorCallSite : ServiceCallSite
|
||||
{
|
||||
internal ConstructorInfo ConstructorInfo { get; }
|
||||
internal IServiceCallSite[] ParameterCallSites { get; }
|
||||
internal ServiceCallSite[] ParameterCallSites { get; }
|
||||
|
||||
public ConstructorCallSite(Type serviceType, ConstructorInfo constructorInfo, IServiceCallSite[] parameterCallSites)
|
||||
public ConstructorCallSite(ResultCache cache, Type serviceType, ConstructorInfo constructorInfo) : this(cache, serviceType, constructorInfo, Array.Empty<ServiceCallSite>())
|
||||
{
|
||||
}
|
||||
|
||||
public ConstructorCallSite(ResultCache cache, Type serviceType, ConstructorInfo constructorInfo, ServiceCallSite[] parameterCallSites) : base(cache)
|
||||
{
|
||||
ServiceType = serviceType;
|
||||
ConstructorInfo = constructorInfo;
|
||||
ParameterCallSites = parameterCallSites;
|
||||
}
|
||||
|
||||
public Type ServiceType { get; }
|
||||
public override Type ServiceType { get; }
|
||||
|
||||
public Type ImplementationType => ConstructorInfo.DeclaringType;
|
||||
public CallSiteKind Kind { get; } = CallSiteKind.Constructor;
|
||||
public override Type ImplementationType => ConstructorInfo.DeclaringType;
|
||||
public override CallSiteKind Kind { get; } = CallSiteKind.Constructor;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,17 +6,4 @@ using System.Runtime.ExceptionServices;
|
|||
|
||||
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
||||
{
|
||||
internal class CreateInstanceCallSite : IServiceCallSite
|
||||
{
|
||||
public Type ServiceType { get; }
|
||||
|
||||
public Type ImplementationType { get; }
|
||||
public CallSiteKind Kind { get; } = CallSiteKind.CreateInstance;
|
||||
|
||||
public CreateInstanceCallSite(Type serviceType, Type implementationType)
|
||||
{
|
||||
ServiceType = serviceType;
|
||||
ImplementationType = implementationType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
{
|
||||
}
|
||||
|
||||
protected override Func<ServiceProviderEngineScope, object> RealizeService(IServiceCallSite callSite)
|
||||
protected override Func<ServiceProviderEngineScope, object> RealizeService(ServiceCallSite callSite)
|
||||
{
|
||||
var callCount = 0;
|
||||
return scope =>
|
||||
|
|
|
@ -14,18 +14,18 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
{
|
||||
internal static readonly MethodInfo InvokeFactoryMethodInfo = GetMethodInfo<Action<Func<IServiceProvider, object>, IServiceProvider>>((a, b) => a.Invoke(b));
|
||||
internal static readonly MethodInfo CaptureDisposableMethodInfo = GetMethodInfo<Func<ServiceProviderEngineScope, object, object>>((a, b) => a.CaptureDisposable(b));
|
||||
internal static readonly MethodInfo TryGetValueMethodInfo = GetMethodInfo<Func<IDictionary<object, object>, object, object, bool>>((a, b, c) => a.TryGetValue(b, out c));
|
||||
internal static readonly MethodInfo AddMethodInfo = GetMethodInfo<Action<IDictionary<object, object>, object, object>>((a, b, c) => a.Add(b, c));
|
||||
internal static readonly MethodInfo TryGetValueMethodInfo = GetMethodInfo<Func<IDictionary<ServiceCacheKey, object>, ServiceCacheKey, object, bool>>((a, b, c) => a.TryGetValue(b, out c));
|
||||
internal static readonly MethodInfo AddMethodInfo = GetMethodInfo<Action<IDictionary<ServiceCacheKey, object>, ServiceCacheKey, object>>((a, b, c) => a.Add(b, c));
|
||||
internal static readonly MethodInfo MonitorEnterMethodInfo = GetMethodInfo<Action<object, bool>>((lockObj, lockTaken) => Monitor.Enter(lockObj, ref lockTaken));
|
||||
internal static readonly MethodInfo MonitorExitMethodInfo = GetMethodInfo<Action<object>>(lockObj => Monitor.Exit(lockObj));
|
||||
internal static readonly MethodInfo CallSiteRuntimeResolverResolve =
|
||||
GetMethodInfo<Func<CallSiteRuntimeResolver, IServiceCallSite, ServiceProviderEngineScope, object>>((r, c, p) => r.Resolve(c, p));
|
||||
GetMethodInfo<Func<CallSiteRuntimeResolver, ServiceCallSite, ServiceProviderEngineScope, object>>((r, c, p) => r.Resolve(c, p));
|
||||
|
||||
internal static readonly MethodInfo ArrayEmptyMethodInfo = typeof(Array).GetMethod(nameof(Array.Empty));
|
||||
|
||||
private static readonly ParameterExpression ScopeParameter = Expression.Parameter(typeof(ServiceProviderEngineScope));
|
||||
|
||||
private static readonly ParameterExpression ResolvedServices = Expression.Variable(typeof(IDictionary<object, object>), ScopeParameter.Name + "resolvedServices");
|
||||
private static readonly ParameterExpression ResolvedServices = Expression.Variable(typeof(IDictionary<ServiceCacheKey, object>), ScopeParameter.Name + "resolvedServices");
|
||||
private static readonly BinaryExpression ResolvedServicesVariableAssignment =
|
||||
Expression.Assign(ResolvedServices,
|
||||
Expression.Property(ScopeParameter, nameof(ServiceProviderEngineScope.ResolvedServices)));
|
||||
|
@ -41,7 +41,8 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
|
||||
private readonly ServiceProviderEngineScope _rootScope;
|
||||
|
||||
public ExpressionResolverBuilder(CallSiteRuntimeResolver runtimeResolver, IServiceScopeFactory serviceScopeFactory, ServiceProviderEngineScope rootScope)
|
||||
public ExpressionResolverBuilder(CallSiteRuntimeResolver runtimeResolver, IServiceScopeFactory serviceScopeFactory, ServiceProviderEngineScope rootScope):
|
||||
base()
|
||||
{
|
||||
if (runtimeResolver == null)
|
||||
{
|
||||
|
@ -52,13 +53,13 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
_rootScope = rootScope;
|
||||
}
|
||||
|
||||
public Func<ServiceProviderEngineScope, object> Build(IServiceCallSite callSite)
|
||||
public Func<ServiceProviderEngineScope, object> Build(ServiceCallSite callSite)
|
||||
{
|
||||
if (callSite is SingletonCallSite singletonCallSite)
|
||||
if (callSite.Cache.Location == CallSiteResultCacheLocation.Root)
|
||||
{
|
||||
// If root call site is singleton we can return Func calling
|
||||
// _runtimeResolver.Resolve directly and avoid Expression generation
|
||||
if (TryResolveSingletonValue(singletonCallSite, out var value))
|
||||
if (TryResolveSingletonValue(callSite, out var value))
|
||||
{
|
||||
return scope => value;
|
||||
}
|
||||
|
@ -69,15 +70,15 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
return BuildExpression(callSite).Compile();
|
||||
}
|
||||
|
||||
private bool TryResolveSingletonValue(SingletonCallSite singletonCallSite, out object value)
|
||||
private bool TryResolveSingletonValue(ServiceCallSite singletonCallSite, out object value)
|
||||
{
|
||||
lock (_rootScope.ResolvedServices)
|
||||
{
|
||||
return _rootScope.ResolvedServices.TryGetValue(singletonCallSite.CacheKey, out value);
|
||||
return _rootScope.ResolvedServices.TryGetValue(singletonCallSite.Cache.Key, out value);
|
||||
}
|
||||
}
|
||||
|
||||
private Expression<Func<ServiceProviderEngineScope, object>> BuildExpression(IServiceCallSite callSite)
|
||||
private Expression<Func<ServiceProviderEngineScope, object>> BuildExpression(ServiceCallSite callSite)
|
||||
{
|
||||
var context = new CallSiteExpressionBuilderContext
|
||||
{
|
||||
|
@ -99,7 +100,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
return Expression.Lambda<Func<ServiceProviderEngineScope, object>>(serviceExpression, ScopeParameter);
|
||||
}
|
||||
|
||||
protected override Expression VisitSingleton(SingletonCallSite singletonCallSite, CallSiteExpressionBuilderContext context)
|
||||
protected override Expression VisitRootCache(ServiceCallSite singletonCallSite, CallSiteExpressionBuilderContext context)
|
||||
{
|
||||
if (TryResolveSingletonValue(singletonCallSite, out var value))
|
||||
{
|
||||
|
@ -109,7 +110,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
return Expression.Call(
|
||||
Expression.Constant(_runtimeResolver),
|
||||
CallSiteRuntimeResolverResolve,
|
||||
Expression.Constant(singletonCallSite, typeof(IServiceCallSite)),
|
||||
Expression.Constant(singletonCallSite, typeof(ServiceCallSite)),
|
||||
context.ScopeParameter);
|
||||
}
|
||||
|
||||
|
@ -118,11 +119,6 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
return Expression.Constant(constantCallSite.DefaultValue);
|
||||
}
|
||||
|
||||
protected override Expression VisitCreateInstance(CreateInstanceCallSite createInstanceCallSite, CallSiteExpressionBuilderContext context)
|
||||
{
|
||||
return Expression.New(createInstanceCallSite.ImplementationType);
|
||||
}
|
||||
|
||||
protected override Expression VisitServiceProvider(ServiceProviderCallSite serviceProviderCallSite, CallSiteExpressionBuilderContext context)
|
||||
{
|
||||
return context.ScopeParameter;
|
||||
|
@ -155,14 +151,14 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
callSite.ItemType)));
|
||||
}
|
||||
|
||||
protected override Expression VisitTransient(TransientCallSite callSite, CallSiteExpressionBuilderContext context)
|
||||
protected override Expression VisitDisposeCache(ServiceCallSite callSite, CallSiteExpressionBuilderContext context)
|
||||
{
|
||||
var implType = callSite.ServiceCallSite.ImplementationType;
|
||||
var implType = callSite.ImplementationType;
|
||||
// Elide calls to GetCaptureDisposable if the implementation type isn't disposable
|
||||
return TryCaptureDisposible(
|
||||
implType,
|
||||
context.ScopeParameter,
|
||||
VisitCallSite(callSite.ServiceCallSite, context));
|
||||
VisitCallSiteMain(callSite, context));
|
||||
}
|
||||
|
||||
private Expression TryCaptureDisposible(Type implType, ParameterExpression scope, Expression service)
|
||||
|
@ -180,10 +176,18 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
protected override Expression VisitConstructor(ConstructorCallSite callSite, CallSiteExpressionBuilderContext context)
|
||||
{
|
||||
var parameters = callSite.ConstructorInfo.GetParameters();
|
||||
var parameterExpressions = new Expression[callSite.ParameterCallSites.Length];
|
||||
for (int i = 0; i < parameterExpressions.Length; i++)
|
||||
Expression[] parameterExpressions;
|
||||
if (callSite.ParameterCallSites.Length == 0)
|
||||
{
|
||||
parameterExpressions[i] = Convert(VisitCallSite(callSite.ParameterCallSites[i], context), parameters[i].ParameterType);
|
||||
parameterExpressions = Array.Empty<Expression>();
|
||||
}
|
||||
else
|
||||
{
|
||||
parameterExpressions = new Expression[callSite.ParameterCallSites.Length];
|
||||
for (int i = 0; i < parameterExpressions.Length; i++)
|
||||
{
|
||||
parameterExpressions[i] = Convert(VisitCallSite(callSite.ParameterCallSites[i], context), parameters[i].ParameterType);
|
||||
}
|
||||
}
|
||||
return Expression.New(callSite.ConstructorInfo, parameterExpressions);
|
||||
}
|
||||
|
@ -199,17 +203,17 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
return Expression.Convert(expression, type);
|
||||
}
|
||||
|
||||
protected override Expression VisitScoped(ScopedCallSite callSite, CallSiteExpressionBuilderContext context)
|
||||
protected override Expression VisitScopeCache(ServiceCallSite callSite, CallSiteExpressionBuilderContext context)
|
||||
{
|
||||
return BuildScopedExpression(callSite, context, VisitCallSite(callSite.ServiceCallSite, context));
|
||||
return BuildScopedExpression(callSite, context, VisitCallSiteMain(callSite, context));
|
||||
}
|
||||
|
||||
// Move off the main stack
|
||||
private Expression BuildScopedExpression(ScopedCallSite callSite, CallSiteExpressionBuilderContext context, Expression service)
|
||||
private Expression BuildScopedExpression(ServiceCallSite callSite, CallSiteExpressionBuilderContext context, Expression service)
|
||||
{
|
||||
var keyExpression = Expression.Constant(
|
||||
callSite.CacheKey,
|
||||
typeof(object));
|
||||
callSite.Cache.Key,
|
||||
typeof(ServiceCacheKey));
|
||||
|
||||
var resolvedVariable = Expression.Variable(typeof(object), "resolved");
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
_expressionResolverBuilder = new ExpressionResolverBuilder(RuntimeResolver, this, Root);
|
||||
}
|
||||
|
||||
protected override Func<ServiceProviderEngineScope, object> RealizeService(IServiceCallSite callSite)
|
||||
protected override Func<ServiceProviderEngineScope, object> RealizeService(ServiceCallSite callSite)
|
||||
{
|
||||
var realizedService = _expressionResolverBuilder.Build(callSite);
|
||||
RealizedServices[callSite.ServiceType] = realizedService;
|
||||
|
|
|
@ -5,19 +5,19 @@ using System;
|
|||
|
||||
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
||||
{
|
||||
internal class FactoryCallSite : IServiceCallSite
|
||||
internal class FactoryCallSite : ServiceCallSite
|
||||
{
|
||||
public Func<IServiceProvider, object> Factory { get; }
|
||||
|
||||
public FactoryCallSite(Type serviceType, Func<IServiceProvider, object> factory)
|
||||
public FactoryCallSite(ResultCache cache, Type serviceType, Func<IServiceProvider, object> factory) : base(cache)
|
||||
{
|
||||
Factory = factory;
|
||||
ServiceType = serviceType;
|
||||
}
|
||||
|
||||
public Type ServiceType { get; }
|
||||
public Type ImplementationType => null;
|
||||
public override Type ServiceType { get; }
|
||||
public override Type ImplementationType => null;
|
||||
|
||||
public CallSiteKind Kind { get; } = CallSiteKind.Factory;
|
||||
public override CallSiteKind Kind { get; } = CallSiteKind.Factory;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,19 +6,19 @@ using System.Collections.Generic;
|
|||
|
||||
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
||||
{
|
||||
internal class IEnumerableCallSite : IServiceCallSite
|
||||
internal class IEnumerableCallSite : ServiceCallSite
|
||||
{
|
||||
internal Type ItemType { get; }
|
||||
internal IServiceCallSite[] ServiceCallSites { get; }
|
||||
internal ServiceCallSite[] ServiceCallSites { get; }
|
||||
|
||||
public IEnumerableCallSite(Type itemType, IServiceCallSite[] serviceCallSites)
|
||||
public IEnumerableCallSite(Type itemType, ServiceCallSite[] serviceCallSites) : base(ResultCache.None)
|
||||
{
|
||||
ItemType = itemType;
|
||||
ServiceCallSites = serviceCallSites;
|
||||
}
|
||||
|
||||
public Type ServiceType => typeof(IEnumerable<>).MakeGenericType(ItemType);
|
||||
public Type ImplementationType => ItemType.MakeArrayType();
|
||||
public CallSiteKind Kind { get; } = CallSiteKind.IEnumerable;
|
||||
public override Type ServiceType => typeof(IEnumerable<>).MakeGenericType(ItemType);
|
||||
public override Type ImplementationType => ItemType.MakeArrayType();
|
||||
public override CallSiteKind Kind { get; } = CallSiteKind.IEnumerable;
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
|
||||
internal static ILEmitCallSiteAnalyzer Instance { get; } = new ILEmitCallSiteAnalyzer();
|
||||
|
||||
protected override ILEmitCallSiteAnalysisResult VisitTransient(TransientCallSite transientCallSite, object argument) => VisitCallSite(transientCallSite.ServiceCallSite, argument);
|
||||
protected override ILEmitCallSiteAnalysisResult VisitDisposeCache(ServiceCallSite transientCallSite, object argument) => VisitCallSiteMain(transientCallSite, argument);
|
||||
|
||||
protected override ILEmitCallSiteAnalysisResult VisitConstructor(ConstructorCallSite constructorCallSite, object argument)
|
||||
{
|
||||
|
@ -32,17 +32,15 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
return result;
|
||||
}
|
||||
|
||||
protected override ILEmitCallSiteAnalysisResult VisitSingleton(SingletonCallSite singletonCallSite, object argument) => VisitCallSite(singletonCallSite.ServiceCallSite, argument);
|
||||
protected override ILEmitCallSiteAnalysisResult VisitRootCache(ServiceCallSite singletonCallSite, object argument) => VisitCallSiteMain(singletonCallSite, argument);
|
||||
|
||||
protected override ILEmitCallSiteAnalysisResult VisitScoped(ScopedCallSite scopedCallSite, object argument)
|
||||
protected override ILEmitCallSiteAnalysisResult VisitScopeCache(ServiceCallSite scopedCallSite, object argument)
|
||||
{
|
||||
return new ILEmitCallSiteAnalysisResult(ScopedILSize, hasScope: true).Add(VisitCallSite(scopedCallSite.ServiceCallSite, argument));
|
||||
return new ILEmitCallSiteAnalysisResult(ScopedILSize, hasScope: true).Add(VisitCallSiteMain(scopedCallSite, argument));
|
||||
}
|
||||
|
||||
protected override ILEmitCallSiteAnalysisResult VisitConstant(ConstantCallSite constantCallSite, object argument) => new ILEmitCallSiteAnalysisResult(ConstantILSize);
|
||||
|
||||
protected override ILEmitCallSiteAnalysisResult VisitCreateInstance(CreateInstanceCallSite createInstanceCallSite, object argument) => new ILEmitCallSiteAnalysisResult(ConstructorILSize);
|
||||
|
||||
protected override ILEmitCallSiteAnalysisResult VisitServiceProvider(ServiceProviderCallSite serviceProviderCallSite, object argument) => new ILEmitCallSiteAnalysisResult(ServiceProviderSize);
|
||||
|
||||
protected override ILEmitCallSiteAnalysisResult VisitServiceScopeFactory(ServiceScopeFactoryCallSite serviceScopeFactoryCallSite, object argument) => new ILEmitCallSiteAnalysisResult(ConstantILSize);
|
||||
|
@ -59,6 +57,6 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
|
||||
protected override ILEmitCallSiteAnalysisResult VisitFactory(FactoryCallSite factoryCallSite, object argument) => new ILEmitCallSiteAnalysisResult(FactoryILSize);
|
||||
|
||||
public ILEmitCallSiteAnalysisResult CollectGenerationInfo(IServiceCallSite callSite) => VisitCallSite(callSite, null);
|
||||
public ILEmitCallSiteAnalysisResult CollectGenerationInfo(ServiceCallSite callSite) => VisitCallSite(callSite, null);
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
|
@ -19,6 +20,9 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
private static readonly FieldInfo RootField = typeof(ILEmitResolverBuilderRuntimeContext).GetField(nameof(ILEmitResolverBuilderRuntimeContext.Root));
|
||||
private static readonly FieldInfo FactoriesField = typeof(ILEmitResolverBuilderRuntimeContext).GetField(nameof(ILEmitResolverBuilderRuntimeContext.Factories));
|
||||
private static readonly FieldInfo ConstantsField = typeof(ILEmitResolverBuilderRuntimeContext).GetField(nameof(ILEmitResolverBuilderRuntimeContext.Constants));
|
||||
private static readonly MethodInfo GetTypeFromHandleMethod = typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle));
|
||||
|
||||
private static readonly ConstructorInfo CacheKeyCtor = typeof(ServiceCacheKey).GetConstructors().First();
|
||||
|
||||
private class ILEmitResolverBuilderRuntimeContext
|
||||
{
|
||||
|
@ -35,7 +39,8 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
|
||||
private readonly ServiceProviderEngineScope _rootScope;
|
||||
|
||||
public ILEmitResolverBuilder(CallSiteRuntimeResolver runtimeResolver, IServiceScopeFactory serviceScopeFactory, ServiceProviderEngineScope rootScope)
|
||||
public ILEmitResolverBuilder(CallSiteRuntimeResolver runtimeResolver, IServiceScopeFactory serviceScopeFactory, ServiceProviderEngineScope rootScope) :
|
||||
base()
|
||||
{
|
||||
if (runtimeResolver == null)
|
||||
{
|
||||
|
@ -46,13 +51,13 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
_rootScope = rootScope;
|
||||
}
|
||||
|
||||
public Func<ServiceProviderEngineScope, object> Build(IServiceCallSite callSite)
|
||||
public Func<ServiceProviderEngineScope, object> Build(ServiceCallSite callSite)
|
||||
{
|
||||
if (callSite is SingletonCallSite singletonCallSite)
|
||||
if (callSite.Cache.Location == CallSiteResultCacheLocation.Root)
|
||||
{
|
||||
// If root call site is singleton we can return Func calling
|
||||
// _runtimeResolver.Resolve directly and avoid Expression generation
|
||||
if (TryResolveSingletonValue(singletonCallSite, out var value))
|
||||
if (TryResolveSingletonValue(callSite, out var value))
|
||||
{
|
||||
return scope => value;
|
||||
}
|
||||
|
@ -63,12 +68,12 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
return BuildType(callSite);
|
||||
}
|
||||
|
||||
protected override Expression VisitTransient(TransientCallSite transientCallSite, ILEmitResolverBuilderContext argument)
|
||||
protected override Expression VisitDisposeCache(ServiceCallSite transientCallSite, ILEmitResolverBuilderContext argument)
|
||||
{
|
||||
// RuntimeScope.CaptureDisposables([create value])
|
||||
var shouldCapture = BeginCaptureDisposable(transientCallSite.ServiceCallSite.ImplementationType, argument);
|
||||
var shouldCapture = BeginCaptureDisposable(transientCallSite.ImplementationType, argument);
|
||||
|
||||
VisitCallSite(transientCallSite.ServiceCallSite, argument);
|
||||
VisitCallSiteMain(transientCallSite, argument);
|
||||
|
||||
if (shouldCapture)
|
||||
{
|
||||
|
@ -88,9 +93,9 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
return null;
|
||||
}
|
||||
|
||||
protected override Expression VisitSingleton(SingletonCallSite singletonCallSite, ILEmitResolverBuilderContext argument)
|
||||
protected override Expression VisitRootCache(ServiceCallSite callSite, ILEmitResolverBuilderContext argument)
|
||||
{
|
||||
if (TryResolveSingletonValue(singletonCallSite, out var value))
|
||||
if (TryResolveSingletonValue(callSite, out var value))
|
||||
{
|
||||
AddConstant(argument, value);
|
||||
return null;
|
||||
|
@ -101,7 +106,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
argument.Generator.Emit(OpCodes.Ldarg_0);
|
||||
argument.Generator.Emit(OpCodes.Ldfld, RuntimeResolverField);
|
||||
|
||||
AddConstant(argument, singletonCallSite);
|
||||
AddConstant(argument, callSite);
|
||||
|
||||
argument.Generator.Emit(OpCodes.Ldarg_0);
|
||||
argument.Generator.Emit(OpCodes.Ldfld, RootField);
|
||||
|
@ -110,7 +115,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
return null;
|
||||
}
|
||||
|
||||
protected override Expression VisitScoped(ScopedCallSite scopedCallSite, ILEmitResolverBuilderContext argument)
|
||||
protected override Expression VisitScopeCache(ServiceCallSite scopedCallSite, ILEmitResolverBuilderContext argument)
|
||||
{
|
||||
|
||||
// var cacheKey = scopedCallSite.CacheKey;
|
||||
|
@ -125,13 +130,13 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
// }
|
||||
|
||||
var resultLocal = argument.Generator.DeclareLocal(scopedCallSite.ServiceType);
|
||||
var cacheKeyLocal = argument.Generator.DeclareLocal(typeof(object));
|
||||
var cacheKeyLocal = argument.Generator.DeclareLocal(typeof(ServiceCacheKey));
|
||||
var endLabel = argument.Generator.DefineLabel();
|
||||
|
||||
// Resolved services would be 0 local
|
||||
argument.Generator.Emit(OpCodes.Ldloc_0);
|
||||
|
||||
AddConstant(argument, scopedCallSite.CacheKey);
|
||||
AddCacheKey(argument, scopedCallSite.Cache.Key);
|
||||
// Duplicate cache key
|
||||
argument.Generator.Emit(OpCodes.Dup);
|
||||
// and store to local
|
||||
|
@ -145,9 +150,9 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
// Jump to create new if nothing in cache
|
||||
argument.Generator.Emit(OpCodes.Brtrue, endLabel);
|
||||
|
||||
var shouldCapture = BeginCaptureDisposable(scopedCallSite.ServiceCallSite.ImplementationType, argument);
|
||||
var shouldCapture = BeginCaptureDisposable(scopedCallSite.ImplementationType, argument);
|
||||
|
||||
VisitCallSite(scopedCallSite.ServiceCallSite, argument);
|
||||
VisitCallSiteMain(scopedCallSite, argument);
|
||||
|
||||
if (shouldCapture)
|
||||
{
|
||||
|
@ -178,13 +183,6 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
return null;
|
||||
}
|
||||
|
||||
protected override Expression VisitCreateInstance(CreateInstanceCallSite createInstanceCallSite, ILEmitResolverBuilderContext argument)
|
||||
{
|
||||
// new Type
|
||||
argument.Generator.Emit(OpCodes.Newobj, createInstanceCallSite.ImplementationType.GetConstructor(Type.EmptyTypes));
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override Expression VisitServiceProvider(ServiceProviderCallSite serviceProviderCallSite, ILEmitResolverBuilderContext argument)
|
||||
{
|
||||
// [return] ProviderScope
|
||||
|
@ -269,8 +267,17 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
argument.Constants.Add(value);
|
||||
}
|
||||
|
||||
private void AddCacheKey(ILEmitResolverBuilderContext argument, ServiceCacheKey key)
|
||||
{
|
||||
// new ServiceCacheKey(typeof(key.Type), key.Slot)
|
||||
argument.Generator.Emit(OpCodes.Ldtoken, key.Type);
|
||||
argument.Generator.Emit(OpCodes.Call, GetTypeFromHandleMethod);
|
||||
argument.Generator.Emit(OpCodes.Ldc_I4, key.Slot);
|
||||
argument.Generator.Emit(OpCodes.Newobj, CacheKeyCtor);
|
||||
}
|
||||
|
||||
private Func<ServiceProviderEngineScope, object> BuildType(IServiceCallSite callSite)
|
||||
|
||||
private Func<ServiceProviderEngineScope, object> BuildType(ServiceCallSite callSite)
|
||||
{
|
||||
// We need to skip visibility checks because services/constructors might be private
|
||||
var dynamicMethod = new DynamicMethod("ResolveService",
|
||||
|
@ -298,13 +305,13 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
|
||||
GenerateMethodBody(callSite, method.GetILGenerator(), info);
|
||||
type.CreateTypeInfo();
|
||||
assembly.Save(assemblyName+".dll");
|
||||
assembly.Save(assemblyName + ".dll");
|
||||
#endif
|
||||
|
||||
return (Func<ServiceProviderEngineScope, object>)dynamicMethod.CreateDelegate(typeof(Func<ServiceProviderEngineScope, object>), runtimeContext);
|
||||
}
|
||||
|
||||
private ILEmitResolverBuilderRuntimeContext GenerateMethodBody(IServiceCallSite callSite, ILGenerator generator, ILEmitCallSiteAnalysisResult info)
|
||||
private ILEmitResolverBuilderRuntimeContext GenerateMethodBody(ServiceCallSite callSite, ILGenerator generator, ILEmitCallSiteAnalysisResult info)
|
||||
{
|
||||
var context = new ILEmitResolverBuilderContext()
|
||||
{
|
||||
|
@ -384,11 +391,11 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
};
|
||||
}
|
||||
|
||||
private bool TryResolveSingletonValue(SingletonCallSite singletonCallSite, out object value)
|
||||
private bool TryResolveSingletonValue(ServiceCallSite callSite, out object value)
|
||||
{
|
||||
lock (_rootScope.ResolvedServices)
|
||||
{
|
||||
return _rootScope.ResolvedServices.TryGetValue(singletonCallSite.CacheKey, out value);
|
||||
return _rootScope.ResolvedServices.TryGetValue(callSite.Cache.Key, out value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
_expressionResolverBuilder = new ILEmitResolverBuilder(RuntimeResolver, this, Root);
|
||||
}
|
||||
|
||||
protected override Func<ServiceProviderEngineScope, object> RealizeService(IServiceCallSite callSite)
|
||||
protected override Func<ServiceProviderEngineScope, object> RealizeService(ServiceCallSite callSite)
|
||||
{
|
||||
var realizedService = _expressionResolverBuilder.Build(callSite);
|
||||
RealizedServices[callSite.ServiceType] = realizedService;
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
{
|
||||
internal interface IServiceProviderEngineCallback
|
||||
{
|
||||
void OnCreate(IServiceCallSite callSite);
|
||||
void OnCreate(ServiceCallSite callSite);
|
||||
void OnResolve(Type serviceType, IServiceScope scope);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
// 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 System.Diagnostics;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
||||
{
|
||||
internal struct ResultCache
|
||||
{
|
||||
public static ResultCache None { get; } = new ResultCache(CallSiteResultCacheLocation.None, ServiceCacheKey.Empty);
|
||||
|
||||
internal ResultCache(CallSiteResultCacheLocation lifetime, ServiceCacheKey cacheKey)
|
||||
{
|
||||
Location = lifetime;
|
||||
Key = cacheKey;
|
||||
}
|
||||
|
||||
public ResultCache(ServiceLifetime lifetime, Type type, int slot)
|
||||
{
|
||||
Debug.Assert(lifetime == ServiceLifetime.Transient || type != null);
|
||||
|
||||
switch (lifetime)
|
||||
{
|
||||
case ServiceLifetime.Singleton:
|
||||
Location = CallSiteResultCacheLocation.Root;
|
||||
break;
|
||||
case ServiceLifetime.Scoped:
|
||||
Location = CallSiteResultCacheLocation.Scope;
|
||||
break;
|
||||
case ServiceLifetime.Transient:
|
||||
Location = CallSiteResultCacheLocation.Dispose;
|
||||
break;
|
||||
default:
|
||||
Location = CallSiteResultCacheLocation.None;
|
||||
break;
|
||||
}
|
||||
Key = new ServiceCacheKey(type, slot);
|
||||
}
|
||||
|
||||
public CallSiteResultCacheLocation Location { get; set; }
|
||||
|
||||
public ServiceCacheKey Key { get; set; }
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
{
|
||||
}
|
||||
|
||||
protected override Func<ServiceProviderEngineScope, object> RealizeService(IServiceCallSite callSite)
|
||||
protected override Func<ServiceProviderEngineScope, object> RealizeService(ServiceCallSite callSite)
|
||||
{
|
||||
return scope =>
|
||||
{
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
||||
{
|
||||
internal class ScopedCallSite : IServiceCallSite
|
||||
{
|
||||
internal IServiceCallSite ServiceCallSite { get; }
|
||||
public object CacheKey { get; }
|
||||
|
||||
public ScopedCallSite(IServiceCallSite serviceCallSite, object cacheKey)
|
||||
{
|
||||
ServiceCallSite = serviceCallSite;
|
||||
CacheKey = cacheKey;
|
||||
}
|
||||
|
||||
public Type ServiceType => ServiceCallSite.ServiceType;
|
||||
public Type ImplementationType => ServiceCallSite.ImplementationType;
|
||||
public virtual CallSiteKind Kind { get; } = CallSiteKind.Scope;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
||||
{
|
||||
internal struct ServiceCacheKey: IEquatable<ServiceCacheKey>
|
||||
{
|
||||
public static ServiceCacheKey Empty { get; } = new ServiceCacheKey(null, 0);
|
||||
|
||||
/// <summary>
|
||||
/// Type of service being cached
|
||||
/// </summary>
|
||||
public Type Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Reverse index of the service when resolved in <code>IEnumerable<Type></code> where default instance gets slot 0.
|
||||
/// For example for service collection
|
||||
/// IService Impl1
|
||||
/// IService Impl2
|
||||
/// IService Impl3
|
||||
/// We would get the following cache keys:
|
||||
/// Impl1 2
|
||||
/// Impl2 1
|
||||
/// Impl3 0
|
||||
/// </summary>
|
||||
public int Slot { get; }
|
||||
|
||||
public ServiceCacheKey(Type type, int slot)
|
||||
{
|
||||
Type = type;
|
||||
Slot = slot;
|
||||
}
|
||||
|
||||
public bool Equals(ServiceCacheKey other)
|
||||
{
|
||||
return Type == other.Type && Slot == other.Slot;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (Type.GetHashCode() * 397) ^ Slot;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,10 +8,16 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
/// <summary>
|
||||
/// Summary description for IServiceCallSite
|
||||
/// </summary>
|
||||
internal interface IServiceCallSite
|
||||
internal abstract class ServiceCallSite
|
||||
{
|
||||
Type ServiceType { get; }
|
||||
Type ImplementationType { get; }
|
||||
CallSiteKind Kind { get; }
|
||||
protected ServiceCallSite(ResultCache cache)
|
||||
{
|
||||
Cache = cache;
|
||||
}
|
||||
|
||||
public abstract Type ServiceType { get; }
|
||||
public abstract Type ImplementationType { get; }
|
||||
public abstract CallSiteKind Kind { get; }
|
||||
public ResultCache Cache { get; }
|
||||
}
|
||||
}
|
|
@ -5,10 +5,14 @@ using System;
|
|||
|
||||
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
||||
{
|
||||
internal class ServiceProviderCallSite : IServiceCallSite
|
||||
internal class ServiceProviderCallSite : ServiceCallSite
|
||||
{
|
||||
public Type ServiceType { get; } = typeof(IServiceProvider);
|
||||
public Type ImplementationType { get; } = typeof(ServiceProvider);
|
||||
public CallSiteKind Kind { get; } = CallSiteKind.ServiceProvider;
|
||||
public ServiceProviderCallSite() : base(ResultCache.None)
|
||||
{
|
||||
}
|
||||
|
||||
public override Type ServiceType { get; } = typeof(IServiceProvider);
|
||||
public override Type ImplementationType { get; } = typeof(ServiceProvider);
|
||||
public override CallSiteKind Kind { get; } = CallSiteKind.ServiceProvider;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
|
||||
public object GetService(Type serviceType) => GetService(serviceType, Root);
|
||||
|
||||
protected abstract Func<ServiceProviderEngineScope, object> RealizeService(IServiceCallSite callSite);
|
||||
protected abstract Func<ServiceProviderEngineScope, object> RealizeService(ServiceCallSite callSite);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
@ -71,7 +71,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
|
||||
private Func<ServiceProviderEngineScope, object> CreateServiceAccessor(Type serviceType)
|
||||
{
|
||||
var callSite = CallSiteFactory.CreateCallSite(serviceType, new CallSiteChain());
|
||||
var callSite = CallSiteFactory.GetCallSite(serviceType, new CallSiteChain());
|
||||
if (callSite != null)
|
||||
{
|
||||
_callback?.OnCreate(callSite);
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
Engine = engine;
|
||||
}
|
||||
|
||||
internal Dictionary<object, object> ResolvedServices { get; } = new Dictionary<object, object>();
|
||||
internal Dictionary<ServiceCacheKey, object> ResolvedServices { get; } = new Dictionary<ServiceCacheKey, object>();
|
||||
|
||||
public ServiceProviderEngine Engine { get; }
|
||||
|
||||
|
|
|
@ -5,10 +5,14 @@ using System;
|
|||
|
||||
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
||||
{
|
||||
internal class ServiceScopeFactoryCallSite : IServiceCallSite
|
||||
internal class ServiceScopeFactoryCallSite : ServiceCallSite
|
||||
{
|
||||
public Type ServiceType { get; } = typeof(IServiceScopeFactory);
|
||||
public Type ImplementationType { get; } = typeof(ServiceProviderEngine);
|
||||
public CallSiteKind Kind { get; } = CallSiteKind.ServiceScopeFactory;
|
||||
public ServiceScopeFactoryCallSite() : base(ResultCache.None)
|
||||
{
|
||||
}
|
||||
|
||||
public override Type ServiceType { get; } = typeof(IServiceScopeFactory);
|
||||
public override Type ImplementationType { get; } = typeof(ServiceProviderEngine);
|
||||
public override CallSiteKind Kind { get; } = CallSiteKind.ServiceScopeFactory;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,4 @@
|
|||
|
||||
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
||||
{
|
||||
internal class SingletonCallSite : ScopedCallSite
|
||||
{
|
||||
public SingletonCallSite(IServiceCallSite serviceCallSite, object cacheKey) : base(serviceCallSite, cacheKey)
|
||||
{
|
||||
}
|
||||
|
||||
public override CallSiteKind Kind { get; } = CallSiteKind.Singleton;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
||||
{
|
||||
internal sealed class StackGuard
|
||||
{
|
||||
private const int MaxExecutionStackCount = 1024;
|
||||
|
||||
private int _executionStackCount;
|
||||
|
||||
|
||||
public bool TryEnterOnCurrentStack()
|
||||
{
|
||||
#if NETCOREAPP2_0
|
||||
if (RuntimeHelpers.TryEnsureSufficientExecutionStack())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
try
|
||||
{
|
||||
RuntimeHelpers.EnsureSufficientExecutionStack();
|
||||
return true;
|
||||
}
|
||||
catch (InsufficientExecutionStackException)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
if (_executionStackCount < MaxExecutionStackCount)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new InsufficientExecutionStackException();
|
||||
}
|
||||
|
||||
public TR RunOnEmptyStack<T1, T2, TR>(Func<T1, T2, TR> action, T1 arg1, T2 arg2)
|
||||
{
|
||||
return RunOnEmptyStackCore(s =>
|
||||
{
|
||||
var t = (Tuple<Func<T1, T2, TR>, T1, T2>)s;
|
||||
return t.Item1(t.Item2, t.Item3);
|
||||
}, Tuple.Create(action, arg1, arg2));
|
||||
}
|
||||
|
||||
private R RunOnEmptyStackCore<R>(Func<object, R> action, object state)
|
||||
{
|
||||
_executionStackCount++;
|
||||
|
||||
try
|
||||
{
|
||||
// Using default scheduler rather than picking up the current scheduler.
|
||||
Task<R> task = Task.Factory.StartNew(action, state, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
|
||||
|
||||
TaskAwaiter<R> awaiter = task.GetAwaiter();
|
||||
|
||||
// Avoid AsyncWaitHandle lazy allocation of ManualResetEvent in the rare case we finish quickly.
|
||||
if (!awaiter.IsCompleted)
|
||||
{
|
||||
// Task.Wait has the potential of inlining the task's execution on the current thread; avoid this.
|
||||
((IAsyncResult)task).AsyncWaitHandle.WaitOne();
|
||||
}
|
||||
|
||||
// Using awaiter here to unwrap AggregateException.
|
||||
return awaiter.GetResult();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_executionStackCount--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
||||
{
|
||||
internal class TransientCallSite : IServiceCallSite
|
||||
{
|
||||
internal IServiceCallSite ServiceCallSite { get; }
|
||||
|
||||
public TransientCallSite(IServiceCallSite serviceCallSite)
|
||||
{
|
||||
ServiceCallSite = serviceCallSite;
|
||||
}
|
||||
|
||||
public Type ServiceType => ServiceCallSite.ServiceType;
|
||||
public Type ImplementationType => ServiceCallSite.ImplementationType;
|
||||
public CallSiteKind Kind { get; } = CallSiteKind.Transient;
|
||||
}
|
||||
}
|
|
@ -55,7 +55,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
/// <inheritdoc />
|
||||
public void Dispose() => _engine.Dispose();
|
||||
|
||||
void IServiceProviderEngineCallback.OnCreate(IServiceCallSite callSite)
|
||||
void IServiceProviderEngineCallback.OnCreate(ServiceCallSite callSite)
|
||||
{
|
||||
_callSiteValidator.ValidateCallSite(callSite);
|
||||
}
|
||||
|
|
|
@ -83,8 +83,8 @@ namespace Microsoft.Extensions.DependencyInjection.Tests
|
|||
{
|
||||
var provider = new DynamicServiceProviderEngine(descriptors, null);
|
||||
|
||||
var callSite = provider.CallSiteFactory.CreateCallSite(serviceType, new CallSiteChain());
|
||||
var collectionCallSite = provider.CallSiteFactory.CreateCallSite(typeof(IEnumerable<>).MakeGenericType(serviceType), new CallSiteChain());
|
||||
var callSite = provider.CallSiteFactory.GetCallSite(serviceType, new CallSiteChain());
|
||||
var collectionCallSite = provider.CallSiteFactory.GetCallSite(typeof(IEnumerable<>).MakeGenericType(serviceType), new CallSiteChain());
|
||||
|
||||
var compiledCallSite = CompileCallSite(callSite, provider);
|
||||
var compiledCollectionCallSite = CompileCallSite(collectionCallSite, provider);
|
||||
|
@ -111,7 +111,7 @@ namespace Microsoft.Extensions.DependencyInjection.Tests
|
|||
descriptors.AddScoped<ServiceC>();
|
||||
|
||||
var provider = new DynamicServiceProviderEngine(descriptors, null);
|
||||
var callSite = provider.CallSiteFactory.CreateCallSite(typeof(ServiceC), new CallSiteChain());
|
||||
var callSite = provider.CallSiteFactory.GetCallSite(typeof(ServiceC), new CallSiteChain());
|
||||
var compiledCallSite = CompileCallSite(callSite, provider);
|
||||
|
||||
var serviceC = (ServiceC)compiledCallSite(provider.Root);
|
||||
|
@ -137,7 +137,7 @@ namespace Microsoft.Extensions.DependencyInjection.Tests
|
|||
{
|
||||
disposables.Add(obj);
|
||||
};
|
||||
var callSite = provider.CallSiteFactory.CreateCallSite(typeof(ServiceC), new CallSiteChain());
|
||||
var callSite = provider.CallSiteFactory.GetCallSite(typeof(ServiceC), new CallSiteChain());
|
||||
var compiledCallSite = CompileCallSite(callSite, provider);
|
||||
|
||||
var serviceC = (DisposableServiceC)compiledCallSite(provider.Root);
|
||||
|
@ -163,7 +163,7 @@ namespace Microsoft.Extensions.DependencyInjection.Tests
|
|||
{
|
||||
disposables.Add(obj);
|
||||
};
|
||||
var callSite = provider.CallSiteFactory.CreateCallSite(typeof(ServiceC), new CallSiteChain());
|
||||
var callSite = provider.CallSiteFactory.GetCallSite(typeof(ServiceC), new CallSiteChain());
|
||||
var compiledCallSite = CompileCallSite(callSite, provider);
|
||||
|
||||
var serviceC = (DisposableServiceC)compiledCallSite(provider.Root);
|
||||
|
@ -192,7 +192,7 @@ namespace Microsoft.Extensions.DependencyInjection.Tests
|
|||
{
|
||||
disposables.Add(obj);
|
||||
};
|
||||
var callSite = provider.CallSiteFactory.CreateCallSite(typeof(ServiceC), new CallSiteChain());
|
||||
var callSite = provider.CallSiteFactory.GetCallSite(typeof(ServiceC), new CallSiteChain());
|
||||
var compiledCallSite = CompileCallSite(callSite, provider);
|
||||
|
||||
var serviceC = (ServiceC)compiledCallSite(provider.Root);
|
||||
|
@ -217,7 +217,7 @@ namespace Microsoft.Extensions.DependencyInjection.Tests
|
|||
{
|
||||
disposables.Add(obj);
|
||||
};
|
||||
var callSite = provider.CallSiteFactory.CreateCallSite(typeof(ServiceD), new CallSiteChain());
|
||||
var callSite = provider.CallSiteFactory.GetCallSite(typeof(ServiceD), new CallSiteChain());
|
||||
var compiledCallSite = CompileCallSite(callSite, provider);
|
||||
|
||||
var serviceD = (ServiceD)compiledCallSite(provider.Root);
|
||||
|
@ -235,10 +235,10 @@ namespace Microsoft.Extensions.DependencyInjection.Tests
|
|||
|
||||
var provider = new DynamicServiceProviderEngine(descriptors, null);
|
||||
|
||||
var callSite1 = provider.CallSiteFactory.CreateCallSite(typeof(ClassWithThrowingEmptyCtor), new CallSiteChain());
|
||||
var callSite1 = provider.CallSiteFactory.GetCallSite(typeof(ClassWithThrowingEmptyCtor), new CallSiteChain());
|
||||
var compiledCallSite1 = CompileCallSite(callSite1, provider);
|
||||
|
||||
var callSite2 = provider.CallSiteFactory.CreateCallSite(typeof(ClassWithThrowingCtor), new CallSiteChain());
|
||||
var callSite2 = provider.CallSiteFactory.GetCallSite(typeof(ClassWithThrowingCtor), new CallSiteChain());
|
||||
var compiledCallSite2 = CompileCallSite(callSite2, provider);
|
||||
|
||||
var ex1 = Assert.Throws<Exception>(() => compiledCallSite1(provider.Root));
|
||||
|
@ -311,12 +311,12 @@ namespace Microsoft.Extensions.DependencyInjection.Tests
|
|||
}
|
||||
}
|
||||
|
||||
private static object Invoke(IServiceCallSite callSite, ServiceProviderEngine provider)
|
||||
private static object Invoke(ServiceCallSite callSite, ServiceProviderEngine provider)
|
||||
{
|
||||
return CallSiteRuntimeResolver.Resolve(callSite, provider.Root);
|
||||
}
|
||||
|
||||
private static Func<ServiceProviderEngineScope, object> CompileCallSite(IServiceCallSite callSite, ServiceProviderEngine engine)
|
||||
private static Func<ServiceProviderEngineScope, object> CompileCallSite(ServiceCallSite callSite, ServiceProviderEngine engine)
|
||||
{
|
||||
return new ExpressionResolverBuilder(CallSiteRuntimeResolver, engine, engine.Root).Build(callSite);
|
||||
}
|
||||
|
|
|
@ -42,8 +42,9 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
var callSite = callSiteFactory(type);
|
||||
|
||||
// Assert
|
||||
var transientCall = Assert.IsType<TransientCallSite>(callSite);
|
||||
Assert.IsType<CreateInstanceCallSite>(transientCall.ServiceCallSite);
|
||||
Assert.Equal(CallSiteResultCacheLocation.Dispose, callSite.Cache.Location);
|
||||
var ctroCallSite = Assert.IsType<ConstructorCallSite>(callSite);
|
||||
Assert.Empty(ctroCallSite.ParameterCallSites);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
@ -65,8 +66,8 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
var callSite = callSiteFactory(type);
|
||||
|
||||
// Assert
|
||||
var transientCall = Assert.IsType<TransientCallSite>(callSite);
|
||||
var constructorCallSite = Assert.IsType<ConstructorCallSite>(transientCall.ServiceCallSite);
|
||||
Assert.Equal(CallSiteResultCacheLocation.Dispose, callSite.Cache.Location);
|
||||
var constructorCallSite = Assert.IsType<ConstructorCallSite>(callSite);
|
||||
Assert.Equal(new[] { typeof(IFakeService) }, GetParameters(constructorCallSite));
|
||||
}
|
||||
|
||||
|
@ -86,8 +87,8 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
var callSite = callSiteFactory(type);
|
||||
|
||||
// Assert
|
||||
var transientCall = Assert.IsType<TransientCallSite>(callSite);
|
||||
var constructorCallSite = Assert.IsType<ConstructorCallSite>(transientCall.ServiceCallSite);
|
||||
Assert.Equal(CallSiteResultCacheLocation.Dispose, callSite.Cache.Location);
|
||||
var constructorCallSite = Assert.IsType<ConstructorCallSite>(callSite);
|
||||
Assert.Equal(
|
||||
new[] { typeof(IEnumerable<IFakeService>), typeof(IEnumerable<IFactoryService>) },
|
||||
GetParameters(constructorCallSite));
|
||||
|
@ -105,12 +106,13 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
var callSite = callSiteFactory(type);
|
||||
|
||||
// Assert
|
||||
var transientCall = Assert.IsType<TransientCallSite>(callSite);
|
||||
Assert.IsType<CreateInstanceCallSite>(transientCall.ServiceCallSite);
|
||||
Assert.Equal(CallSiteResultCacheLocation.Dispose, callSite.Cache.Location);
|
||||
var ctorCallSite = Assert.IsType<ConstructorCallSite>(callSite);
|
||||
Assert.Empty(ctorCallSite.ParameterCallSites);
|
||||
}
|
||||
|
||||
public static TheoryData CreateCallSite_PicksConstructorWithTheMostNumberOfResolvedParametersData =>
|
||||
new TheoryData<Type, Func<Type, object>, Type[]>
|
||||
new TheoryData<Type, Func<Type, ServiceCallSite>, Type[]>
|
||||
{
|
||||
{
|
||||
typeof(TypeWithSupersetConstructors),
|
||||
|
@ -192,17 +194,17 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
|
||||
[Theory]
|
||||
[MemberData(nameof(CreateCallSite_PicksConstructorWithTheMostNumberOfResolvedParametersData))]
|
||||
public void CreateCallSite_PicksConstructorWithTheMostNumberOfResolvedParameters(
|
||||
private void CreateCallSite_PicksConstructorWithTheMostNumberOfResolvedParameters(
|
||||
Type type,
|
||||
Func<Type, object> callSiteFactory,
|
||||
Func<Type, ServiceCallSite> callSiteFactory,
|
||||
Type[] expectedConstructorParameters)
|
||||
{
|
||||
// Act
|
||||
var callSite = callSiteFactory(type);
|
||||
|
||||
// Assert
|
||||
var transientCall = Assert.IsType<TransientCallSite>(callSite);
|
||||
var constructorCallSite = Assert.IsType<ConstructorCallSite>(transientCall.ServiceCallSite);
|
||||
Assert.Equal(CallSiteResultCacheLocation.Dispose, callSite.Cache.Location);
|
||||
var constructorCallSite = Assert.IsType<ConstructorCallSite>(callSite);
|
||||
Assert.Equal(expectedConstructorParameters, GetParameters(constructorCallSite));
|
||||
}
|
||||
|
||||
|
@ -235,8 +237,8 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
|
||||
[Theory]
|
||||
[MemberData(nameof(CreateCallSite_ConsidersConstructorsWithDefaultValuesData))]
|
||||
public void CreateCallSite_ConsidersConstructorsWithDefaultValues(
|
||||
Func<Type, object> callSiteFactory,
|
||||
private void CreateCallSite_ConsidersConstructorsWithDefaultValues(
|
||||
Func<Type, ServiceCallSite> callSiteFactory,
|
||||
Type[] expectedConstructorParameters)
|
||||
{
|
||||
// Arrange
|
||||
|
@ -246,8 +248,8 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
var callSite = callSiteFactory(type);
|
||||
|
||||
// Assert
|
||||
var transientCall = Assert.IsType<TransientCallSite>(callSite);
|
||||
var constructorCallSite = Assert.IsType<ConstructorCallSite>(transientCall.ServiceCallSite);
|
||||
Assert.Equal(CallSiteResultCacheLocation.Dispose, callSite.Cache.Location);
|
||||
var constructorCallSite = Assert.IsType<ConstructorCallSite>(callSite);
|
||||
Assert.Equal(expectedConstructorParameters, GetParameters(constructorCallSite));
|
||||
}
|
||||
|
||||
|
@ -398,7 +400,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
Assert.StartsWith(expectedMessage, ex.Message);
|
||||
}
|
||||
|
||||
private static Func<Type, object> GetCallSiteFactory(params ServiceDescriptor[] descriptors)
|
||||
private static Func<Type, ServiceCallSite> GetCallSiteFactory(params ServiceDescriptor[] descriptors)
|
||||
{
|
||||
var collection = new ServiceCollection();
|
||||
foreach (var descriptor in descriptors)
|
||||
|
@ -408,7 +410,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
|
|||
|
||||
var callSiteFactory = new CallSiteFactory(collection.ToArray());
|
||||
|
||||
return type => callSiteFactory.CreateCallSite(type, new CallSiteChain());
|
||||
return type => callSiteFactory.GetCallSite(type, new CallSiteChain());
|
||||
}
|
||||
|
||||
private static IEnumerable<Type> GetParameters(ConstructorCallSite constructorCallSite) =>
|
||||
|
|
|
@ -11,17 +11,10 @@ namespace Microsoft.Extensions.DependencyInjection.Tests
|
|||
public class ServiceProviderCompilationTest
|
||||
{
|
||||
[Theory]
|
||||
#if DEBUG
|
||||
[InlineData(ServiceProviderMode.Dynamic, typeof(I150))]
|
||||
[InlineData(ServiceProviderMode.Runtime, typeof(I150))]
|
||||
[InlineData(ServiceProviderMode.ILEmit, typeof(I150))]
|
||||
[InlineData(ServiceProviderMode.Expressions, typeof(I150))]
|
||||
#else
|
||||
[InlineData(ServiceProviderMode.Dynamic, typeof(I200))]
|
||||
[InlineData(ServiceProviderMode.Runtime, typeof(I200))]
|
||||
[InlineData(ServiceProviderMode.ILEmit, typeof(I200))]
|
||||
[InlineData(ServiceProviderMode.Expressions, typeof(I200))]
|
||||
#endif
|
||||
[InlineData(ServiceProviderMode.Dynamic, typeof(I999))]
|
||||
[InlineData(ServiceProviderMode.Runtime, typeof(I999))]
|
||||
[InlineData(ServiceProviderMode.ILEmit, typeof(I999))]
|
||||
[InlineData(ServiceProviderMode.Expressions, typeof(I999))]
|
||||
private async Task CompilesInLimitedStackSpace(ServiceProviderMode mode, Type serviceType)
|
||||
{
|
||||
// Arrange
|
||||
|
|
Загрузка…
Ссылка в новой задаче