//
/*
The MIT License (MIT)
Copyright (c) 2013-2020 Maksim Volkau
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#if !PCL && !NET35 && !NET40 && !NET403 && !NETSTANDARD1_0 && !NETSTANDARD1_1 && !NETSTANDARD1_2 && !NETCOREAPP1_0 && !NETCOREAPP1_1
#define SUPPORTS_FAST_EXPRESSION_COMPILER
#endif
#if !PCL && !NET35 && !NET40 && !NET403 && !NETSTANDARD1_0 && !NETSTANDARD1_1 && !NETSTANDARD1_2 && !NETSTANDARD1_3 && !NETSTANDARD1_4 && !NETSTANDARD1_5 && !NETSTANDARD1_6 && !NETCOREAPP1_0 && !NETCOREAPP1_1
#define SUPPORTS_ISERVICE_PROVIDER
#endif
#if !PCL && !NETSTANDARD1_0 && !NETSTANDARD1_1 && !NETSTANDARD1_2 && !NETSTANDARD1_3 && !NETSTANDARD1_4 && !NETSTANDARD1_5 && !NETSTANDARD1_6
#define SUPPORTS_SERIALIZABLE
#define SUPPORTS_STACK_TRACE
#define SUPPORTS_MANAGED_THREAD_ID
#endif
#if !PCL && !NETSTANDARD1_0 && !NETSTANDARD1_1 && !NETSTANDARD1_2 && !NETSTANDARD1_3 && !NETSTANDARD1_5 && !NET35 && !NET40 && !NET403 && !NET45 && !NET451 && !NET452
#define SUPPORTS_ASYNC_LOCAL
#endif
#if !PCL && !NETSTANDARD1_0 && !NETSTANDARD1_1 && !NETSTANDARD1_2 && !NET35 && !NET40 && !NET403
#define SUPPORTS_VARIANCE
#endif
#if !PCL && !NET35 && !NET40 && !NET403 && !NET45 && !NET451 && !NET452 && !NET46 && !NET461 && !NET462 && !NET47 && !NET471 && !NET472 && !NETSTANDARD1_0 && !NETSTANDARD1_1 && !NETSTANDARD1_2 && !NETSTANDARD1_3 && !NETSTANDARD1_4
#define SUPPORTS_EXPRESSION_COMPILE_WITH_PREFER_INTERPRETATION_PARAM
#endif
#if !PCL && !NET35 && !NET40 && !NET403
#define SUPPORTS_DELEGATE_METHOD
#endif
namespace DryIoc
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Diagnostics.CodeAnalysis; // for SuppressMessage
using System.Diagnostics; // for StackTrace
using System.Runtime.CompilerServices; // for MethodImplAttribute
using ImTools;
using static ImTools.ArrayTools;
using static System.Environment;
using ExprType = System.Linq.Expressions.ExpressionType;
#if SUPPORTS_FAST_EXPRESSION_COMPILER
using FastExpressionCompiler.LightExpression;
using static FastExpressionCompiler.LightExpression.Expression;
#else
using System.Linq.Expressions;
using static System.Linq.Expressions.Expression;
#endif
/// Inversion of control container
public sealed partial class Container : IContainer
{
/// Creates new container with default rules .
public Container() : this(Rules.Default, Ref.Of(Registry.Default), NewSingletonScope())
{
SetInitialFactoryID();
}
/// Creates new container, optionally providing to modify default container behavior.
/// (optional) Rules to modify container default resolution behavior.
/// If not specified, then will be used.
/// (optional) Scope context to use for scoped reuse.
public Container(Rules rules = null, IScopeContext scopeContext = null)
: this(rules ?? Rules.Default, Ref.Of(Registry.Default), NewSingletonScope(), scopeContext)
{
SetInitialFactoryID();
}
/// Creates new container with configured rules.
/// Allows to modify rules.
/// (optional) Scope context to use for .
public Container(Func configure, IScopeContext scopeContext = null)
: this(configure.ThrowIfNull()(Rules.Default) ?? Rules.Default, scopeContext)
{ }
private sealed class SinlgetonScopeName
{
public static readonly SinlgetonScopeName Name = new SinlgetonScopeName();
private SinlgetonScopeName() { }
public override string ToString() => nameof(SinlgetonScopeName);
}
/// Helper to create singleton scope
public static IScope NewSingletonScope() => new Scope(name: SinlgetonScopeName.Name);
/// Pretty prints the container info including the open scope details if any.
public override string ToString()
{
var s = _scopeContext == null ? "Container" : "Container with ambient ScopeContext " + _scopeContext;
var scope = CurrentScope;
s += scope == null ? " without Scope" : " with Scope " + scope;
if (Rules != Rules.Default)
s += NewLine + " with " + Rules;
if (IsDisposed)
{
s += " has been DISPOSED!" + NewLine;
if (_disposeStackTrace != null)
s += " Dispose stack-trace " + _disposeStackTrace;
else
s += " You may include Dispose stack-trace into the message via:" + NewLine +
"container.With(rules => rules.WithCaptureContainerDisposeStackTrace())";
}
return s;
}
/// Dispose either open scope, or container with singletons, if no scope opened.
public void Dispose()
{
// if already disposed - just leave
if (Interlocked.CompareExchange(ref _disposed, 1, 0) == 1)
return;
// Nice to have disposal stack-trace, but we can live without it if something goes wrong
if (Rules.CaptureContainerDisposeStackTrace)
try { _disposeStackTrace = new StackTrace(); }
catch { }
if (_parent != null)
{
if (_ownCurrentScope != null)
{
_ownCurrentScope.Dispose();
}
else if (_scopeContext != null)
{
IScope currentScope = null;
_scopeContext.SetCurrent(s =>
{
// save the current scope for the later,
// do dispose it AFTER its parent is actually set to be a new ambient current scope.
currentScope = s;
return s?.Parent;
});
currentScope?.Dispose();
}
}
else
{
_registry.Swap(Registry.Empty);
Rules = Rules.Default;
_singletonScope.Dispose(); // will also dispose any tracked scopes
_scopeContext?.Dispose();
}
}
#region Compile-time generated parts - former DryIocZero
partial void GetLastGeneratedFactoryID(ref int lastFactoryID);
partial void ResolveGenerated(ref object service, Type serviceType);
partial void ResolveGenerated(ref object service,
Type serviceType, object serviceKey, Type requiredServiceType, Request preRequestParent, object[] args);
partial void ResolveManyGenerated(ref IEnumerable services, Type serviceType);
/// Identifies the service when resolving collection
public struct ResolveManyResult
{
/// Factory, the required part
public FactoryDelegate FactoryDelegate;
/// Optional key
public object ServiceKey;
/// Optional required service type, can be an open-generic type.
public Type RequiredServiceType;
/// Constructs the struct.
public static ResolveManyResult Of(FactoryDelegate factoryDelegate,
object serviceKey = null, Type requiredServiceType = null) =>
new ResolveManyResult
{
FactoryDelegate = factoryDelegate,
ServiceKey = serviceKey,
RequiredServiceType = requiredServiceType
};
}
/// Directly uses generated factories to resolve service. Or returns the default if service does not have generated factory.
[SuppressMessage("ReSharper", "InvocationIsSkipped", Justification = "Per design")]
[SuppressMessage("ReSharper", "ExpressionIsAlwaysNull", Justification = "Per design")]
public object ResolveCompileTimeGeneratedOrDefault(Type serviceType)
{
object service = null;
ResolveGenerated(ref service, serviceType);
return service;
}
/// Directly uses generated factories to resolve service. Or returns the default if service does not have generated factory.
[SuppressMessage("ReSharper", "InvocationIsSkipped", Justification = "Per design")]
[SuppressMessage("ReSharper", "ExpressionIsAlwaysNull", Justification = "Per design")]
public object ResolveCompileTimeGeneratedOrDefault(Type serviceType, object serviceKey)
{
object service = null;
ResolveGenerated(ref service, serviceType, serviceKey,
requiredServiceType: null, preRequestParent: null, args: null);
return service;
}
/// Resolves many generated only services. Ignores runtime registrations.
public IEnumerable ResolveManyCompileTimeGeneratedOrEmpty(Type serviceType)
{
IEnumerable manyGenerated = ArrayTools.Empty();
ResolveManyGenerated(ref manyGenerated, serviceType);
return manyGenerated;
}
#endregion
#region IRegistrator
/// Returns all registered service factories with their Type and optional Key.
/// Decorator and Wrapper types are not included.
public IEnumerable GetServiceRegistrations() =>
_registry.Value.GetServiceRegistrations();
// todo: Make `serviceKey` and `factoryType` optional
/// Searches for registered factories by type, and key (if specified),
/// and by factory type (by default uses ).
/// May return empty, 1 or multiple factories.
public Factory[] GetRegisteredFactories(Type serviceType, object serviceKey, FactoryType factoryType) =>
_registry.Value.GetRegisteredFactories(serviceType.ThrowIfNull(), serviceKey, factoryType);
/// Stores factory into container using and as key
/// for later lookup.
/// Any subtypes of .
/// Type of service to resolve later.
/// (optional) Service key of any type with and
/// implemented.
/// (optional) Says how to handle existing registration with the same
/// and .
/// Confirms that service and implementation types are statically checked by compiler.
/// True if factory was added to registry, false otherwise.
/// False may be in case of setting and already existing factory.
public void Register(Factory factory, Type serviceType, object serviceKey, IfAlreadyRegistered? ifAlreadyRegistered, bool isStaticallyChecked)
{
ThrowIfContainerDisposed();
if (serviceKey == null)
serviceKey = Rules.DefaultRegistrationServiceKey;
factory.ThrowIfNull().ValidateAndNormalizeRegistration(serviceType, serviceKey, isStaticallyChecked, Rules);
if (!ifAlreadyRegistered.HasValue)
ifAlreadyRegistered = Rules.DefaultIfAlreadyRegistered;
// Improves performance a bit by first attempting to swap the registry while it is still unchanged.
var r = _registry.Value;
if (!_registry.TrySwapIfStillCurrent(r, r.Register(factory, serviceType, ifAlreadyRegistered.Value, serviceKey)))
RegistrySwap(factory, serviceType, serviceKey, ifAlreadyRegistered);
}
// hiding nested lambda in method to reduce allocations
private Registry RegistrySwap(Factory factory, Type serviceType, object serviceKey, IfAlreadyRegistered? ifAlreadyRegistered) =>
_registry.Swap(r => r.Register(factory, serviceType, ifAlreadyRegistered.Value, serviceKey));
///
public bool IsRegistered(Type serviceType, object serviceKey, FactoryType factoryType, Func condition)
{
ThrowIfContainerDisposed();
return _registry.Value.IsRegistered(serviceType, serviceKey, factoryType, condition);
}
///
public void Unregister(Type serviceType, object serviceKey, FactoryType factoryType, Func condition)
{
ThrowIfContainerDisposed();
_registry.Swap(r => r.Unregister(factoryType, serviceType, serviceKey, condition));
}
#endregion
#region IResolver
#if SUPPORTS_ISERVICE_PROVIDER
/// Resolves service with policy,
/// enabling the fallback resolution for not registered services (default MS convention)
object IServiceProvider.GetService(Type serviceType) =>
((IResolver)this).Resolve(serviceType, IfUnresolved.ReturnDefaultIfNotRegistered);
#endif
object IResolver.Resolve(Type serviceType, IfUnresolved ifUnresolved)
{
object service = null;
ResolveGenerated(ref service, serviceType);
if (service != null)
return service;
var serviceTypeHash = RuntimeHelpers.GetHashCode(serviceType);
var cacheEntry = _registry.Value.GetCachedDefaultFactoryOrDefault(serviceTypeHash, serviceType);
if (cacheEntry != null)
{
ref var entry = ref cacheEntry.Value;
if (entry.Value is FactoryDelegate cachedDelegate)
return cachedDelegate(this);
if (ResolverContext.TryGetUsedInstance(this, serviceType, out var usedInstance))
{
entry.Value = null; // reset the cache
return usedInstance;
}
var rules = Rules;
while (entry.Value is Expression expr)
{
if (rules.UseInterpretation &&
Interpreter.TryInterpretAndUnwrapContainerException(this, expr, false, out var result))
return result;
// set to Compiling to notify other threads to use the interpretation until the service is compiled
if (Interlocked.CompareExchange(ref entry.Value, new Registry.Compiling(expr), expr) == expr)
{
var compiledFactory = expr.CompileToFactoryDelegate(rules.UseFastExpressionCompiler, rules.UseInterpretation);
// todo: should we instead cache only after invoking the factory delegate
entry.Value = compiledFactory;
return compiledFactory(this);
}
}
if (entry.Value is Registry.Compiling compiling)
{
if (Interpreter.TryInterpretAndUnwrapContainerException(this, compiling.Expression, false, out var result))
return result;
return compiling.Expression.CompileToFactoryDelegate(rules.UseFastExpressionCompiler, rules.UseInterpretation)(this);
}
}
return ResolveAndCache(serviceTypeHash, serviceType, ifUnresolved);
}
private object ResolveAndCache(int serviceTypeHash, Type serviceType, IfUnresolved ifUnresolved)
{
ThrowIfContainerDisposed();
if (ResolverContext.TryGetUsedInstance(this, serviceType, out var usedInstance))
return usedInstance;
var request = Request.Create(this, serviceType, ifUnresolved: ifUnresolved);
var factory = ((IContainer)this).ResolveFactory(request); // HACK: may mutate request, but it should be safe
// Delegate to full blown Resolve aware of service key, open scope, etc.
var serviceKey = request.ServiceKey;
var scopeName = CurrentScope?.Name;
if (serviceKey != null || scopeName != null)
return ResolveAndCacheKeyed(serviceTypeHash, serviceType, serviceKey, ifUnresolved, scopeName, null, Request.Empty, null);
if (factory == null)
return null;
var rules = Rules;
FactoryDelegate factoryDelegate;
// todo: [Obsolete] - in v5.0 there should be no check nor the InstanceFactory
if (factory is InstanceFactory ||
!rules.UseInterpretationForTheFirstResolution)
{
factoryDelegate = factory.GetDelegateOrDefault(request);
if (factoryDelegate == null)
return null;
}
else
{
var expr = factory.GetExpressionOrDefault(request);
if (expr == null)
return null;
if (expr is ConstantExpression constExpr)
{
var value = constExpr.Value;
if (factory.Caching != FactoryCaching.DoNotCache)
_registry.Value.TryCacheDefaultFactory(serviceTypeHash, serviceType, value.ToFactoryDelegate);
return value;
}
// Important to cache expression first before tying to interpret,
// so that parallel resolutions may already use it and UseInstance may correctly evict the cache if needed.
if (factory.Caching != FactoryCaching.DoNotCache)
_registry.Value.TryCacheDefaultFactory(serviceTypeHash, serviceType, expr);
// 1) First try to interpret
if (Interpreter.TryInterpretAndUnwrapContainerException(this, expr, rules.UseFastExpressionCompiler, out var instance))
return instance;
// 2) Fallback to expression compilation
factoryDelegate = expr.CompileToFactoryDelegate(rules.UseFastExpressionCompiler, rules.UseInterpretation);
}
if (factory.Caching != FactoryCaching.DoNotCache)
_registry.Value.TryCacheDefaultFactory(serviceTypeHash, serviceType, factoryDelegate);
return factoryDelegate(this);
}
object IResolver.Resolve(Type serviceType, object serviceKey,
IfUnresolved ifUnresolved, Type requiredServiceType, Request preResolveParent, object[] args)
{
// fallback to simple Resolve and its default cache if no keys are passed
var scopeName = CurrentScope?.Name;
if (serviceKey == null && requiredServiceType == null && scopeName == null &&
(preResolveParent == null || preResolveParent.IsEmpty) && args.IsNullOrEmpty())
return ((IResolver)this).Resolve(serviceType, ifUnresolved);
return ResolveAndCacheKeyed(RuntimeHelpers.GetHashCode(serviceType), serviceType,
serviceKey, ifUnresolved, scopeName, requiredServiceType, preResolveParent ?? Request.Empty, args);
}
private object ResolveAndCacheKeyed(int serviceTypeHash, Type serviceType,
object serviceKey, IfUnresolved ifUnresolved, object scopeName, Type requiredServiceType, Request preResolveParent,
object[] args)
{
object service = null;
ResolveGenerated(ref service, serviceType, serviceKey, requiredServiceType, preResolveParent, args);
if (service != null)
return service;
object cacheKey = null;
if (requiredServiceType == null && preResolveParent.IsEmpty && args.IsNullOrEmpty())
{
cacheKey = scopeName == null ? serviceKey
: serviceKey == null ? scopeName
: KV.Of(scopeName, serviceKey);
if (_registry.Value.GetCachedKeyedFactoryOrDefault(serviceTypeHash, serviceType, cacheKey, out var cacheEntry))
{
if (cacheEntry.Factory is FactoryDelegate cachedDelegate)
return cachedDelegate(this);
if (TryInterpretOrCompileCachedExpression(this, cacheEntry, Rules, out var result))
return result;
}
}
// Cache is missed, so get the factory and put it into cache:
ThrowIfContainerDisposed();
var request = Request.Create(this, serviceType, serviceKey, ifUnresolved, requiredServiceType, preResolveParent, default, args);
var factory = ((IContainer)this).ResolveFactory(request);
if (factory == null)
return null;
// Prevents caching if factory says Don't
if (factory.Caching == FactoryCaching.DoNotCache)
cacheKey = null;
// Request service key may be changed when resolving the factory,
// so we need to look into Default cache again for the new key
if (cacheKey != null && serviceKey == null && request.ServiceKey != null)
{
cacheKey = scopeName == null ? request.ServiceKey : KV.Of(scopeName, request.ServiceKey);
if (_registry.Value.GetCachedKeyedFactoryOrDefault(serviceTypeHash, serviceType, cacheKey, out var cacheEntry))
{
if (cacheEntry.Factory is FactoryDelegate cachedDelegate)
return cachedDelegate(this);
if (TryInterpretOrCompileCachedExpression(this, cacheEntry, Rules, out var result))
return result;
}
}
FactoryDelegate factoryDelegate;
if (factory is InstanceFactory || !Rules.UseInterpretationForTheFirstResolution)
{
factoryDelegate = factory.GetDelegateOrDefault(request);
if (factoryDelegate == null)
return null;
}
else
{
var expr = factory.GetExpressionOrDefault(request);
if (expr == null)
return null;
if (expr is ConstantExpression constExpr)
{
var value = constExpr.Value;
if (cacheKey != null)
_registry.Value.TryCacheKeyedFactory(serviceTypeHash, serviceType, cacheKey, (FactoryDelegate)value.ToFactoryDelegate);
return value;
}
// Important to cache expression first before tying to interpret,
// so that parallel resolutions may already use it and UseInstance may correctly evict the cache if needed
if (cacheKey != null)
_registry.Value.TryCacheKeyedFactory(serviceTypeHash, serviceType, cacheKey, expr);
// 1) First try to interpret
var useFec = Rules.UseFastExpressionCompiler;
if (Interpreter.TryInterpretAndUnwrapContainerException(this, expr, useFec, out var instance))
return instance;
// 2) Fallback to expression compilation
factoryDelegate = expr.CompileToFactoryDelegate(useFec, Rules.UseInterpretation);
}
// Cache factory only when we successfully called the factory delegate, to prevent failing delegates to be cached.
// Additionally disable caching when no services registered, not to cache an empty collection wrapper or alike.
if (cacheKey != null)
_registry.Value.TryCacheKeyedFactory(serviceTypeHash, serviceType, cacheKey, factoryDelegate);
return factoryDelegate(this);
}
private static bool TryInterpretOrCompileCachedExpression(IResolverContext r,
Registry.KeyedFactoryCacheEntry cacheEntry, Rules rules, out object result)
{
while (cacheEntry.Factory is Expression expr)
{
if (rules.UseInterpretation &&
Interpreter.TryInterpretAndUnwrapContainerException(r, expr, false, out result))
return true;
// set to Compiling to notify other threads to use the interpretation until the service is compiled
if (Interlocked.CompareExchange(ref cacheEntry.Factory, new Registry.Compiling(expr), expr) == expr)
{
var factoryDelegate = expr.CompileToFactoryDelegate(rules.UseFastExpressionCompiler, rules.UseInterpretation);
// todo: should we instead cache only after invoking the factory delegate
cacheEntry.Factory = factoryDelegate;
result = factoryDelegate(r);
return true;
}
}
if (cacheEntry.Factory is Registry.Compiling compiling)
{
if (!Interpreter.TryInterpretAndUnwrapContainerException(r, compiling.Expression, false, out result))
result = compiling.Expression.CompileToFactoryDelegate(rules.UseFastExpressionCompiler, rules.UseInterpretation)(r);
return true;
}
result = null;
return false;
}
IEnumerable IResolver.ResolveMany(Type serviceType, object serviceKey,
Type requiredServiceType, Request preResolveParent, object[] args)
{
var requiredItemType = requiredServiceType ?? serviceType;
// first return compile-time generated factories if any
var generatedFactories = Enumerable.Empty();
ResolveManyGenerated(ref generatedFactories, serviceType);
if (serviceKey != null)
generatedFactories = generatedFactories.Where(x => serviceKey.Equals(x.ServiceKey));
if (requiredServiceType != null)
generatedFactories = generatedFactories.Where(x => requiredServiceType == x.RequiredServiceType);
foreach (var generated in generatedFactories)
yield return generated.FactoryDelegate(this);
// Emulating the collection parent so that collection related rules and conditions were applied
// the same way as if resolving IEnumerable
if (preResolveParent == null || preResolveParent.IsEmpty)
preResolveParent = Request.Empty.Push(
typeof(IEnumerable), requiredItemType, serviceKey, IfUnresolved.Throw,
WrappersSupport.CollectionWrapperID, FactoryType.Wrapper, null, null, 0, 0);
var container = (IContainer)this;
var unwrappedType = container.GetWrappedType(requiredItemType, null);
if (unwrappedType != null && unwrappedType != typeof(void)) // accounting for the resolved action GH#114
requiredItemType = unwrappedType;
var items = container.GetAllServiceFactories(requiredItemType).ToArrayOrSelf()
.Where(x => x.Value != null)
.Select(f => new ServiceRegistrationInfo(f.Value, requiredServiceType, f.Key));
IEnumerable openGenericItems = null;
if (requiredItemType.IsClosedGeneric())
{
var requiredItemOpenGenericType = requiredItemType.GetGenericDefinitionOrNull();
openGenericItems = container.GetAllServiceFactories(requiredItemOpenGenericType)
.Where(x => x.Value != null)
.Select(x => new ServiceRegistrationInfo(x.Value, requiredServiceType,
new OpenGenericTypeKey(requiredItemOpenGenericType, x.Key)));
}
// Append registered generic types with compatible variance,
// e.g. for IHandler - IHandler is compatible with IHandler if B : A.
IEnumerable variantGenericItems = null;
if (requiredItemType.IsGeneric() && container.Rules.VariantGenericTypesInResolvedCollection)
{
variantGenericItems = container.GetServiceRegistrations()
.Where(x => x.ServiceType.IsGeneric()
&& x.ServiceType.GetGenericTypeDefinition() == requiredItemType.GetGenericTypeDefinition()
&& x.ServiceType != requiredItemType
&& x.ServiceType.IsAssignableTo(requiredItemType));
}
if (serviceKey != null) // include only single item matching key.
{
items = items.Where(it => serviceKey.Equals(it.OptionalServiceKey));
if (openGenericItems != null)
openGenericItems = openGenericItems // extract the actual key from combined type and key
.Where(x => serviceKey.Equals(((OpenGenericTypeKey)x.OptionalServiceKey).ServiceKey));
if (variantGenericItems != null)
variantGenericItems = variantGenericItems
.Where(it => serviceKey.Equals(it.OptionalServiceKey));
}
var metadataKey = preResolveParent.MetadataKey;
var metadata = preResolveParent.Metadata;
if (metadataKey != null || metadata != null)
{
items = items.Where(x => x.Factory.Setup.MatchesMetadata(metadataKey, metadata));
if (openGenericItems != null)
openGenericItems = openGenericItems
.Where(x => x.Factory.Setup.MatchesMetadata(metadataKey, metadata));
if (variantGenericItems != null)
variantGenericItems = variantGenericItems
.Where(x => x.Factory.Setup.MatchesMetadata(metadataKey, metadata));
}
// Exclude composite parent service from items, skip decorators
var parent = preResolveParent;
if (parent.FactoryType != FactoryType.Service)
parent = parent.FirstOrDefault(p => p.FactoryType == FactoryType.Service) ?? Request.Empty;
if (!parent.IsEmpty && parent.GetActualServiceType() == requiredItemType)
{
items = items.Where(x => x.Factory.FactoryID != parent.FactoryID);
if (openGenericItems != null)
openGenericItems = openGenericItems.Where(x => x
.Factory.FactoryGenerator?.GeneratedFactories.Enumerate()
.FindFirst(f => f.Value.FactoryID == parent.FactoryID) == null);
if (variantGenericItems != null)
variantGenericItems = variantGenericItems
.Where(x => x.Factory.FactoryID != parent.FactoryID);
}
var allItems = openGenericItems == null && variantGenericItems == null ? items
: variantGenericItems == null ? items.Concat(openGenericItems)
: openGenericItems == null ? items.Concat(variantGenericItems)
: items.Concat(openGenericItems).Concat(variantGenericItems);
// Resolve in registration order
foreach (var item in allItems.OrderBy(x => x.FactoryRegistrationOrder))
{
var service = container.Resolve(serviceType, item.OptionalServiceKey,
IfUnresolved.ReturnDefaultIfNotRegistered, item.ServiceType, preResolveParent, args);
if (service != null) // skip unresolved items
yield return service;
}
}
private void ThrowIfContainerDisposed()
{
if (IsDisposed)
Throw.It(Error.ContainerIsDisposed, ToString());
}
#endregion
#region IResolverContext
///
public IResolverContext Parent => _parent;
///
public IResolverContext Root
{
get
{
if (_parent == null)
return null;
var p = _parent;
while (p.Parent != null)
p = p.Parent;
return p;
}
}
///
public IScope SingletonScope => _singletonScope;
///
public IScopeContext ScopeContext => _scopeContext;
///
public IScope CurrentScope =>
_scopeContext == null ? _ownCurrentScope : _scopeContext.GetCurrentOrDefault();
///
[MethodImpl((MethodImplOptions)256)]
public IResolverContext WithCurrentScope(IScope scope)
{
ThrowIfContainerDisposed();
return new Container(Rules, _registry, _singletonScope, _scopeContext,
scope, _disposed, _disposeStackTrace, parent: this);
}
/// [Obsolete("Please use `RegisterInstance` or `Use` method instead")]
public void UseInstance(Type serviceType, object instance, IfAlreadyRegistered ifAlreadyRegistered,
bool preventDisposal, bool weaklyReferenced, object serviceKey)
{
ThrowIfContainerDisposed();
if (instance != null)
instance.ThrowIfNotInstanceOf(serviceType, Error.RegisteringInstanceNotAssignableToServiceType);
if (weaklyReferenced)
instance = new WeakReference(instance);
else if (preventDisposal)
instance = new HiddenDisposable(instance);
var scope = _ownCurrentScope ?? _singletonScope;
var reuse = scope == _singletonScope ? Reuse.Singleton : Reuse.Scoped;
var instanceType = instance?.GetType() ?? typeof(object);
_registry.Swap(r =>
{
var entry = r.Services.GetValueOrDefault(serviceType);
var oldEntry = entry;
// no entries, first registration, usual/hot path
if (entry == null)
{
// add new entry with instance factory
var instanceFactory = new InstanceFactory(instance, instanceType, reuse, scope);
entry = serviceKey == null
? (object)instanceFactory
: FactoriesEntry.Empty.With(instanceFactory, serviceKey);
}
else
{
// have some registrations of instance, find if we should replace, add, or throw
var singleDefaultFactory = entry as Factory;
if (singleDefaultFactory != null)
{
if (serviceKey != null)
{
// @ifAlreadyRegistered does not make sense for keyed, because there are no other keyed
entry = FactoriesEntry.Empty.With(singleDefaultFactory)
.With(new InstanceFactory(instance, instanceType, reuse, scope), serviceKey);
}
else // for default instance
{
switch (ifAlreadyRegistered)
{
case IfAlreadyRegistered.AppendNotKeyed:
entry = FactoriesEntry.Empty.With(singleDefaultFactory)
.With(new InstanceFactory(instance, instanceType, reuse, scope));
break;
case IfAlreadyRegistered.Throw:
Throw.It(Error.UnableToRegisterDuplicateDefault, serviceType, singleDefaultFactory);
break;
case IfAlreadyRegistered.Keep:
break;
case IfAlreadyRegistered.Replace:
var reusedFactory = singleDefaultFactory as InstanceFactory;
if (reusedFactory != null)
scope.SetOrAdd(reusedFactory.FactoryID, instance);
else if (reuse != Reuse.Scoped) // for non-instance single registration, just replace with non-scoped instance only
entry = new InstanceFactory(instance, instanceType, reuse, scope);
else
entry = FactoriesEntry.Empty.With(singleDefaultFactory)
.With(new InstanceFactory(instance, instanceType, reuse, scope));
break;
case IfAlreadyRegistered.AppendNewImplementation: // otherwise Keep the old one
if (singleDefaultFactory.CanAccessImplementationType &&
singleDefaultFactory.ImplementationType != instanceType)
entry = FactoriesEntry.Empty.With(singleDefaultFactory)
.With(new InstanceFactory(instance, instanceType, reuse, scope));
break;
}
}
}
else // for multiple existing or single keyed factory
{
var singleKeyedOrManyDefaultFactories = (FactoriesEntry)entry;
if (serviceKey != null)
{
var singleKeyedFactory = singleKeyedOrManyDefaultFactories.Factories.GetValueOrDefault(serviceKey);
if (singleKeyedFactory == null)
{
entry = singleKeyedOrManyDefaultFactories
.With(new InstanceFactory(instance, instanceType, reuse, scope), serviceKey);
}
else // when keyed instance is found
{
switch (ifAlreadyRegistered)
{
case IfAlreadyRegistered.Replace:
var reusedFactory = singleKeyedFactory as InstanceFactory;
if (reusedFactory != null)
scope.SetOrAdd(reusedFactory.FactoryID, instance);
else
entry = singleKeyedOrManyDefaultFactories
.With(new InstanceFactory(instance, instanceType, reuse, scope), serviceKey);
break;
case IfAlreadyRegistered.Keep:
break;
default:
Throw.It(Error.UnableToRegisterDuplicateKey, serviceType, serviceKey, singleKeyedFactory);
break;
}
}
}
else // for default instance
{
var defaultFactories = singleKeyedOrManyDefaultFactories.LastDefaultKey == null
? Empty()
: singleKeyedOrManyDefaultFactories.Factories.Enumerate()
.Match(it => it.Key is DefaultKey, it => it.Value)
.ToArrayOrSelf();
if (defaultFactories.Length == 0) // no default factories among the multiple existing keyed factories
{
entry = singleKeyedOrManyDefaultFactories
.With(new InstanceFactory(instance, instanceType, reuse, scope));
}
else // there are existing default factories
{
switch (ifAlreadyRegistered)
{
case IfAlreadyRegistered.AppendNotKeyed:
entry = singleKeyedOrManyDefaultFactories
.With(new InstanceFactory(instance, instanceType, reuse, scope));
break;
case IfAlreadyRegistered.Throw:
Throw.It(Error.UnableToRegisterDuplicateDefault, serviceType, defaultFactories);
break;
case IfAlreadyRegistered.Keep:
break; // entry does not change
case IfAlreadyRegistered.Replace:
var instanceFactories = defaultFactories.Match(f => f is InstanceFactory);
if (instanceFactories.Length == 1)
{
scope.SetOrAdd(instanceFactories[0].FactoryID, instance);
}
else // multiple default or a keyed factory
{
// scoped instance may be appended only, and not replacing anything
if (reuse == Reuse.Scoped)
{
entry = singleKeyedOrManyDefaultFactories
.With(new InstanceFactory(instance, instanceType, reuse, scope));
}
else // here is the replacement goes on
{
var keyedFactories = singleKeyedOrManyDefaultFactories.Factories.Enumerate()
.Match(it => !(it.Key is DefaultKey)).ToArrayOrSelf();
if (keyedFactories.Length == 0) // replaces all default factories?
entry = new InstanceFactory(instance, instanceType, reuse, scope);
else
{
var factoriesEntry = FactoriesEntry.Empty;
for (var i = 0; i < keyedFactories.Length; i++)
factoriesEntry = factoriesEntry
.With(keyedFactories[i].Value, keyedFactories[i].Key);
entry = factoriesEntry
.With(new InstanceFactory(instance, instanceType, reuse, scope));
}
}
}
break;
case IfAlreadyRegistered.AppendNewImplementation: // otherwise Keep the old one
var duplicateImplIndex = defaultFactories.IndexOf(
x => x.CanAccessImplementationType && x.ImplementationType == instanceType);
if (duplicateImplIndex == -1) // add new implementation
entry = singleKeyedOrManyDefaultFactories
.With(new InstanceFactory(instance, instanceType, reuse, scope));
// otherwise do nothing - keep the old entry
break;
}
}
}
}
}
var hash = RuntimeHelpers.GetHashCode(serviceType);
var registry = r.WithServices(r.Services.AddOrUpdate(hash, serviceType, entry));
// clearing the resolution cache for the updated factory if any
if (oldEntry != null && oldEntry != entry)
{
var oldFactory = oldEntry as Factory;
if (oldFactory != null)
registry.DropFactoryCache(oldFactory, hash, serviceType);
else
((FactoriesEntry)oldEntry).Factories.Enumerate().ToArray()
.ForEach(x => registry.DropFactoryCache(x.Value, hash, serviceType, serviceKey));
}
return registry;
});
}
void IResolverContext.UseInstance(Type serviceType, object instance, IfAlreadyRegistered ifAlreadyRegistered,
bool preventDisposal, bool weaklyReferenced, object serviceKey) =>
UseInstance(serviceType, instance, ifAlreadyRegistered, preventDisposal, weaklyReferenced, serviceKey);
void IRegistrator.UseInstance(Type serviceType, object instance, IfAlreadyRegistered ifAlreadyRegistered,
bool preventDisposal, bool weaklyReferenced, object serviceKey) =>
UseInstance(serviceType, instance, ifAlreadyRegistered, preventDisposal, weaklyReferenced, serviceKey);
void IResolverContext.InjectPropertiesAndFields(object instance, string[] propertyAndFieldNames)
{
var instanceType = instance.ThrowIfNull().GetType();
PropertiesAndFieldsSelector propertiesAndFields = null;
if (!propertyAndFieldNames.IsNullOrEmpty())
{
var matchedMembers = instanceType.GetTypeInfo().DeclaredMembers.Match(
m => (m is PropertyInfo || m is FieldInfo) && propertyAndFieldNames.IndexOf(m.Name) != -1,
PropertyOrFieldServiceInfo.Of);
// todo: Should we throw when no props are found?
propertiesAndFields = matchedMembers.ToFunc>;
}
propertiesAndFields = propertiesAndFields ?? Rules.PropertiesAndFields ?? PropertiesAndFields.Auto;
var request = Request.Create(this, instanceType)
.WithResolvedFactory(new RegisteredInstanceFactory(instance, Reuse.Transient),
skipRecursiveDependencyCheck: true, skipCaptiveDependencyCheck: true);
foreach (var serviceInfo in propertiesAndFields(request))
if (serviceInfo != null)
{
var details = serviceInfo.Details;
var value = ((IResolver)this).Resolve(serviceInfo.ServiceType, details.ServiceKey,
details.IfUnresolved, details.RequiredServiceType, request, args: null);
if (value != null)
serviceInfo.SetValue(instance, value);
}
}
/// Adding the factory directly to scope for resolution
public void Use(Type serviceType, FactoryDelegate factory)
{
(CurrentScope ?? SingletonScope).SetUsedInstance(serviceType, factory);
var serviceTypeHash = RuntimeHelpers.GetHashCode(serviceType);
var cacheEntry = _registry.Value.GetCachedDefaultFactoryOrDefault(serviceTypeHash, serviceType);
if (cacheEntry != null)
{
ref var entry = ref cacheEntry.Value;
entry.Value = null; // reset the cache if any
}
}
#endregion
#region IContainer
/// The rules object defines policies per container for registration and resolution.
public Rules Rules { get; private set; }
/// Represents scope bound to container itself, and not the ambient (context) thing.
public IScope OwnCurrentScope => _ownCurrentScope;
/// Indicates that container is disposed.
public bool IsDisposed => _disposed == 1 || _singletonScope.IsDisposed;
///
public IContainer With(Rules rules, IScopeContext scopeContext, RegistrySharing registrySharing, IScope singletonScope) =>
With(_parent, rules, scopeContext, registrySharing, singletonScope, _ownCurrentScope);
///
public IContainer With(IResolverContext parent, Rules rules, IScopeContext scopeContext,
RegistrySharing registrySharing, IScope singletonScope, IScope curentScope)
{
ThrowIfContainerDisposed();
var registry =
registrySharing == RegistrySharing.Share ? _registry :
registrySharing == RegistrySharing.CloneButKeepCache ? Ref.Of(_registry.Value)
: Ref.Of(_registry.Value.WithoutCache());
return new Container(rules ?? Rules, registry, singletonScope ?? NewSingletonScope(), scopeContext,
curentScope ?? _ownCurrentScope, _disposed, _disposeStackTrace, parent ?? _parent);
}
/// Produces new container which prevents any further registrations.
/// (optional) Controls what to do with the next registration: ignore or throw exception.
/// Throws exception by default.
public IContainer WithNoMoreRegistrationAllowed(bool ignoreInsteadOfThrow = false) =>
new Container(Rules,
Ref.Of(_registry.Value.WithNoMoreRegistrationAllowed(ignoreInsteadOfThrow)),
_singletonScope, _scopeContext, _ownCurrentScope,
_disposed, _disposeStackTrace, _parent);
///
public bool ClearCache(Type serviceType, FactoryType? factoryType, object serviceKey)
{
var hash = RuntimeHelpers.GetHashCode(serviceType);
if (factoryType != null)
return _registry.Value.ClearCache(hash, serviceType, serviceKey, factoryType.Value);
var registry = _registry.Value;
var clearedServices = registry.ClearCache(hash, serviceType, serviceKey, FactoryType.Service);
var clearedWrapper = registry.ClearCache(hash, serviceType, serviceKey, FactoryType.Wrapper);
var clearedDecorator = registry.ClearCache(hash, serviceType, serviceKey, FactoryType.Decorator);
return clearedServices || clearedWrapper || clearedDecorator;
}
[MethodImpl((MethodImplOptions)256)]
internal Expression GetCachedFactoryExpression(int factoryId, Request request, out ImMapEntry slot) =>
_registry.Value.GetCachedFactoryExpression(factoryId, request, out slot);
[MethodImpl((MethodImplOptions) 256)]
internal void CacheFactoryExpression(int factoryId, Request request, Expression expr, ImMapEntry slot) =>
_registry.Value.CacheFactoryExpression(factoryId, request, expr, slot);
Factory IContainer.ResolveFactory(Request request)
{
var factory = ((IContainer)this).GetServiceFactoryOrDefault(request);
if (factory == null)
{
factory = GetWrapperFactoryOrDefault(request);
if (factory != null)
return factory;
var unknownServiceResolvers = Rules.UnknownServiceResolvers;
if (!unknownServiceResolvers.IsNullOrEmpty())
for (var i = 0; factory == null && i < unknownServiceResolvers.Length; i++)
factory = unknownServiceResolvers[i](request)?.DoNotCache();
}
if (factory?.FactoryGenerator != null)
factory = factory.FactoryGenerator.GetGeneratedFactory(request);
if (factory == null)
TryThrowUnableToResolve(request);
return factory;
}
internal static void TryThrowUnableToResolve(Request request)
{
if (request.IfUnresolved != IfUnresolved.Throw)
return;
var str = new StringBuilder();
str = request.Container
.GetAllServiceFactories(request.ServiceType, bothClosedAndOpenGenerics: true)
.Aggregate(str, (s, x) => s
.Append(x.Value.Reuse?.CanApply(request) ?? true ? " " : " without matching scope ")
.Print(x));
if (str.Length != 0)
Throw.It(Error.UnableToResolveFromRegisteredServices, request, str);
else
Throw.It(Error.UnableToResolveUnknownService, request,
request.Rules.DynamicRegistrationProviders.EmptyIfNull().Length,
request.Rules.UnknownServiceResolvers.EmptyIfNull().Length);
}
private Factory GetServiceFactoryOrDefaultForNullServiceKey(Request request, ServiceDetails details)
{
var requiredServiceType = details.RequiredServiceType;
var serviceType = requiredServiceType != null && requiredServiceType.IsOpenGeneric()
? requiredServiceType
: request.GetActualServiceType();
if (Rules.FactorySelector != null)
return GetRuleSelectedServiceFactoryOrDefault(request, serviceType);
var serviceFactories = _registry.Value.Services;
var entry = serviceFactories.GetValueOrDefault(serviceType);
if (entry == null && serviceType.GetTypeInfo().IsGenericType)
entry = serviceFactories.GetValueOrDefault(serviceType.GetGenericTypeDefinition());
// Most common case when we have a single default factory and no dynamic rules in addition
if (entry is Factory singleDefaultFactory &&
Rules.DynamicRegistrationProviders.IsNullOrEmpty())
{
if (singleDefaultFactory.Setup.Condition?.Invoke(request) == false ||
(details.MetadataKey != null || details.Metadata != null) &&
!singleDefaultFactory.Setup.MatchesMetadata(details.MetadataKey, details.Metadata))
return null;
return singleDefaultFactory;
}
var factories = entry == null ? null
: entry is Factory factory ? new KV(DefaultKey.Value, factory).One()
: entry.To().Factories
.Visit(new List>(2), (x, list) => list.Add(KV.Of(x.Key, x.Value)))
.ToArray(); // todo: optimize - we may not need ToArray here
if (!Rules.DynamicRegistrationProviders.IsNullOrEmpty() &&
(factories.IsNullOrEmpty() || !Rules.UseDynamicRegistrationsAsFallbackOnly))
factories = CombineRegisteredWithDynamicFactories(factories, true, FactoryType.Service, serviceType, null);
if (factories.IsNullOrEmpty())
return null;
// First, filter out non default normal and dynamic factories
var defaultFactories = factories.Match(f => f.Key is DefaultKey || f.Key is DefaultDynamicKey);
if (defaultFactories.Length == 0)
return null;
// For multiple matched factories, if the single one has a condition, then use it
var matchedFactories = defaultFactories.Match(x => request.MatchFactoryConditionAndMetadata(x));
// Check the for matching scopes. Only for more than 1 factory,
// for the single factory the check will be down the road
// BitBucket issue: #175
if (matchedFactories.Length > 1 && request.Rules.ImplicitCheckForReuseMatchingScope)
{
var reuseMatchedFactories = matchedFactories.Match(x => request.MatchFactoryReuse(x));
if (reuseMatchedFactories.Length == 1)
matchedFactories = reuseMatchedFactories;
else if (reuseMatchedFactories.Length > 1)
matchedFactories = FindFactoryWithTheMinReuseLifespan(matchedFactories)?.One() ?? matchedFactories;
if (matchedFactories.Length == 1)
{
// Issue: #382
// Add asResolutionCall for the factory to prevent caching of in-lined expression in context with not matching condition
if (request.IsResolutionCall)
request.ChangeServiceKey(matchedFactories[0].Key);
else // for injected dependency
matchedFactories[0].Value.Setup = matchedFactories[0].Value.Setup.WithAsResolutionCall();
}
}
// Match open-generic implementation with closed service type. Performance is OK because the generated factories are cached -
// so there should not be repeating of the check, and not match of Performance decrease.
if (matchedFactories.Length > 1)
matchedFactories = matchedFactories.Match(x => request.MatchGeneratedFactory(x));
if (matchedFactories.Length > 1)
{
var conditionedFactories = matchedFactories.Match(f => f.Value.Setup.Condition != null);
if (conditionedFactories.Length == 1)
matchedFactories = conditionedFactories;
}
if (matchedFactories.Length > 1)
{
var preferedFactories = matchedFactories.Match(f => f.Value.Setup.PreferInSingleServiceResolve);
if (preferedFactories.Length == 1)
matchedFactories = preferedFactories;
}
// The result is a single matched factory
if (matchedFactories.Length == 1)
{
// Changes service key for resolution call to identify single factory in cache and prevent wrong hit
if (defaultFactories.Length > 1 && request.IsResolutionCall)
request.ChangeServiceKey(matchedFactories[0].Key);
return matchedFactories[0].Value;
}
if (matchedFactories.Length > 1 && request.IfUnresolved == IfUnresolved.Throw)
Throw.It(Error.ExpectedSingleDefaultFactory, matchedFactories, request);
return null;
}
private Factory GetServiceFactoryOrDefaultForServiceKey(Request request, ServiceDetails details, object serviceKey)
{
Type serviceType;
var requiredServiceType = details.RequiredServiceType;
if (requiredServiceType != null && requiredServiceType.IsOpenGeneric())
serviceType = requiredServiceType;
else
{
serviceType = request.GetActualServiceType();
// Special case when open-generic required service type is encoded in ServiceKey as array of { ReqOpenGenServiceType, ServiceKey }
// presumes that required service type is closed generic
if (serviceKey is OpenGenericTypeKey openGenericTypeKey &&
serviceType.GetTypeInfo().IsGenericType && serviceType.GetGenericTypeDefinition() == openGenericTypeKey.RequiredServiceType)
{
serviceType = openGenericTypeKey.RequiredServiceType;
serviceKey = openGenericTypeKey.ServiceKey;
}
}
var serviceFactories = _registry.Value.Services;
var entry = serviceFactories.GetValueOrDefault(serviceType);
// If the entry is not found or the key in entry is not found go for the open-generic services
if ((entry == null ||
entry is Factory && !serviceKey.Equals(DefaultKey.Value) ||
entry is FactoriesEntry facEntry && facEntry.Factories.GetValueOrDefault(serviceKey) == null) &&
serviceType.IsClosedGeneric())
entry = serviceFactories.GetValueOrDefault(serviceType.GetGenericTypeDefinition()) ?? entry;
// Most common case when we have a single default factory and no dynamic rules in addition
if (entry is Factory singleDefaultFactory &&
Rules.DynamicRegistrationProviders.IsNullOrEmpty())
{
if (serviceKey != DefaultKey.Value ||
singleDefaultFactory.Setup.Condition?.Invoke(request) == false ||
(details.MetadataKey != null || details.Metadata != null) &&
!singleDefaultFactory.Setup.MatchesMetadata(details.MetadataKey, details.Metadata))
return null;
return singleDefaultFactory;
}
var factories = entry == null ? null
: entry is Factory factory ? new KV(DefaultKey.Value, factory).One()
: entry.To().Factories
.Visit(new List>(2), (x, list) => list.Add(KV.Of(x.Key, x.Value)))
.ToArray(); // todo: optimize - we may not need ToArray here
if (!Rules.DynamicRegistrationProviders.IsNullOrEmpty() &&
(factories.IsNullOrEmpty() || !Rules.UseDynamicRegistrationsAsFallbackOnly))
factories = CombineRegisteredWithDynamicFactories(factories, true, FactoryType.Service, serviceType, serviceKey);
if (factories.IsNullOrEmpty())
return null;
// For requested keyed service (which may be a `DefaultKey` or `DefaultDynamicKey`)
// just lookup for the key and return whatever the result
foreach (var f in factories)
if (serviceKey.Equals(f.Key) && f.Value.CheckCondition(request))
return f.Value;
return null;
}
Factory IContainer.GetServiceFactoryOrDefault(Request request)
{
var details = request.ServiceDetails;
if (details.ServiceKey == null)
return GetServiceFactoryOrDefaultForNullServiceKey(request, details);
return GetServiceFactoryOrDefaultForServiceKey(request, details, details.ServiceKey);
}
private Factory GetRuleSelectedServiceFactoryOrDefault(Request request, Type serviceType)
{
var serviceFactories = _registry.Value.Services;
var entry = serviceFactories.GetValueOrDefault(serviceType);
KV[] factories;
if (entry is Factory singleDefaultFactory)
{
// optimize for the most usual case - a single factory and no dynamic rules
if (Rules.UseDynamicRegistrationsAsFallbackOnly ||
Rules.DynamicRegistrationProviders.IsNullOrEmpty())
return request.MatchFactoryConditionAndMetadata(singleDefaultFactory)
? Rules.FactorySelector(request, DefaultKey.Value.Pair(singleDefaultFactory).One())
: null;
factories = new[] {new KV(DefaultKey.Value, singleDefaultFactory)};
}
else if (entry is FactoriesEntry e)
{
factories = e.Factories.Visit(new List>(), (x, l) => l.Add(KV.Of(x.Key, x.Value))).ToArray();
}
else
{
object openGenericEntry;
factories = Empty>();
if (serviceType.IsClosedGeneric())
{
var openGenericServiceType = serviceType.GetGenericTypeDefinition();
openGenericEntry = serviceFactories.GetValueOrDefault(openGenericServiceType);
if (openGenericEntry != null)
factories = GetRegistryEntryKeyFactoryPairs(openGenericEntry).ToArrayOrSelf();
}
}
if ((factories.Length == 0 || !Rules.UseDynamicRegistrationsAsFallbackOnly) &&
!Rules.DynamicRegistrationProviders.IsNullOrEmpty())
factories = CombineRegisteredWithDynamicFactories(factories, true, FactoryType.Service, serviceType);
if (factories.Length == 0)
return null;
// optimize for the case with the single factory
if (factories.Length == 1)
return request.MatchFactoryConditionAndMetadata(factories[0])
? Rules.FactorySelector(request, factories[0].Key.Pair(factories[0].Value).One())
: null;
// Sort in registration order
if (factories.Length > 1)
Array.Sort(factories, _lastFactoryIDWinsComparer);
var matchedFactories = factories.Match(request.MatchFactoryConditionAndMetadata);
if (matchedFactories.Length > 1 && request.Rules.ImplicitCheckForReuseMatchingScope)
{
// Check the for matching scopes. Only for more than 1 factory,
// for the single factory the check will be down the road
// BitBucket issue: #175
matchedFactories = matchedFactories.Match(request.MatchFactoryReuse);
if (matchedFactories.Length == 1)
{
// Issue: #382
// Add asResolutionCall for the factory to prevent caching of in-lined expression in context with not matching condition
if (request.IsResolutionCall)
request.ChangeServiceKey(matchedFactories[0].Key);
else // for injected dependency
matchedFactories[0].Value.Setup = matchedFactories[0].Value.Setup.WithAsResolutionCall();
}
}
// Match open-generic implementation with closed service type. Performance is OK because the generated factories are cached -
// so there should not be repeating of the check, and not match of Performance decrease.
if (matchedFactories.Length > 1)
matchedFactories = matchedFactories.Match(request.MatchGeneratedFactory);
if (matchedFactories.Length == 0)
return null;
var selectedFactory = Rules.FactorySelector(request, matchedFactories.Map(x => x.Key.Pair(x.Value)));
if (selectedFactory == null)
return null;
// Issue: #508
if (factories.Length > 1)
{
var i = 0;
while (i < matchedFactories.Length && matchedFactories[i].Value.FactoryID != selectedFactory.FactoryID)
++i;
if (i < matchedFactories.Length)
request.ChangeServiceKey(matchedFactories[i].Key);
}
return selectedFactory;
}
private static KV FindFactoryWithTheMinReuseLifespan(KV[] factories)
{
var minLifespan = int.MaxValue;
var multipleFactories = false;
KV minLifespanFactory = null;
for (var i = 0; i < factories.Length; i++)
{
var factory = factories[i];
var reuse = factory.Value.Reuse;
var lifespan = reuse == null || reuse == Reuse.Transient ? int.MaxValue : reuse.Lifespan;
if (lifespan < minLifespan)
{
minLifespan = lifespan;
minLifespanFactory = factory;
multipleFactories = false;
}
else if (lifespan == minLifespan)
{
multipleFactories = true;
}
}
return !multipleFactories && minLifespanFactory != null ? minLifespanFactory : null;
}
IEnumerable> IContainer.GetAllServiceFactories(Type serviceType, bool bothClosedAndOpenGenerics)
{
var serviceFactories = _registry.Value.Services;
var entry = serviceFactories.GetValueOrDefault(serviceType);
var factories = GetRegistryEntryKeyFactoryPairs(entry).ToArrayOrSelf();
if (bothClosedAndOpenGenerics && serviceType.IsClosedGeneric())
{
var openGenericServiceType = serviceType.GetGenericTypeDefinition();
var openGenericEntry = serviceFactories.GetValueOrDefault(openGenericServiceType);
if (openGenericEntry != null)
factories = factories.Append(GetRegistryEntryKeyFactoryPairs(openGenericEntry).ToArrayOrSelf());
}
if (!factories.IsNullOrEmpty() && Rules.UseDynamicRegistrationsAsFallbackOnly ||
Rules.DynamicRegistrationProviders.IsNullOrEmpty())
return factories;
return CombineRegisteredWithDynamicFactories(factories, bothClosedAndOpenGenerics, FactoryType.Service, serviceType);
}
private static IEnumerable> GetRegistryEntryKeyFactoryPairs(object entry) =>
entry == null
? Empty>()
: entry is Factory ? new[] { new KV(DefaultKey.Value, (Factory)entry) }
// todo: optimize
: entry.To().Factories.Visit(new List>(), (x, l) => l.Add(KV.Of(x.Key, x.Value))).ToArray();
Expression IContainer.GetDecoratorExpressionOrDefault(Request request)
{
// return early if no decorators registered
if (_registry.Value.Decorators.IsEmpty && request.Rules.DynamicRegistrationProviders.IsNullOrEmpty())
return null;
var serviceType = request.ServiceType;
var arrayElementType = serviceType.GetArrayElementTypeOrNull();
if (arrayElementType != null)
{
request = request.WithChangedServiceInfo(x =>
x.With(typeof(IEnumerable<>).MakeGenericType(arrayElementType)));
serviceType = request.ServiceType;
}
var container = request.Container;
var decorators = container.GetDecoratorFactoriesOrDefault(serviceType);
// Combine with required service type if different from service type
var requiredServiceType = request.GetActualServiceType();
if (requiredServiceType != serviceType)
decorators = decorators.Append(container.GetDecoratorFactoriesOrDefault(requiredServiceType));
// Define the list of ids for the already applied decorators
int[] appliedDecoratorIDs = null;
if (!decorators.IsNullOrEmpty())
{
appliedDecoratorIDs = GetAppliedDecoratorIDs(request);
if (!appliedDecoratorIDs.IsNullOrEmpty())
decorators = decorators.Match(appliedDecoratorIDs, (ids, d) => ids.IndexOf(d.FactoryID) == -1);
}
// Append open-generic decorators
var genericDecorators = Empty();
Type openGenericServiceType = null;
if (serviceType.GetTypeInfo().IsGenericType)
genericDecorators = container.GetDecoratorFactoriesOrDefault(openGenericServiceType = serviceType.GetGenericTypeDefinition());
// Combine with open-generic required type if they are different from service type
if (requiredServiceType != serviceType)
{
var openGenericRequiredType = requiredServiceType.GetTypeInfo().IsGenericType ? requiredServiceType.GetGenericTypeDefinition() : null;
if (openGenericRequiredType != null && openGenericRequiredType != openGenericServiceType)
genericDecorators = genericDecorators.Append(
container.GetDecoratorFactoriesOrDefault(openGenericRequiredType));
}
// Append generic type argument decorators, registered as Object
// Note: the condition for type arguments should be checked before generating the closed generic version
var typeArgDecorators = container.GetDecoratorFactoriesOrDefault(typeof(object));
if (!typeArgDecorators.IsNullOrEmpty())
genericDecorators = genericDecorators.Append(
typeArgDecorators.Match(request, (r, d) => d.CheckCondition(r)));
// Filter out already applied generic decorators
// And combine with rest of decorators
if (!genericDecorators.IsNullOrEmpty())
{
appliedDecoratorIDs = appliedDecoratorIDs ?? GetAppliedDecoratorIDs(request);
if (!appliedDecoratorIDs.IsNullOrEmpty())
{
genericDecorators = genericDecorators
.Match(appliedDecoratorIDs,
(appliedDecIds, d) =>
{
var factoryGenerator = d.FactoryGenerator;
if (factoryGenerator == null)
return appliedDecIds.IndexOf(d.FactoryID) == -1;
foreach (var entry in factoryGenerator.GeneratedFactories.Enumerate())
if (appliedDecIds.IndexOf(entry.Value.FactoryID) != -1)
return false;
return true;
});
}
// Generate closed-generic versions
if (!genericDecorators.IsNullOrEmpty())
{
genericDecorators = genericDecorators
.Map(request, (r, d) => d.FactoryGenerator == null ? d : d.FactoryGenerator.GetGeneratedFactory(r, ifErrorReturnDefault: true))
.Match(d => d != null);
decorators = decorators.Append(genericDecorators);
}
}
// Filter out the recursive decorators by doing the same recursive check
// that Request.WithResolvedFactory does. Fixes: #267
if (!decorators.IsNullOrEmpty())
decorators = decorators.Match(request, (r, d) => !r.HasRecursiveParent(d.FactoryID));
// Return earlier if no decorators found, or we have filtered out everything
if (decorators.IsNullOrEmpty())
return null;
Factory decorator;
if (decorators.Length == 1)
{
decorator = decorators[0];
if (!decorator.CheckCondition(request))
return null;
}
else
{
// Within the remaining decorators find one with the maximum Order
// or if no Order for all decorators, then find the last registered - with the biggest FactoryID
decorator = decorators
.OrderByDescending(d => ((Setup.DecoratorSetup)d.Setup).Order)
.ThenByDescending(d => d.RegistrationOrder)
.FirstOrDefault(d => d.CheckCondition(request));
}
var decoratorExpr = decorator?.GetExpressionOrDefault(request);
if (decoratorExpr == null)
return null;
// decorator of arrays should be converted back from IEnumerable to array.
if (arrayElementType != null)
decoratorExpr = Call(WrappersSupport.ToArrayMethod.MakeGenericMethod(arrayElementType), decoratorExpr);
return decoratorExpr;
}
private static int[] GetAppliedDecoratorIDs(Request request)
{
var appliedIDs = Empty();
for (var p = request.DirectParent; !p.IsEmpty && p.FactoryType != FactoryType.Service; p = p.DirectParent)
{
if (p.FactoryType == FactoryType.Decorator &&
p.DecoratedFactoryID == request.FactoryID)
appliedIDs = appliedIDs.AppendOrUpdate(p.FactoryID);
}
return appliedIDs;
}
Factory IContainer.GetWrapperFactoryOrDefault(Type serviceType)
{
var wrappers = _registry.Value.Wrappers;
var wrapper = wrappers.GetValueOrDefault(serviceType);
if (wrapper == null && serviceType.GetTypeInfo().IsGenericType)
wrapper = wrappers.GetValueOrDefault(serviceType.GetGenericTypeDefinition());
return wrapper as Factory;
}
Factory[] IContainer.GetDecoratorFactoriesOrDefault(Type serviceType)
{
var allDecorators = _registry.Value.Decorators;
var decorators = allDecorators.IsEmpty ? null : (Factory[])allDecorators.GetValueOrDefault(serviceType);
if (decorators == null)
{
if (Rules.DynamicRegistrationProviders.IsNullOrEmpty())
return Empty();
return CombineRegisteredWithDynamicFactories(null, true, FactoryType.Decorator, serviceType, null).Map(x => x.Value);
}
if (Rules.UseDynamicRegistrationsAsFallbackOnly ||
Rules.DynamicRegistrationProviders.IsNullOrEmpty())
return decorators;
var decoratorsWithDefaultKey = decorators.Map(d => new KV(DefaultKey.Value, d));
return CombineRegisteredWithDynamicFactories(
decoratorsWithDefaultKey, true, FactoryType.Decorator, serviceType, null).Map(x => x.Value);
}
Type IContainer.GetWrappedType(Type serviceType, Type requiredServiceType)
{
if (requiredServiceType != null && requiredServiceType.IsOpenGeneric())
return ((IContainer)this).GetWrappedType(serviceType, null);
serviceType = requiredServiceType ?? serviceType;
var wrappedType = serviceType.GetArrayElementTypeOrNull();
if (wrappedType == null)
{
var factory = ((IContainer)this).GetWrapperFactoryOrDefault(serviceType);
if (factory != null)
{
wrappedType = ((Setup.WrapperSetup)factory.Setup).GetWrappedTypeOrNullIfWrapsRequired(serviceType);
if (wrappedType == null)
return null;
}
}
return wrappedType == null ? serviceType
: ((IContainer)this).GetWrappedType(wrappedType, null);
}
/// Converts known item into literal expression or wraps it in a constant expression.
public Expression GetConstantExpression(object item, Type itemType = null, bool throwIfStateRequired = false)
{
// Check for UsedForExpressionGeneration, and if not set just short-circuit to Expression.Constant
if (!throwIfStateRequired && !Rules.ThrowIfRuntimeStateRequired && !Rules.UsedForExpressionGeneration)
return itemType == null ? Constant(item) : Constant(item, itemType);
if (item == null)
return itemType == null || itemType == typeof(object) ? Constant(null) : Constant(null, itemType);
var convertible = item as IConvertibleToExpression;
if (convertible != null)
return convertible.ToExpression(it => GetConstantExpression(it, null, throwIfStateRequired));
var actualItemType = item.GetType();
if (actualItemType.GetGenericDefinitionOrNull() == typeof(KV<,>))
{
var kvArgTypes = actualItemType.GetGenericParamsAndArgs();
return Call(_kvOfMethod.MakeGenericMethod(kvArgTypes),
GetConstantExpression(actualItemType.GetTypeInfo().GetDeclaredField("Key").GetValue(item), kvArgTypes[0], throwIfStateRequired),
GetConstantExpression(actualItemType.GetTypeInfo().GetDeclaredField("Value").GetValue(item), kvArgTypes[1], throwIfStateRequired));
}
if (actualItemType.IsPrimitive() ||
actualItemType.IsAssignableTo())
return itemType == null ? Constant(item) : Constant(item, itemType);
// don't try to recover the non primitive type of element,
// cause it is a too much work to find the base common element type in array
var arrayElemType = actualItemType.GetArrayElementTypeOrNull();
if (arrayElemType != null && arrayElemType != typeof(object) &&
(arrayElemType.IsPrimitive() || actualItemType.IsAssignableTo()))
return NewArrayInit(arrayElemType,
((object[])item).Map(x => GetConstantExpression(x, arrayElemType, throwIfStateRequired)));
var itemExpr = Rules.ItemToExpressionConverter?.Invoke(item, itemType);
if (itemExpr != null)
return itemExpr;
Throw.If(throwIfStateRequired || Rules.ThrowIfRuntimeStateRequired,
Error.StateIsRequiredToUseItem, item);
return itemType == null ? Constant(item) : Constant(item, itemType);
}
private static readonly MethodInfo _kvOfMethod =
typeof(KV).GetTypeInfo().GetDeclaredMethod(nameof(KV.Of));
#endregion
#region Factories Add/Get
internal sealed class FactoriesEntry
{
public readonly DefaultKey LastDefaultKey;
public readonly ImHashMap Factories;
// lastDefaultKey may be null
public FactoriesEntry(DefaultKey lastDefaultKey, ImHashMap factories)
{
LastDefaultKey = lastDefaultKey;
Factories = factories;
}
public static readonly FactoriesEntry Empty =
new FactoriesEntry(null, ImHashMap.Empty);
public FactoriesEntry With(Factory factory, object serviceKey = null)
{
var lastDefaultKey = serviceKey == null
? LastDefaultKey == null ? DefaultKey.Value : LastDefaultKey.Next()
: LastDefaultKey;
return new FactoriesEntry(lastDefaultKey, Factories.AddOrUpdate(serviceKey ?? lastDefaultKey, factory));
}
}
private KV[] CombineRegisteredWithDynamicFactories(
KV[] registeredFactories, bool bothClosedAndOpenGenerics,
FactoryType factoryType, Type serviceType, object serviceKey = null)
{
Rules.DynamicRegistrationProvider[] dynamicRegistrationProviders = Rules.DynamicRegistrationProviders;
var serviceGenericTypeDefinition = bothClosedAndOpenGenerics && serviceType.IsClosedGeneric()
? serviceType.GetGenericTypeDefinition()
: null;
// Assign unique continious keys across all of dynamic providers,
// to prevent duplicate keys and peeking the wrong factory by collection wrappers
// NOTE: Given that dynamic registration always return the same implementation types in the same order
// then the dynamic key will be assigned deterministically, so that even if `CombineRegisteredWithDynamicFactories`
// is called multiple times during the resolution (like for `ResolveMany`) it is possible to match the required factory by its order.
var dynamicKey = DefaultDynamicKey.Value;
var resultFactories = registeredFactories;
for (var i = 0; i < dynamicRegistrationProviders.Length; i++)
{
var dynamicRegistrationProvider = dynamicRegistrationProviders[i];
var dynamicRegistrations = dynamicRegistrationProvider(serviceType, serviceKey).ToArrayOrSelf();
if (serviceGenericTypeDefinition != null)
dynamicRegistrations = dynamicRegistrations.Append(dynamicRegistrationProvider(serviceGenericTypeDefinition, serviceKey).ToArrayOrSelf());
if (dynamicRegistrations.IsNullOrEmpty())
continue;
if (resultFactories == null)
{
resultFactories = dynamicRegistrations.Match(x =>
x.Factory.FactoryType == factoryType &&
x.Factory.ValidateAndNormalizeRegistration(serviceType, serviceKey, false, Rules),
x => KV.Of(x.ServiceKey ?? (dynamicKey = dynamicKey.Next()), x.Factory));
continue;
}
var remainingDynamicFactories = dynamicRegistrations
.Match(x =>
{
if (x.Factory.FactoryType != factoryType ||
!x.Factory.ValidateAndNormalizeRegistration(serviceType, serviceKey, false, Rules))
return false;
if (x.ServiceKey == null) // for the default dynamic factory
{
switch (x.IfAlreadyRegistered)
{
// accept the default if result factories don't contain it already
case IfAlreadyRegistered.Keep:
case IfAlreadyRegistered.Throw:
return resultFactories.IndexOf(f => f.Key is DefaultKey || f.Key is DefaultDynamicKey) == -1;
// remove the default from the result factories
case IfAlreadyRegistered.Replace:
resultFactories = resultFactories.Match(f => !(f.Key is DefaultKey || f.Key is DefaultDynamicKey));
return true;
case IfAlreadyRegistered.AppendNotKeyed:
return true;
case IfAlreadyRegistered.AppendNewImplementation:
// if we cannot access to dynamic implementation type, assume that the type is new implementation
if (!x.Factory.CanAccessImplementationType)
return true;
// keep dynamic factory if there is no result factory with the same implementation type
return resultFactories.IndexOf(x, (s, f) =>
f.Value.CanAccessImplementationType && f.Value.ImplementationType == s.Factory.ImplementationType) == -1;
}
}
else // for the keyed dynamic factory
{
switch (x.IfAlreadyRegistered)
{
// remove the result factory with the same key
case IfAlreadyRegistered.Replace:
resultFactories = resultFactories.Match(x.ServiceKey, (key, f) => !f.Key.Equals(key));
return true;
// keep the dynamic factory with the new service key, otherwise skip it
default:
return resultFactories.IndexOf(x.ServiceKey, (key, f) => f.Key.Equals(key)) == -1;
}
}
return true;
},
x => KV.Of(x.ServiceKey ?? (dynamicKey = dynamicKey.Next()), x.Factory));
resultFactories = resultFactories.Append(remainingDynamicFactories);
}
return resultFactories;
}
private static readonly LastFactoryIDWinsComparer _lastFactoryIDWinsComparer = new LastFactoryIDWinsComparer();
private struct LastFactoryIDWinsComparer : IComparer>
{
public int Compare(KV first, KV next) =>
(first?.Value.FactoryID ?? 0) - (next?.Value.FactoryID ?? 0);
}
private Factory GetWrapperFactoryOrDefault(Request request)
{
// wrapper ignores the service key and propagates the service key to wrapped service
var serviceType = request.GetActualServiceType();
var serviceTypeInfo = serviceType.GetTypeInfo();
if (serviceTypeInfo.IsArray)
serviceType = typeof(IEnumerable<>).MakeGenericType(serviceTypeInfo.GetElementType());
var factory = ((IContainer)this).GetWrapperFactoryOrDefault(serviceType);
if (factory?.FactoryGenerator != null)
factory = factory.FactoryGenerator.GetGeneratedFactory(request);
if (factory == null)
return null;
var condition = factory.Setup.Condition;
if (condition != null && !condition(request))
return null;
return factory;
}
#endregion
#region Implementation
private int _disposed;
private StackTrace _disposeStackTrace;
internal readonly Ref _registry;
private readonly IScope _singletonScope;
private readonly IScope _ownCurrentScope;
private readonly IScopeContext _scopeContext;
private readonly IResolverContext _parent;
internal sealed class InstanceFactory : Factory
{
public override Type ImplementationType { get; }
public override bool HasRuntimeState => true;
public InstanceFactory(object instance, Type instanceType, IReuse reuse, IScope scopeToAdd = null) : base(reuse)
{
ImplementationType = instanceType;
scopeToAdd?.SetOrAdd(FactoryID, instance);
}
/// Switched off until I (or someone) will figure it out.
public override bool UseInterpretation(Request request) => false;
/// Tries to return instance directly from scope or sigleton, and fallbacks to expression for decorator.
public override FactoryDelegate GetDelegateOrDefault(Request request)
{
if (request.IsResolutionRoot)
{
var decoratedExpr = request.Container.GetDecoratorExpressionOrDefault(request.WithResolvedFactory(this));
if (decoratedExpr != null)
return decoratedExpr.CompileToFactoryDelegate(request.Rules.UseFastExpressionCompiler, request.Rules.UseInterpretation);
}
return GetInstanceFromScopeChainOrSingletons;
}
/// Called for Injection as dependency.
public override Expression GetExpressionOrDefault(Request request)
{
request = request.WithResolvedFactory(this);
return request.Container.GetDecoratorExpressionOrDefault(request)
?? CreateExpressionOrDefault(request);
}
public override Expression CreateExpressionOrDefault(Request request) =>
Resolver.CreateResolutionExpression(request);
#region Implementation
private object GetInstanceFromScopeChainOrSingletons(IResolverContext r)
{
for (var scope = r.CurrentScope; scope != null; scope = scope.Parent)
{
var result = GetAndUnwrapOrDefault(scope, FactoryID);
if (result != null)
return result;
}
var instance = GetAndUnwrapOrDefault(r.SingletonScope, FactoryID);
return instance.ThrowIfNull(Error.UnableToFindSingletonInstance);
}
private static object GetAndUnwrapOrDefault(IScope scope, int factoryId)
{
object value;
if (!scope.TryGet(out value, factoryId))
return null;
return (value as WeakReference)?.Target.ThrowIfNull(Error.WeakRefReuseWrapperGCed)
?? (value as HiddenDisposable)?.Value
?? value;
}
#endregion
}
internal sealed class Registry
{
public static readonly Registry Empty = new Registry();
public static readonly Registry Default = new Registry(WrappersSupport.Wrappers);
// Factories:
public readonly ImMap> Services;
// todo: we may use Factory or Factory[] as a value for decorators
public readonly ImMap> Decorators; // value is Factory[]
public readonly ImMap> Wrappers; // value is Factory
internal const int CACHE_SLOT_COUNT = 16;
internal const int CACHE_SLOT_COUNT_MASK = CACHE_SLOT_COUNT - 1;
public sealed class Compiling
{
public readonly Expression Expression;
public Compiling(Expression expression) => Expression = expression;
}
public ImMap>[] DefaultFactoryCache;
[MethodImpl((MethodImplOptions)256)]
public ImMapEntry> GetCachedDefaultFactoryOrDefault(int serviceTypeHash, Type serviceType)
{
// copy to local `cache` will prevent NRE if cache is set to null from outside
var cache = DefaultFactoryCache;
return cache == null ? null : cache[serviceTypeHash & CACHE_SLOT_COUNT_MASK]?.GetEntryOrDefault(serviceTypeHash, serviceType);
}
public void TryCacheDefaultFactory(int serviceTypeHash, Type serviceType, T factory)
{
// Disable caching when no services registered, not to cache an empty collection wrapper or alike.
if (Services.IsEmpty)
return;
if (DefaultFactoryCache == null)
Interlocked.CompareExchange(ref DefaultFactoryCache, new ImMap>[CACHE_SLOT_COUNT], null);
ref var map = ref DefaultFactoryCache[serviceTypeHash & CACHE_SLOT_COUNT_MASK];
if (map == null)
Interlocked.CompareExchange(ref map, ImMap>.Empty, null);
var m = map;
if (Interlocked.CompareExchange(ref map, m.AddOrUpdate(serviceTypeHash, serviceType, factory), m) != m)
Ref.Swap(ref map, serviceTypeHash, serviceType, factory, (x, h, t, f) => x.AddOrUpdate(h, t, f));
}
internal sealed class KeyedFactoryCacheEntry
{
public readonly KeyedFactoryCacheEntry Rest;
public readonly object Key;
public object Factory;
public KeyedFactoryCacheEntry(KeyedFactoryCacheEntry rest, object key, object factory)
{
Rest = rest;
Key = key;
Factory = factory;
}
}
// Where key is `KV.Of(ServiceKey | ScopeName | RequiredServiceType | KV.Of(ServiceKey, ScopeName | RequiredServiceType) | ...)`
// and value is `KeyedFactoryCacheEntries`
public ImMap>[] KeyedFactoryCache;
[MethodImpl((MethodImplOptions)256)]
public bool GetCachedKeyedFactoryOrDefault(int serviceTypeHash, Type serviceType, object key, out KeyedFactoryCacheEntry result)
{
result = null;
var cache = KeyedFactoryCache;
if (cache != null)
{
var entry = cache[serviceTypeHash & CACHE_SLOT_COUNT_MASK]?.GetEntryOrDefault(serviceTypeHash, serviceType);
if (entry != null)
for (var x = (KeyedFactoryCacheEntry)entry.Value.Value; x != null && result == null; x = x.Rest)
if (x.Key.Equals(key))
result = x;
}
return result != null;
}
public void TryCacheKeyedFactory(int serviceTypeHash, Type serviceType, object key, object factory)
{
// Disable caching when no services registered, not to cache an empty collection wrapper or alike.
if (Services.IsEmpty)
return;
if (KeyedFactoryCache == null)
Interlocked.CompareExchange(ref KeyedFactoryCache, new ImMap>[CACHE_SLOT_COUNT], null);
ref var map = ref KeyedFactoryCache[serviceTypeHash & CACHE_SLOT_COUNT_MASK];
if (map == null)
Interlocked.CompareExchange(ref map, ImMap>.Empty, null);
var entry = map.GetEntryOrDefault(serviceTypeHash, serviceType);
if (entry == null)
{
var m = map;
if (Interlocked.CompareExchange(ref map, m.AddOrKeep(serviceTypeHash, serviceType), m) != m)
Ref.Swap(ref map, serviceTypeHash, serviceType, (x, h, t) => x.AddOrKeep(h, t));
entry = map.GetEntryOrDefault(serviceTypeHash, serviceType);
}
var e = entry.Value.Value;
if (Interlocked.CompareExchange(ref entry.Value.Value, SetOrAddKeyedCacheFactory(e, key, factory), e) != e)
Ref.Swap(ref entry.Value.Value, key, factory, SetOrAddKeyedCacheFactory);
}
private object SetOrAddKeyedCacheFactory(object x, object k, object f)
{
for (var entry = (KeyedFactoryCacheEntry)x; entry != null; entry = entry.Rest)
{
if (entry.Key.Equals(k))
{
entry.Factory = f;
return x;
}
}
return new KeyedFactoryCacheEntry((KeyedFactoryCacheEntry)x, k, f);
}
internal struct ExpressionCacheSlot
{
public Expression Transient;
public Expression Scoped;
public KeyValuePair[] ScopedToName;
}
/// The int key is the `FactoryID`
public ImMap[] FactoryExpressionCache;
public Expression GetCachedFactoryExpression(
int factoryId, Request request, out ImMapEntry entry)
{
entry = null;
var cache = FactoryExpressionCache;
if (cache != null)
{
var map = cache[factoryId & CACHE_SLOT_COUNT_MASK];
if (map != null)
{
entry = map.GetEntryOrDefault(factoryId);
if (entry != null)
{
var reuse = request.Reuse;
if (reuse == Reuse.Transient)
return entry.Value.Transient;
if (reuse is CurrentScopeReuse scoped)
{
if (scoped.Name == null)
return entry.Value.Scoped;
var named = entry.Value.ScopedToName;
if (named != null)
for (var i = 0; i < named.Length; i++)
if (Equals(named[i].Key, scoped.Name))
return named[i].Value;
}
}
}
}
return null;
}
internal void CacheFactoryExpression(int factoryId, Request request, Expression expr, ImMapEntry entry = null)
{
if (entry == null)
{
if (FactoryExpressionCache == null)
Interlocked.CompareExchange(ref FactoryExpressionCache,
new ImMap[CACHE_SLOT_COUNT], null);
ref var map = ref FactoryExpressionCache[factoryId & CACHE_SLOT_COUNT_MASK];
if (map == null)
Interlocked.CompareExchange(ref map, ImMap.Empty, null);
entry = map.GetEntryOrDefault(factoryId);
if (entry == null)
{
var m = map;
if (Interlocked.CompareExchange(ref map, m.AddOrKeep(factoryId), m) != m)
Ref.Swap(ref map, factoryId, (x, id) => x.AddOrKeep(id));
entry = map.GetEntryOrDefault(factoryId);
}
}
var reuse = request.Reuse;
if (reuse == Reuse.Transient)
{
entry.Value.Transient = expr;
}
else if (reuse is CurrentScopeReuse scoped)
{
if (scoped.Name == null)
entry.Value.Scoped = expr;
else
{
var named = entry.Value.ScopedToName;
if (named == null)
{
entry.Value.ScopedToName = scoped.Name.Pair(expr).One();
}
else
{
var i = named.Length - 1;
for (; i >= 0; i--)
if (Equals(named[i].Key, scoped.Name))
break;
if (i == -1)
{
var newNamed = new KeyValuePair[named.Length + 1];
Array.Copy(named, 0, newNamed, 0, named.Length);
newNamed[named.Length] = scoped.Name.Pair(expr);
entry.Value.ScopedToName = newNamed;
}
}
}
}
}
private enum IsChangePermitted { Permitted, Error, Ignored }
private readonly IsChangePermitted _isChangePermitted;
private Registry(ImMap> wrapperFactories = null)
: this(ImMap>.Empty, ImMap>.Empty, wrapperFactories ?? ImMap>.Empty,
null, null, null, // caches are initialized to `null` to quickly check that they
IsChangePermitted.Permitted)
{ }
private Registry(
ImMap> services,
ImMap> decorators,
ImMap> wrappers,
ImMap>[] defaultFactoryCache,
ImMap>[] keyedFactoryCache,
ImMap[] factoryExpressionCache,
IsChangePermitted isChangePermitted)
{
Services = services;
Decorators = decorators;
Wrappers = wrappers;
DefaultFactoryCache = defaultFactoryCache;
KeyedFactoryCache = keyedFactoryCache;
FactoryExpressionCache = factoryExpressionCache;
_isChangePermitted = isChangePermitted;
}
public Registry WithoutCache() =>
new Registry(Services, Decorators, Wrappers, null, null, null, _isChangePermitted);
internal Registry WithServices(ImMap> services) =>
services == Services ? this :
new Registry(services, Decorators, Wrappers,
// Using Copy is fine when you have only the registrations because the caches will be null and no actual copy will be done.
DefaultFactoryCache.Copy(), KeyedFactoryCache.Copy(), FactoryExpressionCache.Copy(),
_isChangePermitted);
private Registry WithDecorators(ImMap> decorators) =>
decorators == Decorators ? this :
new Registry(Services, decorators, Wrappers,
DefaultFactoryCache.Copy(), KeyedFactoryCache.Copy(), FactoryExpressionCache.Copy(), _isChangePermitted);
private Registry WithWrappers(ImMap> wrappers) =>
wrappers == Wrappers ? this :
new Registry(Services, Decorators, wrappers,
DefaultFactoryCache.Copy(), KeyedFactoryCache.Copy(), FactoryExpressionCache.Copy(), _isChangePermitted);
public IEnumerable GetServiceRegistrations()
{
foreach (var entry in Services.Enumerate())
{
if (entry.Value.Value is Factory factory)
yield return new ServiceRegistrationInfo(factory, entry.Value.Key, null);
else
{
var factories = ((FactoriesEntry)entry.Value.Value).Factories;
foreach (var f in factories.Enumerate())
yield return new ServiceRegistrationInfo(f.Value, entry.Value.Key, f.Key);
}
}
}
public Registry Register(Factory factory, Type serviceType, IfAlreadyRegistered ifAlreadyRegistered, object serviceKey)
{
if (_isChangePermitted != IsChangePermitted.Permitted)
return _isChangePermitted == IsChangePermitted.Ignored ? this
: Throw.For(Error.NoMoreRegistrationsAllowed,
serviceType, serviceKey != null ? "with key " + serviceKey : string.Empty, factory);
var serviceTypeHash = RuntimeHelpers.GetHashCode(serviceType);
return factory.FactoryType == FactoryType.Service
? serviceKey == null
? WithDefaultService(factory, serviceTypeHash, serviceType, ifAlreadyRegistered)
: WithKeyedService(factory, serviceTypeHash, serviceType, ifAlreadyRegistered, serviceKey)
: factory.FactoryType == FactoryType.Decorator
? WithDecorators(Decorators.AddOrUpdate(serviceTypeHash, serviceType, factory.One(),
(_, oldf, newf) => oldf.To().Append((Factory[])newf)))
: WithWrappers(Wrappers.AddOrUpdate(serviceTypeHash, serviceType, factory));
}
public Factory[] GetRegisteredFactories(Type serviceType, object serviceKey, FactoryType factoryType)
{
serviceType = serviceType.ThrowIfNull();
switch (factoryType)
{
case FactoryType.Wrapper:
{
// first checking for the explicitly provided say `MyWrapper`
if (Wrappers.GetValueOrDefault(serviceType) is Factory wrapper)
return wrapper.One();
var openGenServiceType = serviceType.GetGenericDefinitionOrNull();
if (openGenServiceType != null &&
Wrappers.GetValueOrDefault(openGenServiceType) is Factory openGenWrapper)
return openGenWrapper.One();
if (serviceType.GetArrayElementTypeOrNull() != null &&
Wrappers.GetValueOrDefault(typeof(IEnumerable<>)) is Factory collectionWrapper)
return collectionWrapper.One();
return null;
}
case FactoryType.Decorator:
{
var decorators = Decorators.GetValueOrDefault(serviceType) as Factory[];
var openGenServiceType = serviceType.GetGenericDefinitionOrNull();
if (openGenServiceType != null)
decorators = decorators.Append(Decorators.GetValueOrDefault(openGenServiceType) as Factory[]);
return decorators;
}
default:
{
var entry = Services.GetValueOrDefault(serviceType);
if (entry == null)
return null;
if (entry is Factory factory)
return serviceKey == null || DefaultKey.Value.Equals(serviceKey) ? factory.One() : null;
var factories = ((FactoriesEntry)entry).Factories;
if (serviceKey == null) // get all the factories
return factories.Visit(new List(), (x, l) => l.Add(x.Value)).ToArray();
return factories.GetValueOrDefault(serviceKey)?.One();
}
}
}
public bool IsRegistered(Type serviceType, object serviceKey, FactoryType factoryType,
Func condition)
{
serviceType = serviceType.ThrowIfNull();
switch (factoryType)
{
case FactoryType.Wrapper:
{
// first checking for the explicitly provided say `MyWrapper`
if (Wrappers.GetValueOrDefault(serviceType) is Factory wrapper &&
(condition == null || condition(wrapper)))
return true;
var openGenServiceType = serviceType.GetGenericDefinitionOrNull();
if (openGenServiceType != null &&
Wrappers.GetValueOrDefault(openGenServiceType) is Factory openGenWrapper &&
(condition == null || condition(openGenWrapper)))
return true;
if (serviceType.GetArrayElementTypeOrNull() != null &&
Wrappers.GetValueOrDefault(typeof(IEnumerable<>)) is Factory collectionWrapper &&
(condition == null || condition(collectionWrapper)))
return true;
return false;
}
case FactoryType.Decorator:
{
if (Decorators.GetValueOrDefault(serviceType) is Factory[] decorators && decorators.Length != 0 &&
(condition == null || decorators.FindFirst(condition) != null))
return true;
var openGenServiceType = serviceType.GetGenericDefinitionOrNull();
if (openGenServiceType != null &&
Decorators.GetValueOrDefault(openGenServiceType) is Factory[] openGenDecorators && openGenDecorators.Length != 0 &&
(condition == null || openGenDecorators.FindFirst(condition) != null))
return true;
return false;
}
default: // services
{
// note: We are not checking the open-generic for the closed-generic service type
// to be able to explicitly understand what registration is available - open or the closed-generic
var entry = Services.GetValueOrDefault(serviceType);
if (entry == null)
return false;
if (entry is Factory factory)
return serviceKey == null || DefaultKey.Value.Equals(serviceKey)
? condition == null || condition(factory)
: false;
var factories = ((FactoriesEntry)entry).Factories;
if (serviceKey == null)
return condition == null || factories.FindFirstOrDefault(f => condition(f.Value)) != null;
factory = factories.GetValueOrDefault(serviceKey);
return factory != null && (condition == null || condition(factory));
}
}
}
public bool ClearCache(int hash, Type serviceType, object serviceKey, FactoryType factoryType)
{
var factories = GetRegisteredFactories(serviceType, serviceKey, factoryType);
if (factories.IsNullOrEmpty())
return false;
for (var i = 0; i < factories.Length; i++)
DropFactoryCache(factories[i], hash, serviceType, serviceKey);
return true;
}
private Registry WithDefaultService(Factory factory, int serviceTypeHash, Type serviceType, IfAlreadyRegistered ifAlreadyRegistered)
{
var services = Services;
object newEntry = factory;
var oldEntry = services.GetValueOrDefault(serviceTypeHash, serviceType);
if (oldEntry != null)
{
switch (ifAlreadyRegistered)
{
case IfAlreadyRegistered.AppendNotKeyed:
newEntry = (oldEntry as FactoriesEntry ?? FactoriesEntry.Empty.With((Factory)oldEntry)).With(factory);
break;
case IfAlreadyRegistered.Throw:
newEntry = oldEntry is FactoriesEntry oldFactoriesEntry && oldFactoriesEntry.LastDefaultKey == null
? oldFactoriesEntry.With(factory)
: Throw.For(Error.UnableToRegisterDuplicateDefault, serviceType, factory, oldEntry);
break;
case IfAlreadyRegistered.Replace:
if (oldEntry is FactoriesEntry facEntryToReplace)
{
if (facEntryToReplace.LastDefaultKey == null)
newEntry = facEntryToReplace.With(factory);
else
{
// remove defaults but keep keyed (issue #569) by collecting the only keyed factories
// and using them in a new factory entry
var keyedFactories = facEntryToReplace.Factories.Fold(
ImHashMap.Empty,
(x, map) => x.Key is DefaultKey == false ? map.AddOrUpdate(x.Key, x.Value) : map);
if (!keyedFactories.IsEmpty)
newEntry = new FactoriesEntry(DefaultKey.Value,
keyedFactories.AddOrUpdate(DefaultKey.Value, factory));
}
}
break;
case IfAlreadyRegistered.AppendNewImplementation:
var oldImplFacsEntry = oldEntry as FactoriesEntry;
if (oldImplFacsEntry != null && oldImplFacsEntry.LastDefaultKey == null)
newEntry = oldImplFacsEntry.With(factory);
else
{
var oldFactory = oldEntry as Factory;
var implementationType = factory.ImplementationType;
if (implementationType == null ||
oldFactory != null && oldFactory.ImplementationType != implementationType)
newEntry = (oldImplFacsEntry ?? FactoriesEntry.Empty.With(oldFactory)).With(factory);
else if (oldImplFacsEntry != null)
{
var isNewImplType = true;
foreach (var f in oldImplFacsEntry.Factories.Enumerate())
if (f.Value.ImplementationType == implementationType)
{
isNewImplType = false;
break;
}
newEntry = isNewImplType
? (oldImplFacsEntry ?? FactoriesEntry.Empty.With(oldFactory)).With(factory)
: oldEntry;
}
}
break;
default: // IfAlreadyRegisteredKeepDefaultService
newEntry = oldEntry is FactoriesEntry oldFacsEntry && oldFacsEntry.LastDefaultKey == null
? oldFacsEntry.With(factory)
: oldEntry;
break;
}
}
// services did not change
if (newEntry == oldEntry)
return this;
var newServices = services.AddOrUpdate(serviceTypeHash, serviceType, newEntry);
var newRegistry = new Registry(newServices, Decorators, Wrappers,
DefaultFactoryCache.Copy(), KeyedFactoryCache.Copy(), FactoryExpressionCache.Copy(), _isChangePermitted);
if (oldEntry != null)
{
if (oldEntry is Factory oldFactory)
newRegistry.DropFactoryCache(oldFactory, serviceTypeHash, serviceType);
else if (oldEntry is FactoriesEntry oldFactoriesEntry && oldFactoriesEntry?.LastDefaultKey != null)
oldFactoriesEntry.Factories.Visit(new{ newRegistry, serviceTypeHash, serviceType }, (x, s) =>
{
if (x.Key is DefaultKey)
s.newRegistry.DropFactoryCache(x.Value, s.serviceTypeHash, s.serviceType);
});
}
return newRegistry;
}
private Registry WithKeyedService(Factory factory, int serviceTypeHash, Type serviceType, IfAlreadyRegistered ifAlreadyRegistered, object serviceKey)
{
object newEntry = null;
var services = Services;
var oldEntry = services.GetValueOrDefault(serviceTypeHash, serviceType);
if (oldEntry != null)
{
switch (ifAlreadyRegistered)
{
case IfAlreadyRegistered.Keep:
if (oldEntry is Factory factoryToKeep)
newEntry = FactoriesEntry.Empty.With(factory, serviceKey).With(factoryToKeep);
else
{
var oldFacs = (FactoriesEntry)oldEntry;
if (oldFacs.Factories.Contains(serviceKey))
return this; // keep the old registry
newEntry = new FactoriesEntry(oldFacs.LastDefaultKey,
oldFacs.Factories.AddOrUpdate(serviceKey, factory));
}
break;
case IfAlreadyRegistered.Replace:
if (oldEntry is Factory factoryToReplace)
newEntry = FactoriesEntry.Empty.With(factory, serviceKey).With(factoryToReplace);
else
newEntry = new FactoriesEntry(((FactoriesEntry)oldEntry).LastDefaultKey,
((FactoriesEntry)oldEntry).Factories.AddOrUpdate(serviceKey, factory));
break;
default:
if (oldEntry is Factory defaultFactory)
newEntry = FactoriesEntry.Empty.With(factory, serviceKey).With(defaultFactory);
else
{
var oldFacs = (FactoriesEntry)oldEntry;
var oldFac = oldFacs.Factories.GetValueOrDefault(serviceKey);
if (oldFac != null)
Throw.It(Error.UnableToRegisterDuplicateKey, serviceKey, serviceKey, oldFac);
newEntry = new FactoriesEntry(oldFacs.LastDefaultKey,
((FactoriesEntry)oldEntry).Factories.AddOrUpdate(serviceKey, factory));
}
break;
}
}
if (newEntry == null)
newEntry = FactoriesEntry.Empty.With(factory, serviceKey);
var newServices = services.AddOrUpdate(serviceTypeHash, serviceType, newEntry);
var newRegistry = new Registry(newServices, Decorators, Wrappers,
DefaultFactoryCache.Copy(), KeyedFactoryCache.Copy(), FactoryExpressionCache.Copy(),
_isChangePermitted);
if (oldEntry != null && ifAlreadyRegistered == IfAlreadyRegistered.Replace &&
oldEntry is FactoriesEntry updatedOldFactories &&
updatedOldFactories.Factories.TryFind(serviceKey, out var droppedFactory))
newRegistry.DropFactoryCache(droppedFactory, serviceTypeHash, serviceType, serviceKey);
return newRegistry;
}
// todo: optimize allocations away
public Registry Unregister(FactoryType factoryType, Type serviceType, object serviceKey, Func condition)
{
if (_isChangePermitted != IsChangePermitted.Permitted)
return _isChangePermitted == IsChangePermitted.Ignored ? this
: Throw.For(Error.NoMoreUnregistrationsAllowed,
serviceType, serviceKey != null ? "with key " + serviceKey : string.Empty, factoryType);
var serviceTypeHash = RuntimeHelpers.GetHashCode(serviceType);
switch (factoryType)
{
case FactoryType.Wrapper:
object removedWrapper = null;
var registry = WithWrappers(Wrappers.Update(serviceTypeHash, serviceType, null, (_, factory, _null) =>
{
if (factory != null && condition != null && !condition((Factory)factory))
return factory;
removedWrapper = factory;
return null;
}));
if (removedWrapper == null)
return this;
registry.DropFactoryCache((Factory)removedWrapper, serviceTypeHash, serviceType);
return registry;
case FactoryType.Decorator:
Factory[] removedDecorators = null;
// todo: minimize allocations in the lambdas below
if (condition == null)
registry = WithDecorators(Decorators.Update(serviceTypeHash, serviceType, null, (_, factories, _null) =>
{
removedDecorators = (Factory[])factories;
return null;
}));
else
registry = WithDecorators(Decorators.Update(serviceTypeHash, serviceType, null, (_, factories, _null) =>
{
removedDecorators = ((Factory[])factories).Match(condition);
return removedDecorators == factories ? null : factories.To().Except(removedDecorators).ToArray();
}));
if (removedDecorators.IsNullOrEmpty())
return this;
for (var i = 0; i < removedDecorators.Length; i++)
registry.DropFactoryCache(removedDecorators[i], serviceTypeHash, serviceType);
return registry;
default:
return UnregisterServiceFactory(serviceType, serviceKey, condition);
}
}
// todo: optimize allocations away
private Registry UnregisterServiceFactory(Type serviceType, object serviceKey = null, Func condition = null)
{
object removed = null; // Factory or FactoriesEntry or Factory[]
ImMap> services;
var hash = RuntimeHelpers.GetHashCode(serviceType);
if (serviceKey == null && condition == null) // simplest case with simplest handling
services = Services.Update(hash, serviceType, null, (_, entry, _null) =>
{
removed = entry;
return null;
});
else
services = Services.Update(hash, serviceType, null, (_, entry, _null) =>
{
if (entry == null)
return null;
if (entry is Factory)
{
if ((serviceKey != null && !DefaultKey.Value.Equals(serviceKey)) ||
(condition != null && !condition((Factory)entry)))
return entry; // keep entry
removed = entry; // otherwise remove it (the only case if serviceKey == DefaultKey.Value)
return null;
}
var factoriesEntry = (FactoriesEntry)entry;
var oldFactories = factoriesEntry.Factories;
var remainingFactories = ImHashMap.Empty;
if (serviceKey == null) // automatically means condition != null
{
// keep factories for which condition is true
remainingFactories = oldFactories.Fold(remainingFactories,
(oldFac, remainingFacs) => condition != null && !condition(oldFac.Value)
? remainingFacs.AddOrUpdate(oldFac.Key, oldFac.Value)
: remainingFacs);
}
else // serviceKey is not default, which automatically means condition == null
{
// set to null factory with specified key if its found
remainingFactories = oldFactories;
var factory = oldFactories.GetValueOrDefault(serviceKey);
if (factory != null)
remainingFactories = oldFactories.Height > 1
? oldFactories.UpdateToDefault(serviceKey.GetHashCode(), serviceKey)
: ImHashMap.Empty;
}
if (remainingFactories.IsEmpty)
{
// if no more remaining factories, then delete the whole entry
removed = entry;
return null;
}
// todo: huh - no perf here?
removed = oldFactories.Enumerate().Except(remainingFactories.Enumerate()).Select(f => f.Value).ToArray();
if (remainingFactories.Height == 1 && DefaultKey.Value.Equals(remainingFactories.Key))
return remainingFactories.Value; // replace entry with single remaining default factory
// update last default key if current default key was removed
var newDefaultKey = factoriesEntry.LastDefaultKey;
if (newDefaultKey != null && remainingFactories.GetValueOrDefault(newDefaultKey) == null)
newDefaultKey = remainingFactories.Enumerate().Select(x => x.Key)
.OfType().OrderByDescending(key => key.RegistrationOrder).FirstOrDefault();
return new FactoriesEntry(newDefaultKey, remainingFactories);
});
if (removed == null)
return this;
var registry = WithServices(services);
var removedFactory = removed as Factory;
if (removedFactory != null)
registry.DropFactoryCache(removedFactory, hash, serviceType, serviceKey);
else
(removed as Factory[] ??
((FactoriesEntry)removed).Factories.Enumerate().Select(f => f.Value).ToArray())
.ForEach(x => registry.DropFactoryCache(x, hash, serviceType, serviceKey));
return registry;
}
internal void DropFactoryCache(Factory factory, int hash, Type serviceType, object serviceKey = null)
{
if (DefaultFactoryCache != null || KeyedFactoryCache != null)
{
if (factory.FactoryGenerator == null)
{
var d = DefaultFactoryCache;
if (d != null)
Ref.Swap(ref d[hash & CACHE_SLOT_COUNT_MASK], hash, serviceType,
(x, h, t) => (x ?? ImMap>.Empty).UpdateToDefault(h, t));
var k = KeyedFactoryCache;
if (k != null)
Ref.Swap(ref k[hash & CACHE_SLOT_COUNT_MASK], hash, serviceType,
(x, h, t) => (x ?? ImMap>.Empty).UpdateToDefault(h, t));
}
else
{
// We cannot remove generated factories, because they are keyed by implementation type and we may remove wrong factory
// a safe alternative is dropping the whole cache
DefaultFactoryCache = null;
KeyedFactoryCache = null;
}
}
if (FactoryExpressionCache != null)
{
var e = FactoryExpressionCache;
if (e != null)
Ref.Swap(ref e[factory.FactoryID & CACHE_SLOT_COUNT_MASK],
factory.FactoryID, (x, i) => (x ?? ImMap.Empty).UpdateToDefault(i));
}
}
public Registry WithNoMoreRegistrationAllowed(bool ignoreInsteadOfThrow) =>
new Registry(Services, Decorators, Wrappers,
DefaultFactoryCache, KeyedFactoryCache, FactoryExpressionCache,
ignoreInsteadOfThrow ? IsChangePermitted.Ignored : IsChangePermitted.Error);
}
private Container(Rules rules, Ref registry, IScope singletonScope,
IScopeContext scopeContext = null, IScope ownCurrentScope = null,
int disposed = 0, StackTrace disposeStackTrace = null,
IResolverContext parent = null)
{
Rules = rules;
_registry = registry;
_singletonScope = singletonScope;
_scopeContext = scopeContext;
_ownCurrentScope = ownCurrentScope;
_disposed = disposed;
_disposeStackTrace = disposeStackTrace;
_parent = parent;
}
private void SetInitialFactoryID()
{
var lastGeneratedId = 0;
GetLastGeneratedFactoryID(ref lastGeneratedId);
if (lastGeneratedId > Factory._lastFactoryID)
Factory._lastFactoryID = lastGeneratedId + 1;
}
#endregion
}
/// Special service key with info about open-generic service type
public sealed class OpenGenericTypeKey : IConvertibleToExpression
{
/// Open-generic required service-type
public readonly Type RequiredServiceType;
/// Optional key
public readonly object ServiceKey;
/// Constructs the thing
public OpenGenericTypeKey(Type requiredServiceType, object serviceKey)
{
RequiredServiceType = requiredServiceType.ThrowIfNull();
ServiceKey = serviceKey;
}
///
public override string ToString() =>
new StringBuilder(nameof(OpenGenericTypeKey)).Append('(')
.Print(RequiredServiceType).Append(", ").Print(ServiceKey)
.Append(')').ToString();
///
public override bool Equals(object obj)
{
var other = obj as OpenGenericTypeKey;
return other != null &&
other.RequiredServiceType == RequiredServiceType &&
Equals(other.ServiceKey, ServiceKey);
}
///
public override int GetHashCode() => Hasher.Combine(RequiredServiceType, ServiceKey);
///
public Expression ToExpression(Func fallbackConverter) =>
New(_ctor, Constant(RequiredServiceType, typeof(Type)), fallbackConverter(ServiceKey));
private static readonly ConstructorInfo _ctor = typeof(OpenGenericTypeKey)
.GetTypeInfo().DeclaredConstructors.First(x => x.GetParameters().Length == 2);
}
///Hides/wraps object with disposable interface.
public sealed class HiddenDisposable
{
internal static ConstructorInfo Ctor = typeof(HiddenDisposable).GetTypeInfo().DeclaredConstructors.First();
internal static FieldInfo ValueField = typeof(HiddenDisposable).GetTypeInfo().GetDeclaredField(nameof(Value));
/// Wrapped value
public readonly object Value;
/// Wraps the value
public HiddenDisposable(object value) { Value = value; }
}
/// Interpreter of expression - where possible uses knowledge of DryIoc internals to avoid reflection
public static class Interpreter
{
/// Calls `TryInterpret` inside try-catch and unwraps/re-throws `ContainerException` from the reflection `TargetInvocationException`
public static bool TryInterpretAndUnwrapContainerException(
IResolverContext r, Expression expr, bool useFec, out object result)
{
try
{
return Interpreter.TryInterpret(r, expr, FactoryDelegateCompiler.ResolverContextParamExpr, r, null, useFec, out result);
}
catch (TargetInvocationException tex) when (tex.InnerException != null)
{
// restore the original exception which is expected by the consumer code
tex.InnerException.TryRethrowWithPreservedStackTrace();
result = null;
return false;
}
}
/// Stores parent lambda params and args
public sealed class ParentLambdaArgs
{
/// Parent or the `null` for the root
public readonly ParentLambdaArgs ParentWithArgs;
/// Params
public readonly object ParamExprs;
/// Args
public readonly object ParamValues;
/// Constructs with parent parent or `null` for the root
public ParentLambdaArgs(ParentLambdaArgs parentWithArgs, object paramExprs, object paramValues)
{
ParentWithArgs = parentWithArgs;
ParamExprs = paramExprs;
ParamValues = paramValues;
}
}
//private static object GetPoolOrNewObjects(object[][] objsPool, int count) =>
// count < 8 ? objsPool[count - 1] ?? new object[count] : new object[count];
//private static void ReturnObjecstsToPool(object[][] objsPool, object[] objs)
//{
// var length = objs.Length;
// if (length < 8)
// objsPool[length - 1] = objs;
//}
/// Interprets passed expression
public static bool TryInterpret(IResolverContext r, Expression expr,
object paramExprs, object paramValues, ParentLambdaArgs parentArgs, bool useFec, out object result
//, object[][] objsPools = null
)
{
result = null;
switch (expr.NodeType)
{
case ExprType.Constant:
{
result = ((ConstantExpression)expr).Value;
return true;
}
case ExprType.New:
{
var newExpr = (NewExpression)expr;
#if SUPPORTS_FAST_EXPRESSION_COMPILER
var fewArgCount = newExpr.FewArgumentCount;
if (fewArgCount >= 0)
{
if (fewArgCount == 0)
{
result = newExpr.Constructor.Invoke(ArrayTools.Empty());
return true;
}
if (fewArgCount == 1)
{
var fewArgs = new object[1];
var fewArgsExpr = ((OneArgumentNewExpression)newExpr).Argument;
if (!TryInterpret(r, fewArgsExpr, paramExprs, paramValues, parentArgs, useFec, out fewArgs[0]))
return false;
result = newExpr.Constructor.Invoke(fewArgs);
return true;
}
if (fewArgCount == 2)
{
var fewArgs = new object[2];
var fewArgsExpr = ((TwoArgumentsNewExpression)newExpr);
if (!TryInterpret(r, fewArgsExpr.Argument0, paramExprs, paramValues, parentArgs, useFec, out fewArgs[0]) ||
!TryInterpret(r, fewArgsExpr.Argument1, paramExprs, paramValues, parentArgs, useFec, out fewArgs[1]))
return false;
result = newExpr.Constructor.Invoke(fewArgs);
return true;
}
if (fewArgCount == 3)
{
var fewArgs = new object[3];
var fewArgsExpr = ((ThreeArgumentsNewExpression)newExpr);
if (!TryInterpret(r, fewArgsExpr.Argument0, paramExprs, paramValues, parentArgs, useFec, out fewArgs[0]) ||
!TryInterpret(r, fewArgsExpr.Argument1, paramExprs, paramValues, parentArgs, useFec, out fewArgs[1]) ||
!TryInterpret(r, fewArgsExpr.Argument2, paramExprs, paramValues, parentArgs, useFec, out fewArgs[2]))
return false;
result = newExpr.Constructor.Invoke(fewArgs);
return true;
}
if (fewArgCount == 4)
{
var fewArgs = new object[4];
var fewArgsExpr = ((FourArgumentsNewExpression)newExpr);
if (!TryInterpret(r, fewArgsExpr.Argument0, paramExprs, paramValues, parentArgs, useFec, out fewArgs[0]) ||
!TryInterpret(r, fewArgsExpr.Argument1, paramExprs, paramValues, parentArgs, useFec, out fewArgs[1]) ||
!TryInterpret(r, fewArgsExpr.Argument2, paramExprs, paramValues, parentArgs, useFec, out fewArgs[2]) ||
!TryInterpret(r, fewArgsExpr.Argument3, paramExprs, paramValues, parentArgs, useFec, out fewArgs[3]))
return false;
result = newExpr.Constructor.Invoke(fewArgs);
return true;
}
if (fewArgCount == 5)
{
var fewArgs = new object[5];
var fewArgsExpr = ((FiveArgumentsNewExpression)newExpr);
if (!TryInterpret(r, fewArgsExpr.Argument0, paramExprs, paramValues, parentArgs, useFec, out fewArgs[0]) ||
!TryInterpret(r, fewArgsExpr.Argument1, paramExprs, paramValues, parentArgs, useFec, out fewArgs[1]) ||
!TryInterpret(r, fewArgsExpr.Argument2, paramExprs, paramValues, parentArgs, useFec, out fewArgs[2]) ||
!TryInterpret(r, fewArgsExpr.Argument3, paramExprs, paramValues, parentArgs, useFec, out fewArgs[3]) ||
!TryInterpret(r, fewArgsExpr.Argument4, paramExprs, paramValues, parentArgs, useFec, out fewArgs[4]))
return false;
result = newExpr.Constructor.Invoke(fewArgs);
return true;
}
}
#endif
var newArgs = newExpr.Arguments.ToListOrSelf();
var newArgCount = newArgs.Count;
if (newArgCount == 0)
result = newExpr.Constructor.Invoke(ArrayTools.Empty());
else
{
var args = new object[newArgCount];
for (var i = 0; i < args.Length; i++)
if (!TryInterpret(r, newArgs[i], paramExprs, paramValues, parentArgs, useFec, out args[i]))
return false;
result = newExpr.Constructor.Invoke(args);
}
return true;
}
case ExprType.Call:
{
return TryInterpretMethodCall(r, expr, paramExprs, paramValues, parentArgs, useFec, ref result);
}
case ExprType.Convert:
{
var convertExpr = (UnaryExpression)expr;
if (!TryInterpret(r, convertExpr.Operand, paramExprs, paramValues, parentArgs, useFec, out var instance))
return false;
// skip conversion for null and for directly assignable type
if (instance == null || instance.GetType().IsAssignableTo(convertExpr.Type))
result = instance;
else
result = Converter.ConvertWithOperator(instance, convertExpr.Type, expr);
return true;
}
case ExprType.MemberAccess:
{
var memberExpr = (MemberExpression)expr;
var instanceExpr = memberExpr.Expression;
object instance = null;
if (instanceExpr != null && !TryInterpret(r, instanceExpr, paramExprs, paramValues, parentArgs, useFec, out instance))
return false;
if (memberExpr.Member is FieldInfo field)
{
result = field.GetValue(instance);
return true;
}
if (memberExpr.Member is PropertyInfo prop)
{
result = prop.GetValue(instance, null);
return true;
}
return false;
}
case ExprType.MemberInit:
{
var memberInit = (MemberInitExpression)expr;
if (!TryInterpret(r, memberInit.NewExpression, paramExprs, paramValues, parentArgs, useFec, out var instance))
return false;
var bindings = memberInit.Bindings;
for (var i = 0; i < bindings.Count; i++)
{
var binding = (MemberAssignment)bindings[i];
if (!TryInterpret(r, binding.Expression, paramExprs, paramValues, parentArgs, useFec, out var memberValue))
return false;
var field = binding.Member as FieldInfo;
if (field != null)
field.SetValue(instance, memberValue);
else
((PropertyInfo)binding.Member).SetValue(instance, memberValue, null);
}
result = instance;
return true;
}
case ExprType.NewArrayInit:
{
var newArray = (NewArrayExpression)expr;
var itemExprs = newArray.Expressions.ToListOrSelf();
var items = new object[itemExprs.Count];
for (var i = 0; i < items.Length; i++)
if (!TryInterpret(r, itemExprs[i], paramExprs, paramValues, parentArgs, useFec, out items[i]))
return false;
result = Converter.ConvertMany(items, newArray.Type.GetElementType());
return true;
}
case ExprType.Invoke:
{
var invokeExpr = (InvocationExpression)expr;
var delegateExpr = invokeExpr.Expression;
// The majority of cases the delegate will be a well known `FactoryDelegate` - so calling it directly
if (delegateExpr.Type == typeof(FactoryDelegate) &&
delegateExpr is ConstantExpression delegateConstExpr)
{
if (!TryInterpret(r, invokeExpr.Arguments[0], paramExprs, paramValues, parentArgs, useFec, out var resolver))
return false;
result = ((FactoryDelegate)delegateConstExpr.Value)((IResolverContext)resolver);
return true;
}
#if !SUPPORTS_DELEGATE_METHOD
return false;
#else
if (!TryInterpret(r, delegateExpr, paramExprs, paramValues, parentArgs, useFec, out var delegateObj))
return false;
var lambda = (Delegate)delegateObj;
var argExprs = invokeExpr.Arguments.ToListOrSelf();
if (argExprs.Count == 0)
result = lambda.GetMethodInfo().Invoke(lambda.Target, ArrayTools.Empty());
else
{
var args = new object[argExprs.Count];
for (var i = 0; i < args.Length; i++)
if (!TryInterpret(r, argExprs[i], paramExprs, paramValues, parentArgs, useFec, out args[i]))
return false;
result = lambda.GetMethodInfo().Invoke(lambda.Target, args);
}
return true;
#endif
}
case ExprType.Parameter:
{
if (expr == paramExprs)
{
result = paramValues;
return true;
}
if (paramExprs is IList multipleParams)
for (var i = 0; i < multipleParams.Count; i++)
if (expr == multipleParams[i])
{
result = ((object[])paramValues)[i];
return true;
}
if (parentArgs != null)
{
for (var p = parentArgs; p != null; p = p.ParentWithArgs)
{
if (expr == p.ParamExprs)
{
result = p.ParamValues;
return true;
}
multipleParams = p.ParamExprs as IList;
if (multipleParams != null)
{
for (var i = 0; i < multipleParams.Count; i++)
if (expr == multipleParams[i])
{
result = ((object[])paramValues)[i];
return true;
}
}
}
}
return false;
}
case ExprType.Lambda:
{
return TryInterpretNestedLambda(r, (LambdaExpression)expr, paramExprs, paramValues, parentArgs, useFec, ref result);
}
default:
break;
}
return false;
}
private static bool TryInterpretNestedLambda(IResolverContext r, LambdaExpression lambdaExpr,
object paramExprs, object paramValues, ParentLambdaArgs parentArgs, bool useFec, ref object result)
{
#if SUPPORTS_FAST_EXPRESSION_COMPILER
var returnType = lambdaExpr.ReturnType;
#else
var returnType = lambdaExpr.Type.GetTypeInfo().GetDeclaredMethod("Invoke").ReturnType;
#endif
if (paramExprs != null)
parentArgs = new ParentLambdaArgs(parentArgs, paramExprs, paramValues);
var bodyExpr = lambdaExpr.Body;
var lambdaParams = lambdaExpr.Parameters;
var paramCount = lambdaParams.Count;
if (paramCount == 0)
{
if (returnType != typeof(void))
{
result = new Func(() => TryInterpretNestedLambdaBodyAndUnwrapException(r, bodyExpr, null, null, parentArgs, useFec));
if (returnType != typeof(object))
result = _convertFuncMethod.MakeGenericMethod(returnType).Invoke(null, new[] { result });
}
else
{
result = new Action(() => TryInterpretNestedLambdaBodyAndUnwrapException(r, bodyExpr, null, null, parentArgs, useFec));
}
}
else if (paramCount == 1)
{
var paramExpr = lambdaParams[0];
if (returnType != typeof(void))
{
result = new Func(arg => TryInterpretNestedLambdaBodyAndUnwrapException(r, bodyExpr, paramExpr, arg, parentArgs, useFec));
if (paramExpr.Type != typeof(object) || returnType != typeof(object))
result = _convertOneArgFuncMethod.MakeGenericMethod(paramExpr.Type, returnType).Invoke(null, new[] { result });
}
else
{
result = new Action(arg => TryInterpretNestedLambdaBodyAndUnwrapException(r, bodyExpr, paramExpr, arg, parentArgs, useFec));
if (paramExpr.Type != typeof(object))
result = _convertOneArgActionMethod.MakeGenericMethod(paramExpr.Type).Invoke(null, new[] { result });
}
}
else if (paramCount == 2)
{
var paramExpr0 = lambdaParams[0];
var paramExpr1 = lambdaParams[1];
if (returnType != typeof(void))
{
result = new Func((arg0, arg1) =>
TryInterpretNestedLambdaBodyAndUnwrapException(r, bodyExpr, lambdaParams, new[] { arg0, arg1 }, parentArgs, useFec));
if (paramExpr0.Type != typeof(object) || paramExpr1.Type != typeof(object) || returnType != typeof(object))
result = _convertTwoArgFuncMethod.MakeGenericMethod(paramExpr0.Type, paramExpr1.Type, returnType).Invoke(null, new[] { result });
}
else
{
result = new Action((arg0, arg1) =>
TryInterpretNestedLambdaBodyAndUnwrapException(r, bodyExpr, lambdaParams, new[] { arg0, arg1 }, parentArgs, useFec));
if (paramExpr0.Type != typeof(object) || paramExpr1.Type != typeof(object))
result = _convertTwoArgActionMethod.MakeGenericMethod(paramExpr0.Type, paramExpr1.Type).Invoke(null, new[] { result });
}
}
else if (paramCount == 3)
{
var paramExpr0 = lambdaParams[0];
var paramExpr1 = lambdaParams[1];
var paramExpr2 = lambdaParams[2];
if (returnType != typeof(void))
{
result = new Func(args =>
TryInterpretNestedLambdaBodyAndUnwrapException(r, bodyExpr, lambdaParams, args, parentArgs, useFec));
result = _convertThreeArgFuncMethod.MakeGenericMethod(paramExpr0.Type, paramExpr1.Type, paramExpr2.Type, returnType)
.Invoke(null, new[] { result });
}
else
{
result = new Action(args =>
TryInterpretNestedLambdaBodyAndUnwrapException(r, bodyExpr, lambdaParams, args, parentArgs, useFec));
result = _convertThreeArgActionMethod.MakeGenericMethod(paramExpr0.Type, paramExpr1.Type, paramExpr2.Type)
.Invoke(null, new[] { result });
}
}
else if (paramCount == 4)
{
var paramExpr0 = lambdaParams[0];
var paramExpr1 = lambdaParams[1];
var paramExpr2 = lambdaParams[2];
var paramExpr3 = lambdaParams[3];
if (returnType != typeof(void))
{
result = new Func(args =>
TryInterpretNestedLambdaBodyAndUnwrapException(r, bodyExpr, lambdaParams, args, parentArgs, useFec));
result = _convertFourArgFuncMethod
.MakeGenericMethod(paramExpr0.Type, paramExpr1.Type, paramExpr2.Type, paramExpr3.Type, returnType)
.Invoke(null, new[] {result});
}
else
{
result = new Action(args =>
TryInterpretNestedLambdaBodyAndUnwrapException(r, bodyExpr, lambdaParams, args, parentArgs, useFec));
result = _convertFourArgActionMethod
.MakeGenericMethod(paramExpr0.Type, paramExpr1.Type, paramExpr2.Type, paramExpr3.Type)
.Invoke(null, new[] {result});
}
}
else
return false;
var resultType = result.GetType();
var lambdaType = lambdaExpr.Type;
if ((resultType.GetGenericDefinitionOrNull() ?? resultType) != (lambdaType.GetGenericDefinitionOrNull() ?? lambdaType))
{
#if SUPPORTS_DELEGATE_METHOD
result = ((Delegate)result).GetMethodInfo().CreateDelegate(lambdaType, ((Delegate)result).Target);
#else
return false;
#endif
}
return true;
}
private static object TryInterpretNestedLambdaBodyAndUnwrapException(IResolverContext r,
Expression bodyExpr, object paramExprs, object paramValues, ParentLambdaArgs parentArgs, bool useFec)
{
try
{
if (!TryInterpret(r, bodyExpr, paramExprs, paramValues, parentArgs, useFec, out var lambdaResult))
Throw.It(Error.UnableToInterpretTheNestedLambda, bodyExpr);
return lambdaResult;
}
catch (TargetInvocationException tex) when (tex.InnerException != null)
{
// restore the original excpetion which is expected by the consumer code
throw tex.InnerException;
}
}
internal static Func ConvertFunc(Func f) => () => (R)f();
private static readonly MethodInfo _convertFuncMethod = typeof(Interpreter).GetTypeInfo().GetDeclaredMethod(nameof(ConvertFunc));
internal static Func ConvertOneArgFunc(Func f) => a => (R)f(a);
private static readonly MethodInfo _convertOneArgFuncMethod = typeof(Interpreter).GetTypeInfo().GetDeclaredMethod(nameof(ConvertOneArgFunc));
internal static Action ConvertOneArgAction(Action f) => a => f(a);
private static readonly MethodInfo _convertOneArgActionMethod = typeof(Interpreter).GetTypeInfo().GetDeclaredMethod(nameof(ConvertOneArgAction));
internal static Func ConvertTwoArgFunc(Func f) => (a0, a1) => (R)f(a0, a1);
private static readonly MethodInfo _convertTwoArgFuncMethod = typeof(Interpreter).GetTypeInfo().GetDeclaredMethod(nameof(ConvertTwoArgFunc));
internal static Action ConvertTwoArgAction(Action f) => (a0, a1) => f(a0, a1);
private static readonly MethodInfo _convertTwoArgActionMethod = typeof(Interpreter).GetTypeInfo().GetDeclaredMethod(nameof(ConvertTwoArgAction));
internal static Func ConvertThreeArgFunc(Func f) => (a0, a1, a2) => (R)f(new object[] {a0, a1, a2});
private static readonly MethodInfo _convertThreeArgFuncMethod = typeof(Interpreter).GetTypeInfo().GetDeclaredMethod(nameof(ConvertThreeArgFunc));
internal static Action ConvertThreeArgAction(Action f) => (a0, a1, a2) => f(new object[] {a0, a1, a2});
private static readonly MethodInfo _convertThreeArgActionMethod = typeof(Interpreter).GetTypeInfo().GetDeclaredMethod(nameof(ConvertThreeArgAction));
internal static Func ConvertFourArgFunc(Func f) => (a0, a1, a2, a3) => (R)f(new object[] { a0, a1, a2, a3 });
private static readonly MethodInfo _convertFourArgFuncMethod = typeof(Interpreter).GetTypeInfo().GetDeclaredMethod(nameof(ConvertFourArgFunc));
internal static Action ConvertFourArgAction(Action f) => (a0, a1, a2, a3) => f(new object[] { a0, a1, a2, a3 });
private static readonly MethodInfo _convertFourArgActionMethod = typeof(Interpreter).GetTypeInfo().GetDeclaredMethod(nameof(ConvertFourArgAction));
private static bool TryInterpretMethodCall(IResolverContext r, Expression expr,
object paramExprs, object paramValues, ParentLambdaArgs parentArgs, bool useFec, ref object result)
{
if (ReferenceEquals(expr, ResolverContext.RootOrSelfExpr))
{
result = r.Root ?? r;
return true;
}
var callExpr = (MethodCallExpression)expr;
var method = callExpr.Method;
var methodDeclaringType = method.DeclaringType;
if (methodDeclaringType == typeof(CurrentScopeReuse))
{
if (method == CurrentScopeReuse.GetScopedViaFactoryDelegateNoDisposalIndexMethod)
{
result = InterpretGetScopedViaFactoryDelegateNoDisposalIndex(r, callExpr, paramExprs, paramValues, parentArgs, useFec);
return true;
}
if (method == CurrentScopeReuse.GetScopedViaFactoryDelegateMethod)
{
result = InterpretGetScopedViaFactoryDelegate(r, callExpr, paramExprs, paramValues, parentArgs, useFec);
return true;
}
if (method == CurrentScopeReuse.GetNameScopedViaFactoryDelegateMethod)
{
result = InterpretGetNameScopedViaFactoryDelegate(r, callExpr, paramExprs, paramValues, parentArgs, useFec);
return true;
}
if (method == CurrentScopeReuse.GetScopedOrSingletonViaFactoryDelegateMethod)
{
result = InterpretGetScopedOrSingletonViaFactoryDelegate(r, callExpr, paramExprs, paramValues, parentArgs, useFec);
return true;
}
var callArgs = callExpr.Arguments.ToListOrSelf();
var resolver = r;
if (!ReferenceEquals(callArgs[0], FactoryDelegateCompiler.ResolverContextParamExpr))
{
if (!TryInterpret(resolver, callArgs[0], paramExprs, paramValues, parentArgs, useFec, out var resolverObj))
return false;
resolver = (IResolverContext)resolverObj;
}
if (method == CurrentScopeReuse.TrackScopedOrSingletonMethod)
{
if (!TryInterpret(resolver, callArgs[1], paramExprs, paramValues, parentArgs, useFec, out var service))
return false;
result = CurrentScopeReuse.TrackScopedOrSingleton(resolver, service);
return true;
}
if (method == CurrentScopeReuse.TrackScopedMethod)
{
var scope = resolver.GetCurrentScope((bool)((ConstantExpression)callArgs[1]).Value);
if (scope == null)
result = null; // result is null in this case
else
{
if (!TryInterpret(resolver, callArgs[2], paramExprs, paramValues, parentArgs, useFec, out var service))
return false;
result = scope.TrackDisposable(service /* todo: what is with `disposalOrder`*/);
}
return true;
}
if (method == CurrentScopeReuse.TrackNameScopedMethod)
{
var scope = resolver.GetNamedScope(ConstValue(callArgs[1]), (bool)ConstValue(callArgs[2]));
if (scope == null)
result = null; // result is null in this case
else
{
if (!TryInterpret(resolver, callArgs[3], paramExprs, paramValues, parentArgs, useFec, out var service))
return false;
result = scope.TrackDisposable(service);
}
return true;
}
}
else if (methodDeclaringType == typeof(IScope))
{
var callArgs = callExpr.Arguments.ToListOrSelf();
if (method == Scope.GetOrAddViaFactoryDelegateMethod)
{
r = r.Root ?? r;
// check if scoped dependency is already in scope, then just return it
var factoryId = (int) ConstValue(callArgs[0]);
if (!r.SingletonScope.TryGet(out result, factoryId))
{
result = r.SingletonScope.TryGetOrAddWithoutClosure(factoryId, r,
((LambdaExpression) callArgs[1]).Body, useFec,
(rc, e, uf) =>
{
if (TryInterpret(rc, e, paramExprs, paramValues, parentArgs, uf, out var value))
return value;
return e.CompileToFactoryDelegate(uf, ((IContainer) rc).Rules.UseInterpretation)(rc);
},
(int) ConstValue(callArgs[3]));
}
return true;
}
if (method == Scope.TrackDisposableMethod)
{
r = r.Root ?? r;
if (!TryInterpret(r, callArgs[0], paramExprs, paramValues, parentArgs, useFec, out var service))
return false;
result = r.SingletonScope.TrackDisposable(service, (int) ConstValue(callArgs[1]));
return true;
}
}
else if (methodDeclaringType == typeof(IResolver))
{
var resolver = r;
if (!ReferenceEquals(callExpr.Object, FactoryDelegateCompiler.ResolverContextParamExpr))
{
if (!TryInterpret(resolver, callExpr.Object, paramExprs, paramValues, parentArgs, useFec, out var resolverObj))
return false;
resolver = (IResolverContext)resolverObj;
}
var callArgs = callExpr.Arguments.ToListOrSelf();
if (method == Resolver.ResolveFastMethod)
{
result = resolver.Resolve((Type) ConstValue(callArgs[0]), (IfUnresolved) ConstValue(callArgs[1]));
return true;
}
if (method == Resolver.ResolveMethod)
{
object serviceKey = null, preResolveParent = null, resolveArgs = null;
if (!TryInterpret(resolver, callArgs[1], paramExprs, paramValues, parentArgs, useFec, out serviceKey) ||
!TryInterpret(resolver, callArgs[4], paramExprs, paramValues, parentArgs, useFec, out preResolveParent) ||
!TryInterpret(resolver, callArgs[5], paramExprs, paramValues, parentArgs, useFec, out resolveArgs))
return false;
result = resolver.Resolve((Type) ConstValue(callArgs[0]), serviceKey,
(IfUnresolved) ConstValue(callArgs[2]),
(Type) ConstValue(callArgs[3]), (Request) preResolveParent, (object[]) resolveArgs);
return true;
}
if (method == Resolver.ResolveManyMethod)
{
object serviceKey = null, preResolveParent = null, resolveArgs = null;
if (!TryInterpret(resolver, callArgs[1], paramExprs, paramValues, parentArgs, useFec, out serviceKey) ||
!TryInterpret(resolver, callArgs[3], paramExprs, paramValues, parentArgs, useFec, out preResolveParent) ||
!TryInterpret(resolver, callArgs[4], paramExprs, paramValues, parentArgs, useFec, out resolveArgs))
return false;
result = resolver.ResolveMany((Type) ConstValue(callArgs[0]), serviceKey, (Type) ConstValue(callArgs[2]),
(Request) preResolveParent, (object[]) resolveArgs);
return true;
}
}
// fallback to reflection invocation
object instance = null;
var callObjectExpr = callExpr.Object;
if (callObjectExpr != null && !TryInterpret(r, callObjectExpr, paramExprs, paramValues, parentArgs, useFec, out instance))
return false;
#if SUPPORTS_FAST_EXPRESSION_COMPILER
var fewArgCount = callExpr.FewArgumentCount;
if (fewArgCount >= 0)
{
if (fewArgCount == 0)
{
result = callExpr.Method.Invoke(instance, ArrayTools.Empty());
return true;
}
if (fewArgCount == 1)
{
var fewArgs = new object[1];
var fewArgsExpr = ((OneArgumentMethodCallExpression)callExpr).Argument;
if (!TryInterpret(r, fewArgsExpr, paramExprs, paramValues, parentArgs, useFec, out fewArgs[0]))
return false;
result = callExpr.Method.Invoke(instance, fewArgs);
return true;
}
if (fewArgCount == 2)
{
var fewArgs = new object[2];
var fewArgsExpr = ((TwoArgumentsMethodCallExpression)callExpr);
if (!TryInterpret(r, fewArgsExpr.Argument0, paramExprs, paramValues, parentArgs, useFec, out fewArgs[0]) ||
!TryInterpret(r, fewArgsExpr.Argument1, paramExprs, paramValues, parentArgs, useFec, out fewArgs[1]))
return false;
result = callExpr.Method.Invoke(instance, fewArgs);
return true;
}
if (fewArgCount == 3)
{
var fewArgs = new object[3];
var fewArgsExpr = ((ThreeArgumentsMethodCallExpression)callExpr);
if (!TryInterpret(r, fewArgsExpr.Argument0, paramExprs, paramValues, parentArgs, useFec, out fewArgs[0]) ||
!TryInterpret(r, fewArgsExpr.Argument1, paramExprs, paramValues, parentArgs, useFec, out fewArgs[1]) ||
!TryInterpret(r, fewArgsExpr.Argument2, paramExprs, paramValues, parentArgs, useFec, out fewArgs[2]))
return false;
result = callExpr.Method.Invoke(instance, fewArgs);
return true;
}
if (fewArgCount == 4)
{
var fewArgs = new object[4];
var fewArgsExpr = ((FourArgumentsMethodCallExpression)callExpr);
if (!TryInterpret(r, fewArgsExpr.Argument0, paramExprs, paramValues, parentArgs, useFec, out fewArgs[0]) ||
!TryInterpret(r, fewArgsExpr.Argument1, paramExprs, paramValues, parentArgs, useFec, out fewArgs[1]) ||
!TryInterpret(r, fewArgsExpr.Argument2, paramExprs, paramValues, parentArgs, useFec, out fewArgs[2]) ||
!TryInterpret(r, fewArgsExpr.Argument3, paramExprs, paramValues, parentArgs, useFec, out fewArgs[3]))
return false;
result = callExpr.Method.Invoke(instance, fewArgs);
return true;
}
if (fewArgCount == 5)
{
var fewArgs = new object[5];
var fewArgsExpr = ((FiveArgumentsMethodCallExpression)callExpr);
if (!TryInterpret(r, fewArgsExpr.Argument0, paramExprs, paramValues, parentArgs, useFec, out fewArgs[0]) ||
!TryInterpret(r, fewArgsExpr.Argument1, paramExprs, paramValues, parentArgs, useFec, out fewArgs[1]) ||
!TryInterpret(r, fewArgsExpr.Argument2, paramExprs, paramValues, parentArgs, useFec, out fewArgs[2]) ||
!TryInterpret(r, fewArgsExpr.Argument3, paramExprs, paramValues, parentArgs, useFec, out fewArgs[3]) ||
!TryInterpret(r, fewArgsExpr.Argument4, paramExprs, paramValues, parentArgs, useFec, out fewArgs[4]))
return false;
result = callExpr.Method.Invoke(instance, fewArgs);
return true;
}
}
#endif
var args = callExpr.Arguments.ToListOrSelf();
var callArgCount = args.Count;
if (callArgCount == 0)
result = method.Invoke(instance, ArrayTools.Empty());
else
{
var argObjects = new object[callArgCount];
for (var i = 0; i < argObjects.Length; i++)
if (!TryInterpret(r, args[i], paramExprs, paramValues, parentArgs, useFec, out argObjects[i]))
return false;
result = method.Invoke(instance, argObjects);
}
return true;
}
private static object InterpretGetScopedViaFactoryDelegateNoDisposalIndex(IResolverContext resolver,
MethodCallExpression callExpr, object paramExprs, object paramValues, ParentLambdaArgs parentArgs, bool useFec)
{
#if SUPPORTS_FAST_EXPRESSION_COMPILER
var fewArgExpr = (FourArgumentsMethodCallExpression)callExpr;
var resolverArg = fewArgExpr.Argument0;
#else
var args = callExpr.Arguments.ToListOrSelf();
var resolverArg = args[0];
#endif
if (!ReferenceEquals(resolverArg, FactoryDelegateCompiler.ResolverContextParamExpr))
{
if (!TryInterpret(resolver, resolverArg, paramExprs, paramValues, parentArgs, useFec, out var resolverObj))
return false;
resolver = (IResolverContext)resolverObj;
}
var scope = (Scope)resolver.CurrentScope;
if (scope == null)
{
#if SUPPORTS_FAST_EXPRESSION_COMPILER
var throwIfNoScopeArg = fewArgExpr.Argument1;
#else
var throwIfNoScopeArg = args[1];
#endif
return (bool)((ConstantExpression)throwIfNoScopeArg).Value ? Throw.For(Error.NoCurrentScope, resolver) : null;
}
#if SUPPORTS_FAST_EXPRESSION_COMPILER
var factoryIdArg = fewArgExpr.Argument2;
#else
var factoryIdArg = args[2];
#endif
var id = (int)((ConstantExpression)factoryIdArg).Value;
ref var map = ref scope._maps[id & Scope.MAP_COUNT_SUFFIX_MASK];
var itemRef = map.GetEntryOrDefault(id);
if (itemRef != null && itemRef.Value != Scope.NoItem)
return itemRef.Value;
if (scope.IsDisposed)
Throw.It(Error.ScopeIsDisposed, scope.ToString());
// add only, keep old item if it already exists
var m = map;
if (Interlocked.CompareExchange(ref map, m.AddOrKeep(id, Scope.NoItem), m) != m)
Ref.Swap(ref map, id, (x, i) => x.AddOrKeep(i, Scope.NoItem));
itemRef = map.GetEntryOrDefault(id);
if (itemRef.Value == Scope.NoItem)
{
#if SUPPORTS_FAST_EXPRESSION_COMPILER
var lambdaArg = fewArgExpr.Argument3;
#else
var lambdaArg = args[3];
#endif
object result = null;
lock (itemRef)
{
if (itemRef.Value != Scope.NoItem)
return itemRef.Value;
if (lambdaArg is ConstantExpression lambdaConstExpr)
result = ((FactoryDelegate)lambdaConstExpr.Value)(resolver);
else
{
var body = ((LambdaExpression)lambdaArg).Body;
if (!TryInterpret(resolver, body, paramExprs, paramValues, parentArgs, useFec, out result))
result = body.CompileToFactoryDelegate(useFec, ((IContainer)resolver).Rules.UseInterpretation)(resolver);
}
itemRef.Value = result;
}
if (result is IDisposable disp && disp != scope)
scope.AddUnorderedDisposable(disp);
}
return itemRef.Value;
}
private static object InterpretGetScopedViaFactoryDelegate(IResolverContext resolver,
MethodCallExpression callExpr, object paramExprs, object paramValues, ParentLambdaArgs parentArgs, bool useFec)
{
#if SUPPORTS_FAST_EXPRESSION_COMPILER
var fewArgExpr = (FiveArgumentsMethodCallExpression)callExpr;
var resolverArg = fewArgExpr.Argument0;
#else
var args = callExpr.Arguments.ToListOrSelf();
var resolverArg = args[0];
#endif
if (!ReferenceEquals(resolverArg, FactoryDelegateCompiler.ResolverContextParamExpr))
{
if (!TryInterpret(resolver, resolverArg, paramExprs, paramValues, parentArgs, useFec, out var resolverObj))
return false;
resolver = (IResolverContext)resolverObj;
}
var scope = (Scope)resolver.CurrentScope;
if (scope == null)
{
#if SUPPORTS_FAST_EXPRESSION_COMPILER
var throwIfNoScopeArg = fewArgExpr.Argument1;
#else
var throwIfNoScopeArg = args[1];
#endif
return (bool)((ConstantExpression)throwIfNoScopeArg).Value ? Throw.For(Error.NoCurrentScope, resolver) : null;
}
#if SUPPORTS_FAST_EXPRESSION_COMPILER
var factoryIdArg = fewArgExpr.Argument2;
#else
var factoryIdArg = args[2];
#endif
var id = (int)((ConstantExpression)factoryIdArg).Value;
ref var map = ref scope._maps[id & Scope.MAP_COUNT_SUFFIX_MASK];
var itemRef = map.GetEntryOrDefault(id);
if (itemRef != null && itemRef.Value != Scope.NoItem)
return itemRef.Value;
if (scope.IsDisposed)
Throw.It(Error.ScopeIsDisposed, scope.ToString());
// add only, keep old item if it already exists
var m = map;
if (Interlocked.CompareExchange(ref map, m.AddOrKeep(id, Scope.NoItem), m) != m)
Ref.Swap(ref map, id, (x, i) => x.AddOrKeep(i, Scope.NoItem));
itemRef = map.GetEntryOrDefault(id);
if (itemRef.Value == Scope.NoItem)
{
#if SUPPORTS_FAST_EXPRESSION_COMPILER
var lambdaArg = fewArgExpr.Argument3;
#else
var lambdaArg = args[3];
#endif
object result = null;
lock (itemRef)
{
if (itemRef.Value != Scope.NoItem)
return itemRef.Value;
if (lambdaArg is ConstantExpression lambdaConstExpr)
result = ((FactoryDelegate)lambdaConstExpr.Value)(resolver);
else if (!TryInterpret(resolver, ((LambdaExpression)lambdaArg).Body, paramExprs, paramValues, parentArgs, useFec, out result))
result = ((LambdaExpression)lambdaArg).Body.CompileToFactoryDelegate(useFec,
((IContainer)resolver).Rules.UseInterpretation)(resolver);
itemRef.Value = result;
}
if (result is IDisposable disp && disp != scope)
{
#if SUPPORTS_FAST_EXPRESSION_COMPILER
var disposalOrderArg = fewArgExpr.Argument4;
#else
var disposalOrderArg = args[4];
#endif
var disposalOrder = (int)((ConstantExpression)disposalOrderArg).Value;
if (disposalOrder == 0)
scope.AddUnorderedDisposable(disp);
else
scope.AddDisposable(disp, disposalOrder);
}
}
return itemRef.Value;
}
private static object InterpretGetNameScopedViaFactoryDelegate(IResolverContext r,
MethodCallExpression callExpr, object paramExprs, object paramValues, ParentLambdaArgs parentArgs, bool useFec)
{
var args = callExpr.Arguments.ToListOrSelf();
if (!ReferenceEquals(args[0], FactoryDelegateCompiler.ResolverContextParamExpr))
{
if (!TryInterpret(r, args[0], paramExprs, paramValues, parentArgs, useFec, out var resolverObj))
return false;
r = (IResolverContext)resolverObj;
}
var scope = (Scope)r.GetNamedScope(((ConstantExpression)args[1]).Value, (bool)((ConstantExpression)args[2]).Value);
if (scope == null)
return null; // result is null in this case
if (scope.IsDisposed)
Throw.It(Error.ScopeIsDisposed, scope.ToString());
// check if scoped dependency is already in scope, then just return it
var id = (int)((ConstantExpression)args[3]).Value;
ref var map = ref scope._maps[id & Scope.MAP_COUNT_SUFFIX_MASK];
var itemRef = map.GetEntryOrDefault(id);
if (itemRef != null && itemRef.Value != Scope.NoItem)
return itemRef.Value;
// add only, keep old item if it already exists
var m = map;
if (Interlocked.CompareExchange(ref map, m.AddOrKeep(id, Scope.NoItem), m) != m)
Ref.Swap(ref map, id, (x, i) => x.AddOrKeep(i, Scope.NoItem));
itemRef = map.GetEntryOrDefault(id);
if (itemRef.Value == Scope.NoItem)
{
var lambda = args[4];
object result = null;
lock (itemRef)
{
if (itemRef.Value != Scope.NoItem)
return itemRef.Value;
if (lambda is ConstantExpression lambdaConstExpr)
result = ((FactoryDelegate)lambdaConstExpr.Value)(r);
else if (!TryInterpret(r, ((LambdaExpression)lambda).Body, paramExprs, paramValues, parentArgs, useFec, out result))
result = ((LambdaExpression)lambda).Body.CompileToFactoryDelegate(useFec,
((IContainer)r).Rules.UseInterpretation)(r);
itemRef.Value = result;
}
if (result is IDisposable disp && disp != scope)
{
var disposalOrder = (int)((ConstantExpression)args[5]).Value;
if (disposalOrder == 0)
scope.AddUnorderedDisposable(disp);
else
scope.AddDisposable(disp, disposalOrder);
}
}
return itemRef.Value;
}
private static object InterpretGetScopedOrSingletonViaFactoryDelegate(IResolverContext r,
MethodCallExpression callExpr, object paramExprs, object paramValues, ParentLambdaArgs parentArgs, bool useFec)
{
#if SUPPORTS_FAST_EXPRESSION_COMPILER
var fewArgExpr = (FourArgumentsMethodCallExpression)callExpr;
var resolverArg = fewArgExpr.Argument0;
#else
var args = callExpr.Arguments.ToListOrSelf();
var resolverArg = args[0];
#endif
if (!ReferenceEquals(resolverArg, FactoryDelegateCompiler.ResolverContextParamExpr))
{
if (!TryInterpret(r, resolverArg, paramExprs, paramValues, parentArgs, useFec, out var resolverObj))
return false;
r = (IResolverContext)resolverObj;
}
var scope = (Scope)(r.CurrentScope ?? r.SingletonScope);
#if SUPPORTS_FAST_EXPRESSION_COMPILER
var factoryIdArg = fewArgExpr.Argument1;
#else
var factoryIdArg = args[1];
#endif
var id = (int)((ConstantExpression)factoryIdArg).Value;
ref var map = ref scope._maps[id & Scope.MAP_COUNT_SUFFIX_MASK];
var itemRef = map.GetEntryOrDefault(id);
if (itemRef != null && itemRef.Value != Scope.NoItem)
return itemRef.Value;
if (scope.IsDisposed)
Throw.It(Error.ScopeIsDisposed, scope.ToString());
// add only, keep old item if it already exists
var m = map;
if (Interlocked.CompareExchange(ref map, m.AddOrKeep(id, Scope.NoItem), m) != m)
Ref.Swap(ref map, id, (x, i) => x.AddOrKeep(i, Scope.NoItem));
itemRef = map.GetEntryOrDefault(id);
if (itemRef.Value == Scope.NoItem)
{
#if SUPPORTS_FAST_EXPRESSION_COMPILER
var lambda = fewArgExpr.Argument2;
#else
var lambda = args[2];
#endif
object result = null;
lock (itemRef)
{
if (itemRef.Value != Scope.NoItem)
return itemRef.Value;
if (lambda is ConstantExpression lambdaConstExpr)
result = ((FactoryDelegate)lambdaConstExpr.Value)(r);
else if (!TryInterpret(r, ((LambdaExpression)lambda).Body, paramExprs, paramValues, parentArgs, useFec, out result))
result = ((LambdaExpression)lambda).Body.CompileToFactoryDelegate(useFec,
((IContainer)r).Rules.UseInterpretation)(r);
itemRef.Value = result;
}
if (result is IDisposable disp && disp != scope)
{
#if SUPPORTS_FAST_EXPRESSION_COMPILER
var disposalOrderArg = fewArgExpr.Argument3;
#else
var disposalOrderArg = args[3];
#endif
var disposalOrder = (int)((ConstantExpression)disposalOrderArg).Value;
if (disposalOrder == 0)
scope.AddUnorderedDisposable(disp);
else
scope.AddDisposable(disp, disposalOrder);
}
}
return itemRef.Value;
}
[MethodImpl((MethodImplOptions)256)]
private static object ConstValue(Expression expr) => ((ConstantExpression)expr).Value;
}
internal static class Converter
{
public static object ConvertWithOperator(object source, Type targetType, Expression expr)
{
var sourceType = source.GetType();
var sourceConvertOp = sourceType.FindConvertOperator(sourceType, targetType);
if (sourceConvertOp != null)
return sourceConvertOp.Invoke(null, new[] { source });
var targetConvertOp = targetType.FindConvertOperator(sourceType, targetType);
if (targetConvertOp == null)
Throw.It(Error.NoConversionOperatorFoundWhenInterpretingTheConvertExpression, source, targetType, expr);
return targetConvertOp.Invoke(null, new[] { source });
}
public static object ConvertMany(object[] source, Type targetType) =>
_convertManyMethod.MakeGenericMethod(targetType).Invoke(null, source.One());
public static R[] DoConvertMany(object[] items)
{
if (items == null && items.Length == 0)
return ArrayTools.Empty();
var results = new R[items.Length];
for (var i = 0; i < items.Length; i++)
results[i] = (R)items[i];
return results;
}
private static readonly MethodInfo _convertManyMethod =
typeof(Converter).GetTypeInfo().GetDeclaredMethod(nameof(DoConvertMany));
}
/// Compiles expression to factory delegate.
public static class FactoryDelegateCompiler
{
/// Resolver context parameter expression in FactoryDelegate.
public static readonly ParameterExpression ResolverContextParamExpr = Parameter(typeof(IResolverContext), "r");
/// [Obsolete("Not used anymore")]
public static readonly Type[] FactoryDelegateParamTypes = { typeof(IResolverContext) };
/// Optimization: singleton array with the parameter expression of IResolverContext
public static readonly ParameterExpression[] FactoryDelegateParamExprs = { ResolverContextParamExpr };
/// Strips the unnecessary or adds the necessary cast to expression return result
public static Expression NormalizeExpression(this Expression expr)
{
if (expr.NodeType == System.Linq.Expressions.ExpressionType.Convert)
{
var operandExpr = ((UnaryExpression)expr).Operand;
if (operandExpr.Type == typeof(object))
return operandExpr;
}
if (expr.Type != typeof(void) && expr.Type.IsValueType())
return Convert(expr, typeof(object));
return expr;
}
/// Wraps service creation expression (body) into and returns result lambda expression.
public static Expression WrapInFactoryExpression(this Expression expression) =>
Lambda(expression.NormalizeExpression(), FactoryDelegateParamExprs
#if SUPPORTS_FAST_EXPRESSION_COMPILER
, typeof(object)
#endif
);
/// First wraps the input service expression into lambda expression and
/// then compiles lambda expression to actual used for service resolution.
public static FactoryDelegate CompileToFactoryDelegate(
this Expression expression, bool useFastExpressionCompiler, bool preferInterpretation)
{
expression = expression.NormalizeExpression();
if (expression is ConstantExpression constExpr)
return constExpr.Value.ToFactoryDelegate;
if (!preferInterpretation && useFastExpressionCompiler)
{
var factoryDelegate = (FactoryDelegate)(FastExpressionCompiler.LightExpression.ExpressionCompiler.TryCompileBoundToFirstClosureParam(
typeof(FactoryDelegate), expression, FactoryDelegateParamExprs,
new[] { typeof(FastExpressionCompiler.LightExpression.ExpressionCompiler.ArrayClosure), typeof(IResolverContext) }, typeof(object)));
if (factoryDelegate != null)
return factoryDelegate;
}
// fallback for platforms when FastExpressionCompiler is not supported,
// or just in case when some expression is not supported (did not found one yet)
#if SUPPORTS_FAST_EXPRESSION_COMPILER
return Lambda(expression, FactoryDelegateParamExprs, typeof(object)).ToLambdaExpression()
#else
return Lambda(expression, FactoryDelegateParamExprs)
#endif
.Compile(
#if SUPPORTS_EXPRESSION_COMPILE_WITH_PREFER_INTERPRETATION_PARAM
preferInterpretation
#endif
);
}
/// Compiles lambda expression to actual `FactoryDelegate` wrapper.
public static object CompileToFactoryDelegate(this Expression expression,
Type factoryDelegateType, Type resultType, bool useFastExpressionCompiler, bool preferInterpretation)
{
if (!preferInterpretation && useFastExpressionCompiler)
{
var factoryDelegate = (FastExpressionCompiler.LightExpression.ExpressionCompiler.TryCompileBoundToFirstClosureParam(
factoryDelegateType, expression, FactoryDelegateParamExprs,
new[] { typeof(FastExpressionCompiler.LightExpression.ExpressionCompiler.ArrayClosure), typeof(IResolverContext) }, resultType));
if (factoryDelegate != null)
return factoryDelegate;
}
// fallback for platforms when FastExpressionCompiler is not supported,
// or just in case when some expression is not supported (did not found one yet)
#if SUPPORTS_FAST_EXPRESSION_COMPILER
return Lambda(factoryDelegateType, expression, FactoryDelegateParamExprs, resultType).ToLambdaExpression()
#else
return Lambda(factoryDelegateType, expression, FactoryDelegateParamExprs)
#endif
.Compile(
#if SUPPORTS_EXPRESSION_COMPILE_WITH_PREFER_INTERPRETATION_PARAM
preferInterpretation
#endif
);
}
/// [Obsolete("Use the version with `preferInterpretation` parameter instead")]
public static FactoryDelegate CompileToFactoryDelegate(this Expression expression,
bool useFastExpressionCompiler = false)
{
expression = expression.NormalizeExpression();
// Optimization for constants
if (expression is ConstantExpression ce)
return ce.Value.ToFactoryDelegate;
if (useFastExpressionCompiler)
{
var factoryDelegate = (FactoryDelegate)(FastExpressionCompiler.LightExpression.ExpressionCompiler.TryCompileBoundToFirstClosureParam(
typeof(FactoryDelegate), expression, FactoryDelegateParamExprs,
new[] { typeof(FastExpressionCompiler.LightExpression.ExpressionCompiler.ArrayClosure), typeof(IResolverContext) }, typeof(object)));
if (factoryDelegate != null)
return factoryDelegate;
}
// fallback for platforms when FastExpressionCompiler is not supported,
// or just in case when some expression is not supported (did not found one yet)
#if SUPPORTS_FAST_EXPRESSION_COMPILER
return Lambda(expression, FactoryDelegateParamExprs, typeof(object)).ToLambdaExpression().Compile();
#else
return Lambda(expression, FactoryDelegateParamExprs).Compile();
#endif
}
// todo: remove unused
/// Restores the expression from LightExpression, or returns itself if already an Expression.
public static System.Linq.Expressions.Expression ToExpression(this Expression expr) =>
#if SUPPORTS_FAST_EXPRESSION_COMPILER
expr.ToExpression();
#else
expr;
#endif
}
/// Container extended features.
public static class ContainerTools
{
/// The default key for services registered into container created by
public const string FacadeKey = "@facade"; // todo: use invisible keys #555
/// Allows to register new specially keyed services which will facade the same default service,
/// registered earlier. May be used to "override" registrations when testing the container
public static IContainer CreateFacade(this IContainer container, string facadeKey = FacadeKey) =>
container.With(rules => rules
.WithDefaultRegistrationServiceKey(facadeKey)
.WithFactorySelector(Rules.SelectKeyedOverDefaultFactory(facadeKey)));
/// Shares all of container state except the cache and the new rules.
public static IContainer With(this IContainer container,
Func configure = null, IScopeContext scopeContext = null) =>
container.With(configure?.Invoke(container.Rules) ?? container.Rules, scopeContext ?? container.ScopeContext,
RegistrySharing.CloneAndDropCache, container.SingletonScope);
/// Prepares container for expression generation.
public static IContainer WithExpressionGeneration(this IContainer container, bool allowRuntimeState = false) =>
container.With(rules => rules.WithExpressionGeneration(allowRuntimeState));
/// Returns new container with all expression, delegate, items cache removed/reset.
/// But it will preserve resolved services in Singleton/Current scope.
public static IContainer WithoutCache(this IContainer container) =>
container.With(container.Rules, container.ScopeContext,
RegistrySharing.CloneAndDropCache, container.SingletonScope);
/// Creates new container with state shared with original, except for the singletons and cache.
public static IContainer WithoutSingletonsAndCache(this IContainer container) =>
container.With(container.Rules, container.ScopeContext,
RegistrySharing.CloneAndDropCache, singletonScope: null);
/// Shares the setup with original container but copies the registrations, so the new registrations
/// won't be visible in original. Registrations include decorators and wrappers as well.
public static IContainer WithRegistrationsCopy(this IContainer container, bool preserveCache = false) =>
container.With(container.Rules, container.ScopeContext,
preserveCache ? RegistrySharing.CloneButKeepCache : RegistrySharing.CloneAndDropCache,
container.SingletonScope);
/// For given instance resolves and sets properties and fields.
/// It respects rules set per container,
/// or if rules are not set it uses .
public static TService InjectPropertiesAndFields(this IResolverContext r, TService instance) =>
r.InjectPropertiesAndFields(instance, null);
/// For given instance resolves and sets properties and fields. You may specify what
/// properties and fields.
public static TService InjectPropertiesAndFields(this IResolverContext r, TService instance,
params string[] propertyAndFieldNames)
{
r.InjectPropertiesAndFields(instance, propertyAndFieldNames);
return instance;
}
/// Creates service using container for injecting parameters without registering anything in
/// if the TYPE is not registered yet.
/// Container to use for type creation and injecting its dependencies.
/// Type to instantiate. Wrappers (Func, Lazy, etc.) is also supported.
/// Setup for the concrete type, e.g. `TrackDisposableTransient`
/// (optional) Injection rules to select constructor/factory method, inject parameters,
/// properties and fields.
/// The default is
/// Object instantiated by constructor or object returned by factory method.
public static object New(this IContainer container, Type concreteType, Setup setup, Made made = null,
RegistrySharing registrySharing = RegistrySharing.CloneButKeepCache)
{
var containerClone = container.With(container.Rules, container.ScopeContext,
registrySharing, container.SingletonScope);
var implType = containerClone.GetWrappedType(concreteType, null);
var condition = setup == null && made == null ? null
: made == null ? (Func)(f => f.Setup == setup)
: setup == null ? (Func)(f => f.Made == made)
: (f => f.Made == made && f.Setup == setup);
if (!containerClone.IsRegistered(implType, condition: condition))
containerClone.Register(implType, made: made, setup: setup);
// No need to Dispose facade because it shares singleton/open scopes with source container, and disposing source container does the job.
return containerClone.Resolve(concreteType, IfUnresolved.Throw);
}
/// Creates service using container for injecting parameters without registering anything in .
/// Container to use for type creation and injecting its dependencies.
/// Type to instantiate. Wrappers (Func, Lazy, etc.) is also supported.
/// (optional) Injection rules to select constructor/factory method, inject parameters,
/// properties and fields.
/// The default is
/// Object instantiated by constructor or object returned by factory method.
public static object New(this IContainer container, Type concreteType, Made made = null,
RegistrySharing registrySharing = RegistrySharing.CloneButKeepCache) =>
container.New(concreteType, setup: null, made, registrySharing);
/// Creates service using container for injecting parameters without registering anything in .
/// Type to instantiate.
/// Container to use for type creation and injecting its dependencies.
/// (optional) Injection rules to select constructor/factory method, inject parameters, properties and fields.
/// The default is
/// Object instantiated by constructor or object returned by factory method.
public static T New(this IContainer container, Made made = null,
RegistrySharing registrySharing = RegistrySharing.CloneButKeepCache) =>
(T)container.New(typeof(T), made, registrySharing);
/// Creates service given strongly-typed creation expression.
/// Can be used to invoke arbitrary method returning some value with injecting its parameters from container.
/// Method or constructor result type.
/// Container to use for injecting dependencies.
/// Creation expression.
/// The default is
/// Created result.
public static T New(this IContainer container, Made.TypedMade made,
RegistrySharing registrySharing = RegistrySharing.CloneButKeepCache) =>
(T)container.New(typeof(T), made, registrySharing);
// todo: vNext: remove, replaced by Registrator.RegisterMapping
/// Registers new service type with factory for registered service type.
/// Throw if no such registered service type in container.
/// Container New service type.
/// Existing registered service type.
/// (optional) (optional)
/// Does nothing if registration is already exists.
public static void RegisterMapping(this IContainer container, Type serviceType, Type registeredServiceType,
object serviceKey = null, object registeredServiceKey = null) =>
Registrator.RegisterMapping(container,
serviceType, registeredServiceType, serviceKey, registeredServiceKey);
// todo: vNext: remove, replaced by Registrator.RegisterMapping
/// Registers new service type with factory for registered service type.
/// Throw if no such registered service type in container.
/// Container
/// New service type.
/// Existing registered service type.
/// (optional) (optional)
/// Does nothing if registration is already exists.
public static void RegisterMapping(this IContainer container,
object serviceKey = null, object registeredServiceKey = null) =>
Registrator.RegisterMapping(container,
typeof(TService), typeof(TRegisteredService), serviceKey, registeredServiceKey);
// todo: Remove in VNext?
/// Forwards to .
public static void RegisterPlaceholder(this IContainer container, Type serviceType,
IfAlreadyRegistered? ifAlreadyRegistered = null, object serviceKey = null) =>
Registrator.RegisterPlaceholder(container, serviceType, ifAlreadyRegistered, serviceKey);
// todo: vNext: Remove, replaced by Registrator.RegisterPlaceholder
/// Register a service without implementation which can be provided later in terms
/// of normal registration with IfAlreadyRegistered.Replace parameter.
/// When the implementation is still not provided when the placeholder service is accessed,
/// then the exception will be thrown.
/// This feature allows you to postpone decision on implementation until it is later known.
/// Internally the empty factory is registered with the setup asResolutionCall set to true.
/// That means, instead of placing service instance into graph expression we put here redirecting call to
/// container Resolve.
public static void RegisterPlaceholder(this IContainer container,
IfAlreadyRegistered? ifAlreadyRegistered = null, object serviceKey = null) =>
container.RegisterPlaceholder(typeof(TService), ifAlreadyRegistered, serviceKey);
/// Obsolete: please use WithAutoFallbackDynamicRegistration
[Obsolete("Please use WithAutoFallbackDynamicRegistration instead")]
public static IContainer WithAutoFallbackResolution(this IContainer container,
IEnumerable implTypes,
Func changeDefaultReuse = null,
Func condition = null) =>
container.ThrowIfNull().With(rules =>
rules.WithUnknownServiceResolvers(
Rules.AutoRegisterUnknownServiceRule(implTypes, changeDefaultReuse, condition)));
/// Obsolete: please use WithAutoFallbackDynamicRegistration
[Obsolete("Please use WithAutoFallbackDynamicRegistration instead")]
public static IContainer WithAutoFallbackResolution(this IContainer container,
IEnumerable implTypeAssemblies,
Func changeDefaultReuse = null,
Func condition = null) =>
container.WithAutoFallbackResolution(implTypeAssemblies.ThrowIfNull()
.SelectMany(assembly => assembly.GetLoadedTypes())
.Where(Registrator.IsImplementationType).ToArray(),
changeDefaultReuse, condition);
/// Provides automatic fallback resolution mechanism for not normally registered
/// services. Underneath uses .
public static IContainer WithAutoFallbackDynamicRegistrations(this IContainer container,
Func> getImplTypes, Func factory = null) =>
container.ThrowIfNull()
.With(rules => rules.WithDynamicRegistrationsAsFallback(
Rules.AutoFallbackDynamicRegistrations(getImplTypes, factory)));
/// Provides automatic fallback resolution mechanism for not normally registered
/// services. Underneath uses .
public static IContainer WithAutoFallbackDynamicRegistrations(this IContainer container, params Type[] implTypes) =>
container.WithAutoFallbackDynamicRegistrations((_, __) => implTypes);
/// Provides automatic fallback resolution mechanism for not normally registered
/// services. Underneath uses .
public static IContainer WithAutoFallbackDynamicRegistrations(this IContainer container,
IReuse reuse, params Type[] implTypes) =>
container.WithAutoFallbackDynamicRegistrations((_, __) => implTypes, implType => new ReflectionFactory(implType, reuse));
/// Provides automatic fallback resolution mechanism for not normally registered
/// services. Underneath uses .
public static IContainer WithAutoFallbackDynamicRegistrations(this IContainer container,
IReuse reuse, Setup setup, params Type[] implTypes) =>
container.WithAutoFallbackDynamicRegistrations(
(ignoredServiceType, ignoredServiceKey) => implTypes,
implType => new ReflectionFactory(implType, reuse, setup: setup));
/// Provides automatic fallback resolution mechanism for not normally registered
/// services. Underneath uses .
public static IContainer WithAutoFallbackDynamicRegistrations(this IContainer container,
Func> getImplTypeAssemblies,
Func factory = null) =>
container.ThrowIfNull().With(rules => rules.WithDynamicRegistrations(
Rules.AutoFallbackDynamicRegistrations(
(serviceType, serviceKey) =>
{
var assemblies = getImplTypeAssemblies(serviceType, serviceKey);
if (assemblies == null)
return Empty();
return assemblies
.SelectMany(ReflectionTools.GetLoadedTypes)
.Where(Registrator.IsImplementationType)
.ToArray();
},
factory)));
/// Provides automatic fallback resolution mechanism for not normally registered
/// services. Underneath uses .
public static IContainer WithAutoFallbackDynamicRegistrations(this IContainer container,
params Assembly[] implTypeAssemblies) =>
container.WithAutoFallbackDynamicRegistrations((_, __) => implTypeAssemblies);
/// Provides automatic fallback resolution mechanism for not normally registered
/// services. Underneath uses .
public static IContainer WithAutoFallbackDynamicRegistrations(this IContainer container,
IEnumerable implTypeAssemblies) =>
container.WithAutoFallbackDynamicRegistrations((_, __) => implTypeAssemblies);
/// Creates new container with provided parameters and properties
/// to pass the custom dependency values for injection. The old parameters and properties are overridden,
/// but not replaced.
/// Container to work with.
/// (optional) Parameters specification, can be used to proved custom values.
/// (optional) Properties and fields specification, can be used to proved custom values.
/// New container with adjusted rules.
/// (_ => "Nya!"));
/// var a = c.Resolve(); // where A accepts string parameter in constructor
/// Assert.AreEqual("Nya!", a.Message)
/// ]]>
public static IContainer WithDependencies(this IContainer container,
ParameterSelector parameters = null, PropertiesAndFieldsSelector propertiesAndFields = null) =>
container.With(rules => rules.With(Made.Of(
parameters: rules.Parameters.OverrideWith(parameters),
propertiesAndFields: rules.PropertiesAndFields.OverrideWith(propertiesAndFields)),
overrideRegistrationMade: true));
/// Result of GenerateResolutionExpressions methods
public class GeneratedExpressions
{
/// Resolutions roots
public readonly List>>
Roots = new List>>();
/// Dependency of Resolve calls
public readonly List>
ResolveDependencies = new List>();
/// Errors
public readonly List>
Errors = new List>();
}
/// Generates expressions for specified roots and their "Resolve-call" dependencies.
/// Wraps exceptions into errors. The method does not create any actual services.
/// You may use Factory .
public static GeneratedExpressions GenerateResolutionExpressions(this IContainer container,
Func, IEnumerable> getRoots = null, bool allowRuntimeState = false)
{
var generatingContainer = container.WithExpressionGeneration(allowRuntimeState);
var regs = generatingContainer.GetServiceRegistrations();
var roots = getRoots != null ? getRoots(regs) : regs.Select(r => r.ToServiceInfo());
var result = new GeneratedExpressions();
foreach (var root in roots)
{
try
{
var request = Request.Create(generatingContainer, root);
var expr = generatingContainer.ResolveFactory(request)?.GetExpressionOrDefault(request);
if (expr == null)
continue;
result.Roots.Add(root.Pair(expr.WrapInFactoryExpression()
#if SUPPORTS_FAST_EXPRESSION_COMPILER
.ToLambdaExpression()
#endif
));
}
catch (ContainerException ex)
{
result.Errors.Add(root.Pair(ex));
}
}
var depExprs = generatingContainer.Rules.DependencyResolutionCallExprs.Value;
result.ResolveDependencies.AddRange(depExprs.Enumerate().Select(r => r.Key.Pair(r.Value)));
return result;
}
/// Generates expressions for provided root services
public static GeneratedExpressions GenerateResolutionExpressions(
this IContainer container, Func condition) =>
container.GenerateResolutionExpressions(regs => regs.Where(condition.ThrowIfNull()).Select(r => r.ToServiceInfo()));
/// Generates expressions for provided root services
public static GeneratedExpressions GenerateResolutionExpressions(
this IContainer container, params ServiceInfo[] roots) =>
container.GenerateResolutionExpressions(roots.ToFunc, IEnumerable>);
/// Excluding open-generic registrations, cause you need to provide type arguments to actually create these types.
public static bool DefaultValidateCondition(ServiceRegistrationInfo reg) => !reg.ServiceType.IsOpenGeneric();
// todo: Should we have a version which is throws by default?
// todo: Should we break it by making the condition a mandatory? - because it the pass to avoid problems of validating the unnecessary dependencies
/// Helps to find potential problems in service registration setup.
/// Method tries to resolve the specified registrations, collects exceptions, and
/// returns them to user. Does not create any actual service objects.
/// You must specify to define your resolution roots,
/// otherwise container will try to resolve all registrations,
/// which usually is not realistic case to validate.
public static KeyValuePair[] Validate(this IContainer container,
Func condition = null)
{
var noOpenGenericsWithCondition = condition == null
? (Func)DefaultValidateCondition
: (r => condition(r) && DefaultValidateCondition(r));
var roots = container.GetServiceRegistrations().Where(noOpenGenericsWithCondition).Select(r => r.ToServiceInfo()).ToArray();
if (roots.Length == 0)
Throw.It(Error.FoundNoRootsToValidate, container);
return container.Validate(roots);
}
/// Helps to find potential problems when resolving the .
/// Method will collect the exceptions when resolving or injecting the specific root.
/// Does not create any actual service objects.
/// You must specify to define your resolution roots,
/// otherwise container will try to resolve all registrations,
/// which usually is not realistic case to validate.
public static KeyValuePair[] Validate(
this IContainer container, params ServiceInfo[] roots)
{
var validatingContainer = container.With(rules => rules.ForValidate());
List> errors = null;
for (var i = 0; i < roots.Length; i++)
{
var root = roots[i];
try
{
var request = Request.Create(validatingContainer, root);
var expr = validatingContainer.ResolveFactory(request)?.GetExpressionOrDefault(request);
if (expr == null)
continue;
}
catch (ContainerException ex)
{
if (errors == null)
errors = new List>();
errors.Add(root.Pair(ex));
}
}
return errors?.ToArray() ?? ArrayTools.Empty>();
}
/// Re-constructs the whole request chain as request creation expression.
public static Expression GetRequestExpression(this IContainer container, Request request,
RequestFlags requestParentFlags = default(RequestFlags))
{
if (request.IsEmpty)
return (requestParentFlags & RequestFlags.OpensResolutionScope) != 0
? Request.EmptyOpensResolutionScopeRequestExpr
: Request.EmptyRequestExpr;
var flags = request.Flags | requestParentFlags;
var r = requestParentFlags == default(RequestFlags) ? request : request.WithFlags(flags);
// When not for generation, using run-time request object to Minimize generated object graph.
if (!container.Rules.UsedForExpressionGeneration)
return Constant(r.IsolateRequestChain());
// recursively ask for parent expression until it is empty
var parentExpr = container.GetRequestExpression(request.DirectParent);
var serviceType = r.ServiceType;
var ifUnresolved = r.IfUnresolved;
var requiredServiceType = r.RequiredServiceType;
var serviceKey = r.ServiceKey;
var metadataKey = r.MetadataKey;
var metadata = r.Metadata;
var factoryID = r.FactoryID;
var factoryType = r.FactoryType;
var implementationType = r.ImplementationType;
var decoratedFactoryID = r.DecoratedFactoryID;
var serviceTypeExpr = Constant(serviceType);
var factoryIdExpr = Constant(factoryID);
var implTypeExpr = Constant(implementationType);
var reuseExpr = r.Reuse == null ? Constant(null)
: r.Reuse.ToExpression(it => container.GetConstantExpression(it));
if (ifUnresolved == IfUnresolved.Throw &&
requiredServiceType == null && serviceKey == null && metadataKey == null && metadata == null &&
factoryType == FactoryType.Service && flags == default(RequestFlags) && decoratedFactoryID == 0)
return Call(parentExpr, Request.PushMethodWith4Args.Value,
serviceTypeExpr, factoryIdExpr, implTypeExpr, reuseExpr);
var requiredServiceTypeExpr = Constant(requiredServiceType);
var serviceKeyExpr = container.GetConstantExpression(serviceKey, typeof(object));
var factoryTypeExpr = Constant(factoryType);
var flagsExpr = Constant(flags);
if (ifUnresolved == IfUnresolved.Throw &&
metadataKey == null && metadata == null && decoratedFactoryID == 0)
return Call(parentExpr, Request.PushMethodWith8Args.Value,
serviceTypeExpr, requiredServiceTypeExpr, serviceKeyExpr,
factoryIdExpr, factoryTypeExpr, implTypeExpr, reuseExpr, flagsExpr);
var ifUnresolvedExpr = Constant(ifUnresolved);
var decoratedFactoryIDExpr = Constant(decoratedFactoryID);
if (metadataKey == null && metadata == null)
return Call(parentExpr, Request.PushMethodWith10Args.Value,
serviceTypeExpr, requiredServiceTypeExpr, serviceKeyExpr, ifUnresolvedExpr,
factoryIdExpr, factoryTypeExpr, implTypeExpr, reuseExpr, flagsExpr, decoratedFactoryIDExpr);
var metadataKeyExpr = Constant(metadataKey);
var metadataExpr = container.GetConstantExpression(metadata, typeof(object));
return Call(parentExpr, Request.PushMethodWith12Args.Value,
serviceTypeExpr, requiredServiceTypeExpr, serviceKeyExpr, metadataKeyExpr, metadataExpr, ifUnresolvedExpr,
factoryIdExpr, factoryTypeExpr, implTypeExpr, reuseExpr, flagsExpr, decoratedFactoryIDExpr);
}
/// Clears delegate and expression cache for specified .
/// But does not clear instances of already resolved/created singletons and scoped services!
public static bool ClearCache(this IContainer container, FactoryType? factoryType = null, object serviceKey = null) =>
container.ClearCache(typeof(T), factoryType, serviceKey);
/// Clears delegate and expression cache for specified service.
/// But does not clear instances of already resolved/created singletons and scoped services!
public static bool ClearCache(this IContainer container, Type serviceType,
FactoryType? factoryType = null, object serviceKey = null) =>
container.ClearCache(serviceType, factoryType, serviceKey);
}
/// Interface used to convert reuse instance to expression.
public interface IConvertibleToExpression
{
/// Returns expression representation without closure.
/// Use to converting the sub-items, constants to container.
Expression ToExpression(Func fallbackConverter);
}
/// Used to represent multiple default service keys.
/// Exposes to determine order of service added.
public sealed class DefaultKey : IConvertibleToExpression
{
/// Default value.
public static readonly DefaultKey Value = new DefaultKey(0);
/// Allows to determine service registration order.
public readonly int RegistrationOrder;
/// Returns the default key with specified registration order.
public static DefaultKey Of(int registrationOrder) =>
registrationOrder == 0 ? Value : new DefaultKey(registrationOrder);
private static readonly MethodInfo _ofMethod =
typeof(DefaultKey).GetTypeInfo().GetDeclaredMethod(nameof(Of));
/// Converts to expression
public Expression ToExpression(Func fallbackConverter) =>
Call(_ofMethod, Constant(RegistrationOrder));
/// Returns next default key with increased .
public DefaultKey Next() => Of(RegistrationOrder + 1);
/// Compares keys based on registration order. The null (represents default) key is considered equal.
public override bool Equals(object key) =>
key == null || (key as DefaultKey)?.RegistrationOrder == RegistrationOrder;
/// Returns registration order as hash.
public override int GetHashCode() => RegistrationOrder;
/// Prints registration order to string.
public override string ToString() => GetType().Name + "(" + RegistrationOrder + ")";
private DefaultKey(int registrationOrder)
{
RegistrationOrder = registrationOrder;
}
}
/// Represents default key for dynamic registrations
public sealed class DefaultDynamicKey : IConvertibleToExpression
{
/// Default value.
public static readonly DefaultDynamicKey Value = new DefaultDynamicKey(0);
/// Associated ID.
public readonly int RegistrationOrder;
/// Returns dynamic key with specified ID.
public static DefaultDynamicKey Of(int registrationOrder) =>
registrationOrder == 0 ? Value : new DefaultDynamicKey(registrationOrder);
private static readonly MethodInfo _ofMethod =
typeof(DefaultDynamicKey).GetTypeInfo().GetDeclaredMethod(nameof(Of));
/// Converts to expression
public Expression ToExpression(Func fallbackConverter) =>
Call(_ofMethod, Constant(RegistrationOrder));
/// Returns next dynamic key with increased .
public DefaultDynamicKey Next() => Of(RegistrationOrder + 1);
/// Compares key's IDs. The null (default) key is considered equal!
public override bool Equals(object key) =>
key == null || (key as DefaultDynamicKey)?.RegistrationOrder == RegistrationOrder;
/// Returns key index as hash.
public override int GetHashCode() => RegistrationOrder;
/// Prints registration order to string.
public override string ToString() => GetType().Name + "(" + RegistrationOrder + ")";
private DefaultDynamicKey(int registrationOrder)
{
RegistrationOrder = registrationOrder;
}
}
/// Extends IResolver to provide an access to scope hierarchy.
public interface IResolverContext : IResolver, IDisposable
{
/// True if container is disposed.
bool IsDisposed { get; }
/// Parent context of the scoped context.
IResolverContext Parent { get; }
/// The root context of the scoped context.
IResolverContext Root { get; }
/// Singleton scope, always associated with root scope.
IScope SingletonScope { get; }
/// Optional ambient scope context.
IScopeContext ScopeContext { get; }
/// Current opened scope. May return the current scope from if context is not null.
IScope CurrentScope { get; }
/// Creates the resolver context with specified current Container-OWN scope
IResolverContext WithCurrentScope(IScope scope);
/// Put instance into the current scope or singletons.
void UseInstance(Type serviceType, object instance, IfAlreadyRegistered IfAlreadyRegistered,
bool preventDisposal, bool weaklyReferenced, object serviceKey);
/// Puts instance created via the passed factory on demand into the current or singleton scope
void Use(Type serviceType, FactoryDelegate factory);
/// For given instance resolves and sets properties and fields.
void InjectPropertiesAndFields(object instance, string[] propertyAndFieldNames);
}
/// Provides a usable abstractions for
public static class ResolverContext
{
/// Just a sugar that allow to get root or self container.
public static IResolverContext RootOrSelf(this IResolverContext r) => r.Root ?? r;
internal static readonly PropertyInfo ParentProperty =
typeof(IResolverContext).Property(nameof(IResolverContext.Parent));
internal static readonly MethodInfo OpenScopeMethod =
typeof(ResolverContext).GetTypeInfo().GetDeclaredMethod(nameof(OpenScope));
/// Returns root or self resolver based on request.
public static Expression GetRootOrSelfExpr(Request request) =>
request.DirectParent.IsSingletonOrDependencyOfSingleton && !request.OpensResolutionScope && !request.IsDirectlyWrappedInFunc()
? RootOrSelfExpr
: FactoryDelegateCompiler.ResolverContextParamExpr;
/// Resolver context parameter expression in FactoryDelegate.
public static readonly Expression ParentExpr =
Property(FactoryDelegateCompiler.ResolverContextParamExpr, ParentProperty);
/// Resolver parameter expression in FactoryDelegate.
public static readonly Expression RootOrSelfExpr =
Call(typeof(ResolverContext).GetTypeInfo().GetDeclaredMethod(nameof(RootOrSelf)),
FactoryDelegateCompiler.ResolverContextParamExpr);
/// Resolver parameter expression in FactoryDelegate.
public static readonly Expression SingletonScopeExpr =
Property(FactoryDelegateCompiler.ResolverContextParamExpr,
typeof(IResolverContext).Property(nameof(IResolverContext.SingletonScope)));
/// Access to scopes in FactoryDelegate.
public static readonly Expression CurrentScopeExpr =
Property(FactoryDelegateCompiler.ResolverContextParamExpr,
typeof(IResolverContext).Property(nameof(IResolverContext.CurrentScope)));
/// Indicates that context is scoped - that's is only possible if container is not the Root one and has a Parent context
public static bool IsScoped(this IResolverContext r) => r.Parent != null;
/// Provides access to the current scope - may return `null` if ambient scope context has it scope changed in-between
public static IScope GetCurrentScope(this IResolverContext r, bool throwIfNotFound) =>
r.CurrentScope ?? (throwIfNotFound ? Throw.For(Error.NoCurrentScope, r) : null);
/// Gets current scope matching the
public static IScope GetNamedScope(this IResolverContext r, object name, bool throwIfNotFound)
{
var currentScope = r.CurrentScope;
if (currentScope == null)
return throwIfNotFound ? Throw.For(Error.NoCurrentScope, r) : null;
if (name == null)
return currentScope;
if (name is IScopeName scopeName)
{
for (var s = currentScope; s != null; s = s.Parent)
if (scopeName.Match(s.Name))
return s;
}
else
{
for (var s = currentScope; s != null; s = s.Parent)
if (ReferenceEquals(name, s.Name) || name.Equals(s.Name))
return s;
}
return !throwIfNotFound ? null : Throw.For(Error.NoMatchedScopeFound, name, currentScope);
}
/// Opens scope with optional name and optional tracking of new scope in a parent scope.
/// Parent context to use.
/// (optional)
/// (optional) Instructs to additionally store the opened scope in parent,
/// so it will be disposed when parent is disposed. If no parent scope is available the scope will be tracked by Singleton scope.
/// Used to dispose a resolution scope.
/// Scoped resolver context.
/// ();
/// handler.Handle(data);
/// }
/// ]]>
public static IResolverContext OpenScope(this IResolverContext r, object name = null, bool trackInParent = false)
{
if (r.ScopeContext == null)
{
// todo: may use `r.OwnCurrentScope` when its moved to `IResolverContext` from `IContainer`
var parentScope = r.CurrentScope;
var newOwnScope = new Scope(parentScope, name);
if (trackInParent)
(parentScope ?? r.SingletonScope).TrackDisposable(newOwnScope);
return r.WithCurrentScope(newOwnScope);
}
var newContextScope = name == null
? r.ScopeContext.SetCurrent(parent => new Scope(parent))
: r.ScopeContext.SetCurrent(parent => new Scope(parent, name));
if (trackInParent)
(newContextScope.Parent ?? r.SingletonScope).TrackDisposable(newContextScope);
return r.WithCurrentScope(null);
}
internal static bool TryGetUsedInstance(this IResolverContext r, Type serviceType, out object instance)
{
instance = null;
return r.CurrentScope? .TryGetUsedInstance(r, serviceType, out instance) == true
|| r.SingletonScope.TryGetUsedInstance(r, serviceType, out instance);
}
/// A bit if sugar to track disposable in singleton or current scope
public static T TrackDisposable(this IResolverContext r, T instance) where T : IDisposable =>
(T)(r.SingletonScope ?? r.CurrentScope).TrackDisposable(instance);
}
/// The result delegate generated by DryIoc for service creation.
public delegate object FactoryDelegate(IResolverContext r);
/// The stronly typed delegate for service creation registered as a Wrapper.
public delegate TService FactoryDelegate(IResolverContext r);
/// Adds to Container support for:
///
/// - Open-generic services
/// - Service generics wrappers and arrays using
extension point.
/// Supported wrappers include: Func of , Lazy, Many, IEnumerable, arrays, Meta, KeyValuePair, DebugExpression.
/// All wrapper factories are added into collection of .
/// unregistered resolution rule.
///
public static class WrappersSupport
{
/// Supported Func types.
public static readonly Type[] FuncTypes =
{
typeof(Func<>), typeof(Func<,>), typeof(Func<,,>), typeof(Func<,,,>), typeof(Func<,,,,>),
typeof(Func<,,,,,>), typeof(Func<,,,,,,>), typeof(Func<,,,,,,,>)
};
/// Supported Action types. Yeah, action I can resolve or inject void returning method as action.
public static readonly Type[] ActionTypes =
{
typeof(Action), typeof(Action<>), typeof(Action<,>), typeof(Action<,,>), typeof(Action<,,,>),
typeof(Action<,,,,>), typeof(Action<,,,,,>), typeof(Action<,,,,,,>)
};
/// Supported open-generic collection types - all the interfaces implemented by array.
public static readonly Type[] SupportedCollectionTypes =
typeof(object[]).GetImplementedInterfaces().Match(t => t.IsGeneric(), t => t.GetGenericTypeDefinition());
/// Returns true if type is supported , and false otherwise.
public static bool IsFunc(this Type type)
{
var genericDefinition = type.GetGenericDefinitionOrNull();
return genericDefinition != null && FuncTypes.IndexOfReference(genericDefinition) != -1;
}
internal static int CollectionWrapperID { get; private set; }
/// Registered wrappers by their concrete or generic definition service type.
public static readonly ImMap> Wrappers = BuildSupportedWrappers();
private static ImMap> BuildSupportedWrappers()
{
var wrappers = ImMap>.Empty;
var arrayExpr = new ExpressionFactory(GetArrayExpression, setup: Setup.Wrapper);
CollectionWrapperID = arrayExpr.FactoryID;
var arrayInterfaces = SupportedCollectionTypes;
for (var i = 0; i < arrayInterfaces.Length; i++)
wrappers = wrappers.AddOrUpdate(RuntimeHelpers.GetHashCode(arrayInterfaces[i]), arrayInterfaces[i], arrayExpr);
wrappers = wrappers.AddOrUpdate(RuntimeHelpers.GetHashCode(typeof(LazyEnumerable<>)), typeof(LazyEnumerable<>),
new ExpressionFactory(GetLazyEnumerableExpressionOrDefault, setup: Setup.Wrapper));
wrappers = wrappers.AddOrUpdate(RuntimeHelpers.GetHashCode(typeof(Lazy<>)), typeof(Lazy<>),
new ExpressionFactory(r => GetLazyExpressionOrDefault(r), setup: Setup.Wrapper));
wrappers = wrappers.AddOrUpdate(RuntimeHelpers.GetHashCode(typeof(KeyValuePair<,>)), typeof(KeyValuePair<,>),
new ExpressionFactory(GetKeyValuePairExpressionOrDefault, setup: Setup.WrapperWith(1)));
wrappers = wrappers.AddOrUpdate(RuntimeHelpers.GetHashCode(typeof(Meta<,>)), typeof(Meta<,>),
new ExpressionFactory(GetMetaExpressionOrDefault, setup: Setup.WrapperWith(0)));
wrappers = wrappers.AddOrUpdate(RuntimeHelpers.GetHashCode(typeof(Tuple<,>)), typeof(Tuple<,>),
new ExpressionFactory(GetMetaExpressionOrDefault, setup: Setup.WrapperWith(0)));
wrappers = wrappers.AddOrUpdate(RuntimeHelpers.GetHashCode(typeof(System.Linq.Expressions.LambdaExpression)), typeof(System.Linq.Expressions.LambdaExpression),
new ExpressionFactory(GetLambdaExpressionExpressionOrDefault, setup: Setup.Wrapper));
wrappers = wrappers.AddOrUpdate(RuntimeHelpers.GetHashCode(typeof(FactoryDelegate)), typeof(FactoryDelegate),
new ExpressionFactory(GetFactoryDelegateExpressionOrDefault, setup: Setup.Wrapper));
wrappers = wrappers.AddOrUpdate(RuntimeHelpers.GetHashCode(typeof(FactoryDelegate<>)), typeof(FactoryDelegate<>),
new ExpressionFactory(GetFactoryDelegateExpressionOrDefault, setup: Setup.WrapperWith(0)));
wrappers = wrappers.AddOrUpdate(RuntimeHelpers.GetHashCode(typeof(Func<>)), typeof(Func<>),
new ExpressionFactory(GetFuncOrActionExpressionOrDefault, setup: Setup.Wrapper));
for (var i = 0; i < FuncTypes.Length; i++)
wrappers = wrappers.AddOrUpdate(RuntimeHelpers.GetHashCode(FuncTypes[i]), FuncTypes[i],
new ExpressionFactory(GetFuncOrActionExpressionOrDefault, setup: Setup.WrapperWith(i)));
for (var i = 0; i < ActionTypes.Length; i++)
wrappers = wrappers.AddOrUpdate(RuntimeHelpers.GetHashCode(ActionTypes[i]), ActionTypes[i],
new ExpressionFactory(GetFuncOrActionExpressionOrDefault,
setup: Setup.WrapperWith(unwrap: typeof(void).ToFunc)));
wrappers = wrappers.AddContainerInterfaces();
return wrappers;
}
private static ImMap> AddContainerInterfaces(this ImMap> wrappers)
{
var resolverContextExpr = new ExpressionFactory(
ResolverContext.GetRootOrSelfExpr,
Reuse.Transient, Setup.WrapperWith(preventDisposal: true));
var containerExpr = new ExpressionFactory(
r => Convert(ResolverContext.GetRootOrSelfExpr(r), r.ServiceType),
Reuse.Transient, Setup.WrapperWith(preventDisposal: true));
wrappers = wrappers
.AddOrUpdate(RuntimeHelpers.GetHashCode(typeof(IResolverContext)), typeof(IResolverContext), resolverContextExpr)
.AddOrUpdate(RuntimeHelpers.GetHashCode(typeof(IResolver)), typeof(IResolver), resolverContextExpr)
.AddOrUpdate(RuntimeHelpers.GetHashCode(typeof(IContainer)), typeof(IContainer), containerExpr)
.AddOrUpdate(RuntimeHelpers.GetHashCode(typeof(IRegistrator)), typeof(IRegistrator), containerExpr)
#if SUPPORTS_ISERVICE_PROVIDER
.AddOrUpdate(RuntimeHelpers.GetHashCode(typeof(IServiceProvider)), typeof(IServiceProvider), resolverContextExpr)
#endif
;
return wrappers;
}
internal static readonly MethodInfo ToArrayMethod =
typeof(ArrayTools).GetTypeInfo().GetDeclaredMethod(nameof(ArrayTools.ToArrayOrSelf));
private static Expression GetArrayExpression(Request request)
{
var collectionType = request.GetActualServiceType();
var container = request.Container;
var rules = container.Rules;
var itemType = collectionType.GetArrayElementTypeOrNull() ?? collectionType.GetGenericParamsAndArgs()[0];
if (rules.ResolveIEnumerableAsLazyEnumerable)
{
var lazyEnumerableExpr = GetLazyEnumerableExpressionOrDefault(request);
return collectionType.GetGenericDefinitionOrNull() != typeof(IEnumerable<>)
? Call(ToArrayMethod.MakeGenericMethod(itemType), lazyEnumerableExpr)
: lazyEnumerableExpr;
}
var requiredItemType = container.GetWrappedType(itemType, request.RequiredServiceType);
var items = container.GetAllServiceFactories(requiredItemType)
.Map(x => new ServiceRegistrationInfo(x.Value, requiredItemType, x.Key))
.ToArrayOrSelf();
if (requiredItemType.IsClosedGeneric())
{
var requiredItemOpenGenericType = requiredItemType.GetGenericDefinitionOrNull();
var openGenericItems = container.GetAllServiceFactories(requiredItemOpenGenericType)
.Map(f => new ServiceRegistrationInfo(f.Value, requiredItemType,
new OpenGenericTypeKey(requiredItemType.GetGenericDefinitionOrNull(), f.Key)))
.ToArrayOrSelf();
items = items.Append(openGenericItems);
}
// Append registered generic types with compatible variance,
// e.g. for IHandler - IHandler is compatible with IHandler if B : A.
if (requiredItemType.IsGeneric() &&
rules.VariantGenericTypesInResolvedCollection)
{
var variantGenericItems = container.GetServiceRegistrations()
.Match(x =>
x.ServiceType.IsGeneric() &&
x.ServiceType.GetGenericTypeDefinition() == requiredItemType.GetGenericTypeDefinition() &&
x.ServiceType != requiredItemType && x.ServiceType.IsAssignableTo(requiredItemType))
.ToArrayOrSelf();
items = items.Append(variantGenericItems);
}
// Composite pattern support: filter out composite parent service skip wrappers and decorators
var parent = request.Parent;
if (parent.FactoryType != FactoryType.Service)
parent = parent.FirstOrDefault(p => p.FactoryType == FactoryType.Service) ?? Request.Empty;
// check fast for the parent of the same type
if (!parent.IsEmpty && parent.GetActualServiceType() == requiredItemType)
{
items = items.Match(parent.FactoryID, (pID, x) => x.Factory.FactoryID != pID);
if (requiredItemType.IsGeneric())
items = items.Match(parent.FactoryID,
(pID, x) => x.Factory.FactoryGenerator?.GeneratedFactories.Enumerate().FindFirst(f => f.Value.FactoryID == pID) == null);
}
// Return collection of single matched item if key is specified.
var serviceKey = request.ServiceKey;
if (serviceKey != null)
items = items.Match(serviceKey, (key, x) => key.Equals(x.OptionalServiceKey));
var metadataKey = request.MetadataKey;
var metadata = request.Metadata;
if (metadataKey != null || metadata != null)
items = items.Match(metadataKey.Pair(metadata), (m, x) => x.Factory.Setup.MatchesMetadata(m.Key, m.Value));
var itemExprs = Empty();
if (!items.IsNullOrEmpty())
{
Array.Sort(items); // to resolve the items in order of registration
for (var i = 0; i < items.Length; i++)
{
var item = items[i];
var itemRequest = request.Push(itemType, item.OptionalServiceKey,
IfUnresolved.ReturnDefaultIfNotRegistered, requiredServiceType: item.ServiceType);
var itemFactory = container.ResolveFactory(itemRequest);
var itemExpr = itemFactory?.GetExpressionOrDefault(itemRequest);
if (itemExpr != null)
itemExprs = itemExprs.AppendOrUpdate(itemExpr);
}
}
return NewArrayInit(itemType, itemExprs);
}
private static Expression GetLazyEnumerableExpressionOrDefault(Request request)
{
var container = request.Container;
var collectionType = request.ServiceType;
var itemType = collectionType.GetArrayElementTypeOrNull() ?? collectionType.GetGenericParamsAndArgs()[0];
var requiredItemType = container.GetWrappedType(itemType, request.RequiredServiceType);
var resolverExpr = ResolverContext.GetRootOrSelfExpr(request);
var preResolveParentExpr = container.GetRequestExpression(request);
var resolveManyExpr = Call(resolverExpr, Resolver.ResolveManyMethod,
Constant(itemType),
container.GetConstantExpression(request.ServiceKey),
Constant(requiredItemType),
preResolveParentExpr,
request.GetInputArgsExpr());
return New(typeof(LazyEnumerable<>).MakeGenericType(itemType)
.GetTypeInfo().DeclaredConstructors.First(x => x.GetParameters().Length == 1),
// cast to object is not required cause Resolve already returns IEnumerable
itemType == typeof(object) ? (Expression)resolveManyExpr : Call(_enumerableCastMethod.MakeGenericMethod(itemType), resolveManyExpr));
}
private static readonly MethodInfo _enumerableCastMethod =
typeof(Enumerable).GetTypeInfo().GetDeclaredMethod(nameof(Enumerable.Cast));
/// Gets the expression for wrapper.
/// The resolution request.
/// if set to true then check for service registration before creating resolution expression.
/// Expression: new Lazy(() => r.Resolve{TService}(key, ifUnresolved, requiredType))]]>
public static Expression GetLazyExpressionOrDefault(Request request, bool nullWrapperForUnresolvedService = false)
{
var lazyType = request.GetActualServiceType();
var serviceType = lazyType.GetGenericParamsAndArgs()[0];
var serviceRequest = request.Push(serviceType);
var container = request.Container;
if (!container.Rules.FuncAndLazyWithoutRegistration)
{
var serviceFactory = container.ResolveFactory(serviceRequest);
if (serviceFactory == null)
return request.IfUnresolved == IfUnresolved.Throw ? null : Constant(null, lazyType);
serviceRequest = serviceRequest.WithResolvedFactory(serviceFactory, skipRecursiveDependencyCheck: true);
}
// creates: r => new Lazy(() => r.Resolve(key))
// or for singleton : r => new Lazy(() => r.Root.Resolve(key))
var serviceExpr = Resolver.CreateResolutionExpression(serviceRequest);
// The conversion is required in .NET 3.5 to handle lack of covariance for Func
// So that Func may be used for Func
if (serviceExpr.Type != serviceType)
serviceExpr = Convert(serviceExpr, serviceType);
var lazyValueFactoryType = typeof(Func<>).MakeGenericType(serviceType);
var wrapperCtor = lazyType.Constructor(lazyValueFactoryType);
return New(wrapperCtor, Lambda(lazyValueFactoryType, serviceExpr, Empty()
#if SUPPORTS_FAST_EXPRESSION_COMPILER
, serviceType
#endif
));
}
private static Expression GetFuncOrActionExpressionOrDefault(Request request)
{
var wrapperType = request.GetActualServiceType();
var isAction = wrapperType == typeof(Action);
if (!isAction)
{
var openGenericWrapperType = wrapperType.GetGenericDefinitionOrNull().ThrowIfNull();
if (FuncTypes.IndexOfReference(openGenericWrapperType) == -1)
Throw.If(!(isAction = ActionTypes.IndexOfReference(openGenericWrapperType) != -1));
}
var argTypes = wrapperType.GetGenericParamsAndArgs();
var argCount = isAction ? argTypes.Length : argTypes.Length - 1;
var serviceType = isAction ? typeof(void) : argTypes[argCount];
var argExprs = Empty(); // may be empty, that's OK
if (argCount != 0)
{
argExprs = new ParameterExpression[argCount];
for (var i = 0; i < argCount; ++i)
// assign valid unique argument names for code generation
argExprs[i] = Parameter(argTypes[i], argTypes[i].Name + "@" + i); // todo: optimize string allocations
request = request.WithInputArgs(argExprs);
}
var serviceRequest = request.Push(serviceType, flags: RequestFlags.IsWrappedInFunc | RequestFlags.IsDirectlyWrappedInFunc);
var container = request.Container;
var serviceExpr = container.Rules.FuncAndLazyWithoutRegistration && !isAction
? Resolver.CreateResolutionExpression(serviceRequest)
: container.ResolveFactory(serviceRequest)?.GetExpressionOrDefault(serviceRequest);
if (serviceExpr == null)
return null;
// The conversion to handle lack of covariance for Func in .NET 3.5
// So that Func may be used for Func
if (!isAction && serviceExpr.Type != serviceType)
serviceExpr = Convert(serviceExpr, serviceType);
return Lambda(wrapperType, serviceExpr, argExprs
#if SUPPORTS_FAST_EXPRESSION_COMPILER
, isAction ? typeof(void) : serviceType
#endif
);
}
private static Expression GetLambdaExpressionExpressionOrDefault(Request request)
{
var serviceType = request.RequiredServiceType
.ThrowIfNull(Error.ResolutionNeedsRequiredServiceType, request);
request = request.Push(serviceType);
var expr = request.Container.ResolveFactory(request)?.GetExpressionOrDefault(request);
if (expr == null)
return null;
return Constant(expr.WrapInFactoryExpression()
#if SUPPORTS_FAST_EXPRESSION_COMPILER
.ToLambdaExpression()
#endif
, typeof(LambdaExpression));
}
private static Expression GetFactoryDelegateExpressionOrDefault(Request request)
{
Type serviceType;
var wrapperType = request.ServiceType;
if (wrapperType == typeof(FactoryDelegate))
serviceType = request.RequiredServiceType.ThrowIfNull(Error.ResolutionNeedsRequiredServiceType, request);
else
serviceType = request.RequiredServiceType ?? wrapperType.GetGenericParamsAndArgs()[0];
request = request.Push(serviceType);
var container = request.Container;
var expr = container.ResolveFactory(request)?.GetExpressionOrDefault(request);
if (expr == null)
return null;
var rules = container.Rules;
if (wrapperType == typeof(FactoryDelegate))
return Constant(expr.CompileToFactoryDelegate(rules.UseFastExpressionCompiler, rules.UseInterpretation));
return Constant(
expr.CompileToFactoryDelegate(wrapperType, serviceType, rules.UseFastExpressionCompiler, rules.UseInterpretation),
wrapperType);
}
private static Expression GetKeyValuePairExpressionOrDefault(Request request)
{
var keyValueType = request.GetActualServiceType();
var typeArgs = keyValueType.GetGenericParamsAndArgs();
var serviceKeyType = typeArgs[0];
var serviceKey = request.ServiceKey;
if (serviceKey == null && serviceKeyType.IsValueType() ||
serviceKey != null && !serviceKeyType.IsTypeOf(serviceKey))
return null;
var serviceType = typeArgs[1];
var serviceRequest = request.Push(serviceType, serviceKey);
var serviceFactory = request.Container.ResolveFactory(serviceRequest);
var serviceExpr = serviceFactory?.GetExpressionOrDefault(serviceRequest);
if (serviceExpr == null)
return null;
var keyExpr = request.Container.GetConstantExpression(serviceKey, serviceKeyType);
return New(
keyValueType.GetTypeInfo().DeclaredConstructors.First(x => x.GetParameters().Length == 2),
keyExpr, serviceExpr);
}
/// Discovers and combines service with its setup metadata.
/// Works with any generic type with first Type arg - Service type and second Type arg - Metadata type,
/// and constructor with Service and Metadata arguments respectively.
/// - if service key is not specified in request then method will search for all
/// registered factories with the same metadata type ignoring keys.
/// - if metadata is IDictionary{string, object},
/// then the First value matching the TMetadata type will be returned.
public static Expression GetMetaExpressionOrDefault(Request request)
{
var metaType = request.GetActualServiceType();
var typeArgs = metaType.GetGenericParamsAndArgs();
var metaCtor = metaType.GetConstructorOrNull(typeArgs)
.ThrowIfNull(Error.NotFoundMetaCtorWithTwoArgs, typeArgs, request);
var metadataType = typeArgs[1];
var serviceType = typeArgs[0];
var container = request.Container;
var requiredServiceType = container.GetWrappedType(serviceType, request.RequiredServiceType);
var factories = container
.GetAllServiceFactories(requiredServiceType, bothClosedAndOpenGenerics: true)
.ToArrayOrSelf();
if (factories.Length == 0)
return null;
var serviceKey = request.ServiceKey;
if (serviceKey != null)
{
factories = factories.Match(serviceKey, (key, f) => key.Equals(f.Key));
if (factories.Length == 0)
return null;
}
// if the service keys for some reason are not unique
factories = factories
.Match(metadataType, (mType, f) =>
{
var metadata = f.Value.Setup.Metadata;
if (metadata == null)
return false;
if (mType == typeof(object))
return true;
var metadataDict = metadata as IDictionary;
if (metadataDict != null)
return mType == typeof(IDictionary) || metadataDict.Values.Any(m => mType.IsTypeOf(m));
return mType.IsTypeOf(metadata);
});
if (factories.Length == 0)
return null;
// Prevent non-determinism when more than 1 factory is matching the metadata
if (factories.Length > 1)
{
if (request.IfUnresolved == IfUnresolved.Throw)
Throw.It(Error.UnableToSelectFromManyRegistrationsWithMatchingMetadata, metadataType, factories, request);
return null;
}
var factory = factories[0];
if (factory == null)
return null;
serviceKey = factory.Key;
var serviceRequest = request.Push(serviceType, serviceKey);
var serviceFactory = container.ResolveFactory(serviceRequest);
var serviceExpr = serviceFactory?.GetExpressionOrDefault(serviceRequest);
if (serviceExpr == null)
return null;
var resultMetadata = factory.Value.Setup.Metadata;
if (metadataType != typeof(object))
{
var resultMetadataDict = resultMetadata as IDictionary;
if (resultMetadataDict != null && metadataType != typeof(IDictionary))
resultMetadata = resultMetadataDict.Values.FirstOrDefault(m => metadataType.IsTypeOf(m));
}
var metadataExpr = container.GetConstantExpression(resultMetadata, metadataType);
return New(metaCtor, serviceExpr, metadataExpr);
}
}
/// Represents info required for dynamic registration: service key, factory,
/// and option how to combine dynamic with normal registrations.
public sealed class DynamicRegistration
{
/// Factory
public readonly Factory Factory;
/// Optional: will be by default.
public readonly IfAlreadyRegistered IfAlreadyRegistered;
/// Optional service key: if null the default will be used.
public readonly object ServiceKey;
/// Constructs the info
public DynamicRegistration(Factory factory,
IfAlreadyRegistered ifAlreadyRegistered = IfAlreadyRegistered.AppendNotKeyed, object serviceKey = null)
{
Factory = factory.ThrowIfNull().DoNotCache();
ServiceKey = serviceKey;
IfAlreadyRegistered = ifAlreadyRegistered;
}
}
/// Defines resolution/registration rules associated with Container instance. They may be different for different containers.
public sealed class Rules
{
/// Default rules as staring point.
public static readonly Rules Default = new Rules();
/// Default rules as staring point.
public static readonly Rules MicrosoftDependencyInjectionRules = new Rules(
(DEFAULT_SETTINGS | Settings.TrackingDisposableTransients) & ~Settings.ThrowOnRegisteringDisposableTransient,
Rules.SelectLastRegisteredFactory(), Reuse.Transient,
Made.Of(DryIoc.FactoryMethod.ConstructorWithResolvableArguments),
IfAlreadyRegistered.AppendNotKeyed,
DefaultDependencyDepthToSplitObjectGraph, null, null, null, null, null);
/// Default value for
public const int DefaultDependencyDepthToSplitObjectGraph = 20;
/// Nested dependency depth to split an object graph
public int DependencyDepthToSplitObjectGraph { get; private set; }
/// Sets .
/// Set to prevent split.
/// To disable the limit please use
public Rules WithDependencyDepthToSplitObjectGraph(int depth) =>
new Rules(_settings, FactorySelector, DefaultReuse,
_made, DefaultIfAlreadyRegistered, depth < 1 ? 1 : depth,
DependencyResolutionCallExprs, ItemToExpressionConverter,
DynamicRegistrationProviders, UnknownServiceResolvers, DefaultRegistrationServiceKey);
/// Disables the limitation.
public Rules WithoutDependencyDepthToSplitObjectGraph() => WithDependencyDepthToSplitObjectGraph(int.MaxValue);
/// Shorthand to
public FactoryMethodSelector FactoryMethod => _made.FactoryMethod;
/// Shorthand to
public ParameterSelector Parameters => _made.Parameters;
/// Shorthand to
public PropertiesAndFieldsSelector PropertiesAndFields => _made.PropertiesAndFields;
/// Instructs to override per-registration made settings with these rules settings.
public bool OverrideRegistrationMade =>
(_settings & Settings.OverrideRegistrationMade) != 0;
/// Returns new instance of the rules new Made composed out of
/// provided factory method, parameters, propertiesAndFields.
public Rules With(
FactoryMethodSelector factoryMethod = null,
ParameterSelector parameters = null,
PropertiesAndFieldsSelector propertiesAndFields = null) =>
With(Made.Of(factoryMethod, parameters, propertiesAndFields));
/// Returns new instance of the rules with specified .
/// New Made.Of rules.
/// Instructs to override registration level Made.Of
/// New rules.
public Rules With(Made made, bool overrideRegistrationMade = false) =>
new Rules(
_settings | (overrideRegistrationMade ? Settings.OverrideRegistrationMade : 0),
FactorySelector, DefaultReuse,
_made == Made.Default
? made
: Made.Of(
made.FactoryMethod ?? _made.FactoryMethod,
made.Parameters ?? _made.Parameters,
made.PropertiesAndFields ?? _made.PropertiesAndFields),
DefaultIfAlreadyRegistered, DependencyDepthToSplitObjectGraph,
DependencyResolutionCallExprs, ItemToExpressionConverter,
DynamicRegistrationProviders, UnknownServiceResolvers, DefaultRegistrationServiceKey);
/// Service key to be used instead on `null` in registration.
public object DefaultRegistrationServiceKey { get; }
/// Sets the
public Rules WithDefaultRegistrationServiceKey(object serviceKey) =>
serviceKey == null ? this :
new Rules(_settings, FactorySelector, DefaultReuse,
_made, DefaultIfAlreadyRegistered, DependencyDepthToSplitObjectGraph,
DependencyResolutionCallExprs, ItemToExpressionConverter,
DynamicRegistrationProviders, UnknownServiceResolvers, serviceKey);
/// Defines single factory selector delegate.
/// Provides service request leading to factory selection.
/// Registered factories with corresponding key to select from.
/// Single selected factory, or null if unable to select.
public delegate Factory FactorySelectorRule(Request request, KeyValuePair[] factories);
/// Rules to select single matched factory default and keyed registered factory/factories.
/// Selectors applied in specified array order, until first returns not null .
/// Default behavior is to throw on multiple registered default factories, cause it is not obvious what to use.
public FactorySelectorRule FactorySelector { get; }
/// Sets
public Rules WithFactorySelector(FactorySelectorRule rule) =>
new Rules(_settings | (rule == SelectLastRegisteredFactory ? Settings.SelectLastRegisteredFactory : default(Settings)),
rule, DefaultReuse, _made, DefaultIfAlreadyRegistered, DependencyDepthToSplitObjectGraph,
DependencyResolutionCallExprs, ItemToExpressionConverter,
DynamicRegistrationProviders, UnknownServiceResolvers, DefaultRegistrationServiceKey);
/// Select last registered factory from the multiple default.
public static FactorySelectorRule SelectLastRegisteredFactory() => SelectLastRegisteredFactory;
private static Factory SelectLastRegisteredFactory(Request request, KeyValuePair[] factories)
{
var serviceKey = request.ServiceKey;
for (var i = factories.Length - 1; i >= 0; i--)
{
var factory = factories[i];
if (factory.Key.Equals(serviceKey))
return factory.Value;
}
return null;
}
//we are watching you...public static
/// Prefer specified service key (if found) over default key.
/// Help to override default registrations in Open Scope scenarios:
/// I may register service with key and resolve it as default in current scope.
public static FactorySelectorRule SelectKeyedOverDefaultFactory(object serviceKey) =>
(r, fs) => fs.FindFirst(serviceKey, (key, f) => f.Key.Equals(key)).Value ??
fs.FindFirst(f => f.Key.Equals(null)).Value;
/// Specify the method signature for returning multiple keyed factories.
/// This is dynamic analog to the normal Container Registry.
/// Requested service type.
/// (optional) If null will request all factories of
/// Key-Factory pairs.
public delegate IEnumerable DynamicRegistrationProvider(Type serviceType, object serviceKey);
/// Providers for resolving multiple not-registered services. Null by default.
public DynamicRegistrationProvider[] DynamicRegistrationProviders { get; private set; }
// todo: Should I use Settings.UseDynamicRegistrationsAsFallback, 5 tests are failing only
/// Appends dynamic registration rules.
public Rules WithDynamicRegistrations(params DynamicRegistrationProvider[] rules) =>
new Rules(_settings, FactorySelector, DefaultReuse,
_made, DefaultIfAlreadyRegistered, DependencyDepthToSplitObjectGraph,
DependencyResolutionCallExprs, ItemToExpressionConverter,
DynamicRegistrationProviders.Append(rules), UnknownServiceResolvers, DefaultRegistrationServiceKey);
/// Appends dynamic registration rules
/// And additionally specifies to use dynamic registrations only when no normal registrations found!
/// Rules to append. New Rules.
public Rules WithDynamicRegistrationsAsFallback(params DynamicRegistrationProvider[] rules) =>
new Rules(_settings | Settings.UseDynamicRegistrationsAsFallbackOnly, FactorySelector, DefaultReuse,
_made, DefaultIfAlreadyRegistered, DependencyDepthToSplitObjectGraph,
DependencyResolutionCallExprs, ItemToExpressionConverter,
DynamicRegistrationProviders.Append(rules), UnknownServiceResolvers, DefaultRegistrationServiceKey);
/// Specifies to use dynamic registrations only when no normal registrations found
public bool UseDynamicRegistrationsAsFallbackOnly =>
(_settings & Settings.UseDynamicRegistrationsAsFallbackOnly) != 0;
/// Defines delegate to return factory for request not resolved by registered factories or prior rules.
/// Applied in specified array order until return not null .
public delegate Factory UnknownServiceResolver(Request request);
/// Gets rules for resolving not-registered services. Null by default.
public UnknownServiceResolver[] UnknownServiceResolvers { get; private set; }
/// Appends resolver to current unknown service resolvers.
public Rules WithUnknownServiceResolvers(params UnknownServiceResolver[] rules) =>
new Rules(_settings, FactorySelector, DefaultReuse,
_made, DefaultIfAlreadyRegistered, DependencyDepthToSplitObjectGraph,
DependencyResolutionCallExprs, ItemToExpressionConverter,
DynamicRegistrationProviders, UnknownServiceResolvers.Append(rules),
DefaultRegistrationServiceKey);
/// Removes specified resolver from unknown service resolvers, and returns new Rules.
/// If no resolver was found then will stay the same instance,
/// so it could be check for remove success or fail.
public Rules WithoutUnknownServiceResolver(UnknownServiceResolver rule) =>
new Rules(_settings, FactorySelector, DefaultReuse,
_made, DefaultIfAlreadyRegistered, DependencyDepthToSplitObjectGraph,
DependencyResolutionCallExprs, ItemToExpressionConverter,
DynamicRegistrationProviders, UnknownServiceResolvers.Remove(rule),
DefaultRegistrationServiceKey);
/// Sugar on top of to simplify setting the diagnostic action.
/// Does not guard you from action throwing an exception. Actually can be used to throw your custom exception
/// instead of .
public Rules WithUnknownServiceHandler(Action handler) =>
WithUnknownServiceResolvers(request =>
{
handler(request);
return null;
});
/// The alternative is ConcreteTypeDynamicRegistrations
public static UnknownServiceResolver AutoResolveConcreteTypeRule(Func condition = null) =>
request =>
{
var concreteType = request.GetActualServiceType();
if (!concreteType.IsImplementationType() || concreteType.IsPrimitive() ||
condition != null && !condition(request))
return null;
var openGenericServiceType = concreteType.GetGenericDefinitionOrNull();
if (openGenericServiceType != null && WrappersSupport.Wrappers.GetValueOrDefault(openGenericServiceType) != null)
return null;
var factory = new ReflectionFactory(concreteType,
made: DryIoc.FactoryMethod.ConstructorWithResolvableArgumentsIncludingNonPublic);
// to enable fallback to other rules if unresolved try to resolve expression first and return null
return factory.GetExpressionOrDefault(request.WithIfUnresolved(IfUnresolved.ReturnDefault)) != null ? factory : null;
};
/// Rule to automatically resolves non-registered service type which is: nor interface, nor abstract.
/// For constructor selection we are using .
/// The resolution creates transient services.
/// (optional) Condition for requested service type and key.
/// (optional) Reuse for concrete types.
/// New rule.
public static DynamicRegistrationProvider ConcreteTypeDynamicRegistrations(
Func condition = null, IReuse reuse = null) =>
AutoFallbackDynamicRegistrations((serviceType, serviceKey) =>
{
if (serviceType.IsAbstract() ||
serviceType.IsOpenGeneric() || // service type in principle should be concrete, so should not be open-generic
condition != null && !condition(serviceType, serviceKey))
return null;
// exclude concrete service types which are pre-defined DryIoc wrapper types
var openGenericServiceType = serviceType.GetGenericDefinitionOrNull();
if (openGenericServiceType != null && WrappersSupport.Wrappers.GetValueOrDefault(openGenericServiceType) != null)
return null;
return serviceType.One(); // use concrete service type as implementation type
},
implType =>
{
ReflectionFactory factory = null;
// the condition checks that factory is resolvable
factory = new ReflectionFactory(implType, reuse,
DryIoc.FactoryMethod.ConstructorWithResolvableArgumentsIncludingNonPublic,
Setup.With(condition: req => factory?.GetExpressionOrDefault(req.WithIfUnresolved(IfUnresolved.ReturnDefault)) != null));
return factory;
});
/// Automatically resolves non-registered service type which is: nor interface, nor abstract.
/// The resolution creates Transient services.
public Rules WithConcreteTypeDynamicRegistrations(
Func condition = null, IReuse reuse = null) =>
WithDynamicRegistrationsAsFallback(ConcreteTypeDynamicRegistrations(condition, reuse));
/// Replaced with `WithConcreteTypeDynamicRegistrations`
public Rules WithAutoConcreteTypeResolution(Func condition = null) =>
new Rules(_settings | Settings.AutoConcreteTypeResolution, FactorySelector, DefaultReuse,
_made, DefaultIfAlreadyRegistered, DependencyDepthToSplitObjectGraph,
DependencyResolutionCallExprs, ItemToExpressionConverter,
DynamicRegistrationProviders, UnknownServiceResolvers.Append(AutoResolveConcreteTypeRule(condition)),
DefaultRegistrationServiceKey);
/// Creates dynamic fallback registrations for the requested service type
/// with provided .
/// Fallback means that the dynamic registrations will be applied Only if no normal registrations
/// exist for the requested service type, hence the "fallback".
/// Implementation types to select for service.
/// (optional) Handler to customize the factory, e.g.
/// specify reuse or setup. Handler should not return null .
/// Registration provider.
public static DynamicRegistrationProvider AutoFallbackDynamicRegistrations(
Func> getImplementationTypes,
Func factory = null)
{
// cache factory for implementation type to enable reuse semantics
var factories = Ref.Of(ImHashMap.Empty);
return (serviceType, serviceKey) =>
{
if (!serviceType.IsServiceType())
return Enumerable.Empty();
var implementationTypes = getImplementationTypes(serviceType, serviceKey);
return implementationTypes.Match(
implType => implType.IsImplementingServiceType(serviceType),
implType =>
{
var implTypeHash = RuntimeHelpers.GetHashCode(implType);
var implFactory = factories.Value.GetValueOrDefault(implTypeHash, implType);
if (implFactory == null)
{
if (factory == null)
factories.Swap(fs => (implFactory = fs.GetValueOrDefault(implTypeHash, implType)) != null ? fs
: fs.AddOrUpdate(implTypeHash, implType, implFactory = new ReflectionFactory(implType)));
else
factories.Swap(fs => (implFactory = fs.GetValueOrDefault(implTypeHash, implType)) != null ? fs
: fs.AddOrUpdate(implTypeHash, implType, implFactory = factory.Invoke(implType).ThrowIfNull()));
}
// We nullify default keys (usually passed by ResolveMany to resolve the specific factory in order)
// so that `CombineRegisteredWithDynamicFactories` may assign the key again.
// Given that the implementation types are unchanged then the new keys assignement will be the same the last one,
// so that the factory resolution will correctly match the required factory by key.
// e.g. bitbucket issue #396
var theKey = serviceKey is DefaultDynamicKey ? null : serviceKey;
return new DynamicRegistration(implFactory, IfAlreadyRegistered.Keep, theKey);
});
};
}
/// Obsolete: replaced by
[Obsolete("Replaced by " + nameof(AutoFallbackDynamicRegistrations), false)]
public static UnknownServiceResolver AutoRegisterUnknownServiceRule(IEnumerable implTypes,
Func changeDefaultReuse = null, Func condition = null) =>
request =>
{
if (condition != null && !condition(request))
return null;
var scope = request.Container.CurrentScope;
var reuse = scope != null ? Reuse.ScopedTo(scope.Name) : Reuse.Singleton;
if (changeDefaultReuse != null)
reuse = changeDefaultReuse(reuse, request);
var requestedServiceType = request.GetActualServiceType();
request.Container.RegisterMany(implTypes, reuse,
serviceTypeCondition: serviceType =>
serviceType.IsOpenGeneric() && requestedServiceType.IsClosedGeneric()
? serviceType == requestedServiceType.GetGenericTypeDefinition()
: serviceType == requestedServiceType);
return request.Container.GetServiceFactoryOrDefault(request);
};
/// See
public IReuse DefaultReuse { get; }
/// The reuse used in case if reuse is unspecified (null) in Register methods.
public Rules WithDefaultReuse(IReuse reuse) =>
new Rules(_settings, FactorySelector, reuse ?? Reuse.Transient,
_made, DefaultIfAlreadyRegistered, DependencyDepthToSplitObjectGraph,
DependencyResolutionCallExprs, ItemToExpressionConverter,
DynamicRegistrationProviders, UnknownServiceResolvers, DefaultRegistrationServiceKey);
/// Replaced by WithDefaultReuse because for some cases InsteadOfTransient does not make sense.
[Obsolete("Replaced by WithDefaultReuse because for some cases ..InsteadOfTransient does not make sense.", error: false)]
public Rules WithDefaultReuseInsteadOfTransient(IReuse reuse) => WithDefaultReuse(reuse);
/// Given item object and its type should return item "pure" expression presentation,
/// without side-effects or external dependencies.
/// e.g. for string "blah" Expression.Constant("blah", typeof(string)).
/// If unable to convert should return null.
public delegate Expression ItemToExpressionConverterRule(object item, Type itemType);
/// .
public ItemToExpressionConverterRule ItemToExpressionConverter { get; private set; }
/// Specifies custom rule to convert non-primitive items to their expression representation.
/// That may be required because DryIoc by default does not support non-primitive service keys and registration metadata.
/// To enable non-primitive values support DryIoc need a way to recreate them as expression tree.
public Rules WithItemToExpressionConverter(ItemToExpressionConverterRule itemToExpressionOrDefault) =>
new Rules(_settings, FactorySelector, DefaultReuse,
_made, DefaultIfAlreadyRegistered, DependencyDepthToSplitObjectGraph,
DependencyResolutionCallExprs, itemToExpressionOrDefault,
DynamicRegistrationProviders, UnknownServiceResolvers, DefaultRegistrationServiceKey);
/// .
public bool ThrowIfDependencyHasShorterReuseLifespan =>
(_settings & Settings.ThrowIfDependencyHasShorterReuseLifespan) != 0;
/// Turns off throwing exception when dependency has shorter reuse lifespan than its parent or ancestor.
/// New rules with new setting value.
public Rules WithoutThrowIfDependencyHasShorterReuseLifespan() =>
WithSettings(_settings & ~Settings.ThrowIfDependencyHasShorterReuseLifespan);
///
public bool ThrowOnRegisteringDisposableTransient =>
(_settings & Settings.ThrowOnRegisteringDisposableTransient) != 0;
/// Turns Off the rule .
/// Allows to register disposable transient but it is up to you to handle their disposal.
/// You can use to actually track disposable transient in
/// container, so that disposal will be handled by container.
public Rules WithoutThrowOnRegisteringDisposableTransient() =>
WithSettings(_settings & ~Settings.ThrowOnRegisteringDisposableTransient);
///
public bool TrackingDisposableTransients =>
(_settings & Settings.TrackingDisposableTransients) != 0;
/// Turns tracking of disposable transients in dependency parent scope, or in current scope if service
/// is resolved directly.
///
/// If there is no open scope at the moment then resolved transient won't be tracked and it is up to you
/// to dispose it! That's is similar situation to creating service by new - you have full control.
///
/// If dependency wrapped in Func somewhere in parent chain then it also won't be tracked, because
/// Func supposedly means multiple object creation and for container it is not clear what to do, so container
/// delegates that to user. Func here is the similar to Owned relationship type in Autofac library.
///
/// Turning this setting On automatically turns off .
public Rules WithTrackingDisposableTransients() =>
WithSettings((_settings | Settings.TrackingDisposableTransients)
& ~Settings.ThrowOnRegisteringDisposableTransient);
/// .
public bool EagerCachingSingletonForFasterAccess =>
(_settings & Settings.EagerCachingSingletonForFasterAccess) != 0;
/// Turns off optimization: creating singletons during resolution of object graph.
public Rules WithoutEagerCachingSingletonForFasterAccess() =>
WithSettings(_settings & ~Settings.EagerCachingSingletonForFasterAccess);
/// .
public Ref> DependencyResolutionCallExprs { get; private set; }
/// Indicates that container is used for generation purposes, so it should use less runtime state
public bool UsedForExpressionGeneration => (_settings & Settings.UsedForExpressionGeneration) != 0;
private Settings GetSettingsForExpressionGeneration(bool allowRuntimeState = false) =>
_settings & ~Settings.EagerCachingSingletonForFasterAccess
& ~Settings.ImplicitCheckForReuseMatchingScope
& ~Settings.UseInterpretationForTheFirstResolution
& ~Settings.UseInterpretation
| Settings.UsedForExpressionGeneration
| (allowRuntimeState ? 0 : Settings.ThrowIfRuntimeStateRequired);
/// Specifies to generate ResolutionCall dependency creation expression and stores the result
/// in the-per rules collection.
public Rules WithExpressionGeneration(bool allowRuntimeState = false) =>
new Rules(GetSettingsForExpressionGeneration(allowRuntimeState), FactorySelector, DefaultReuse,
_made, DefaultIfAlreadyRegistered, DependencyDepthToSplitObjectGraph,
Ref.Of(ImHashMap.Empty), ItemToExpressionConverter,
DynamicRegistrationProviders, UnknownServiceResolvers, DefaultRegistrationServiceKey);
/// Indicates that rules are used for the validation, e.g. the rules created in `Validate` method
public bool UsedForValidation => (_settings & Settings.UsedForValidation) != 0;
private Settings GetSettingsForValidation() =>
_settings & ~Settings.EagerCachingSingletonForFasterAccess
& ~Settings.ImplicitCheckForReuseMatchingScope
| Settings.UsedForValidation;
/// Specifies to generate ResolutionCall dependency creation expression and stores the result
/// in the-per rules collection.
public Rules ForValidate() =>
new Rules(GetSettingsForValidation(),
FactorySelector, DefaultReuse, _made, DefaultIfAlreadyRegistered,
int.MaxValue, // lifts the `DefaultDependencyDepthToSplitObjectGraph` to the Max, so we will construct the whole tree
null,
ItemToExpressionConverter,
DynamicRegistrationProviders, UnknownServiceResolvers, DefaultRegistrationServiceKey);
///