// <auto-generated/>
/*
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
    {
        /// <summary>Creates new container with default rules <see cref="DryIoc.Rules.Default"/>.</summary>
        public Container() : this(Rules.Default, Ref.Of(Registry.Default), NewSingletonScope())
        {
            SetInitialFactoryID();
        }

        /// <summary>Creates new container, optionally providing <see cref="Rules"/> to modify default container behavior.</summary>
        /// <param name="rules">(optional) Rules to modify container default resolution behavior.
        /// If not specified, then <see cref="DryIoc.Rules.Default"/> will be used.</param>
        /// <param name="scopeContext">(optional) Scope context to use for scoped reuse.</param>
        public Container(Rules rules = null, IScopeContext scopeContext = null)
            : this(rules ?? Rules.Default, Ref.Of(Registry.Default), NewSingletonScope(), scopeContext)
        {
            SetInitialFactoryID();
        }

        /// <summary>Creates new container with configured rules.</summary>
        /// <param name="configure">Allows to modify <see cref="DryIoc.Rules.Default"/> rules.</param>
        /// <param name="scopeContext">(optional) Scope context to use for <see cref="Reuse.InCurrentScope"/>.</param>
        public Container(Func<Rules, Rules> 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);
        }

        /// <summary>Helper to create singleton scope</summary>
        public static IScope NewSingletonScope() => new Scope(name: SinlgetonScopeName.Name);

        /// <summary>Pretty prints the container info including the open scope details if any.</summary> 
        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;
        }

        /// <summary>Dispose either open scope, or container with singletons, if no scope opened.</summary>
        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<ResolveManyResult> services, Type serviceType);

        /// <summary>Identifies the service when resolving collection</summary>
        public struct ResolveManyResult
        {
            /// <summary>Factory, the required part</summary>
            public FactoryDelegate FactoryDelegate;

            /// <summary>Optional key</summary>
            public object ServiceKey;

            /// <summary>Optional required service type, can be an open-generic type.</summary>
            public Type RequiredServiceType;

            /// <summary>Constructs the struct.</summary>
            public static ResolveManyResult Of(FactoryDelegate factoryDelegate,
                object serviceKey = null, Type requiredServiceType = null) =>
                new ResolveManyResult
                {
                    FactoryDelegate = factoryDelegate,
                    ServiceKey = serviceKey,
                    RequiredServiceType = requiredServiceType
                };
        }

        /// <summary>Directly uses generated factories to resolve service. Or returns the default if service does not have generated factory.</summary>
        [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;
        }

        /// <summary>Directly uses generated factories to resolve service. Or returns the default if service does not have generated factory.</summary>
        [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;
        }

        /// <summary>Resolves many generated only services. Ignores runtime registrations.</summary>
        public IEnumerable<ResolveManyResult> ResolveManyCompileTimeGeneratedOrEmpty(Type serviceType)
        {
            IEnumerable<ResolveManyResult> manyGenerated = ArrayTools.Empty<ResolveManyResult>();
            ResolveManyGenerated(ref manyGenerated, serviceType);
            return manyGenerated;
        }

        #endregion

        #region IRegistrator

        /// <summary>Returns all registered service factories with their Type and optional Key.</summary>
        /// <remarks>Decorator and Wrapper types are not included.</remarks>
        public IEnumerable<ServiceRegistrationInfo> GetServiceRegistrations() =>
            _registry.Value.GetServiceRegistrations();

        // todo: Make `serviceKey` and `factoryType` optional
        /// <summary>Searches for registered factories by type, and key (if specified),
        /// and by factory type (by default uses <see cref="FactoryType.Service"/>).
        /// May return empty, 1 or multiple factories.</summary>
        public Factory[] GetRegisteredFactories(Type serviceType, object serviceKey, FactoryType factoryType) =>
            _registry.Value.GetRegisteredFactories(serviceType.ThrowIfNull(), serviceKey, factoryType);

        /// <summary>Stores factory into container using <paramref name="serviceType"/> and <paramref name="serviceKey"/> as key
        /// for later lookup.</summary>
        /// <param name="factory">Any subtypes of <see cref="Factory"/>.</param>
        /// <param name="serviceType">Type of service to resolve later.</param>
        /// <param name="serviceKey">(optional) Service key of any type with <see cref="object.GetHashCode"/> and <see cref="object.Equals(object)"/>
        /// implemented.</param>
        /// <param name="ifAlreadyRegistered">(optional) Says how to handle existing registration with the same
        /// <paramref name="serviceType"/> and <paramref name="serviceKey"/>.</param>
        /// <param name="isStaticallyChecked">Confirms that service and implementation types are statically checked by compiler.</param>
        /// <returns>True if factory was added to registry, false otherwise.
        /// False may be in case of <see cref="IfAlreadyRegistered.Keep"/> setting and already existing factory.</returns>
        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));

        /// <inheritdoc />
        public bool IsRegistered(Type serviceType, object serviceKey, FactoryType factoryType, Func<Factory, bool> condition)
        {
            ThrowIfContainerDisposed();
            return _registry.Value.IsRegistered(serviceType, serviceKey, factoryType, condition);
        }

        /// <inheritdoc />
        public void Unregister(Type serviceType, object serviceKey, FactoryType factoryType, Func<Factory, bool> condition)
        {
            ThrowIfContainerDisposed();
            _registry.Swap(r => r.Unregister(factoryType, serviceType, serviceKey, condition));
        }

#endregion

#region IResolver

#if SUPPORTS_ISERVICE_PROVIDER
        /// Resolves service with <see cref="IfUnresolved.ReturnDefaultIfNotRegistered"/> 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<FactoryDelegate>(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<object> 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<ResolveManyResult>();
            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<T>
            if (preResolveParent == null || preResolveParent.IsEmpty)
                preResolveParent = Request.Empty.Push(
                    typeof(IEnumerable<object>), 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<ServiceRegistrationInfo> 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<in E> - IHandler<A> is compatible with IHandler<B> if B : A.
            IEnumerable<ServiceRegistrationInfo> 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

        /// <inheritdoc />
        public IResolverContext Parent => _parent;

        /// <inheritdoc />
        public IResolverContext Root
        {
            get
            {
                if (_parent == null)
                    return null;
                var p = _parent;
                while (p.Parent != null)
                    p = p.Parent;
                return p;
            }
        }

        /// <inheritdoc />
        public IScope SingletonScope => _singletonScope;

        /// <inheritdoc />
        public IScopeContext ScopeContext => _scopeContext;

        /// <inheritdoc />
        public IScope CurrentScope =>
            _scopeContext == null ? _ownCurrentScope : _scopeContext.GetCurrentOrDefault();

        /// <inheritdoc />
        [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<Factory>()
                                : 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<Request, IEnumerable<PropertyOrFieldServiceInfo>>;
            }

            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

        /// <summary>The rules object defines policies per container for registration and resolution.</summary>
        public Rules Rules { get; private set; }

        /// <summary>Represents scope bound to container itself, and not the ambient (context) thing.</summary>
        public IScope OwnCurrentScope => _ownCurrentScope;

        /// <summary>Indicates that container is disposed.</summary>
        public bool IsDisposed => _disposed == 1 || _singletonScope.IsDisposed;

        /// <inheritdoc />
        public IContainer With(Rules rules, IScopeContext scopeContext, RegistrySharing registrySharing, IScope singletonScope) =>
            With(_parent, rules, scopeContext, registrySharing, singletonScope, _ownCurrentScope);

        /// <inheritdoc />
        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);
        }

        /// <summary>Produces new container which prevents any further registrations.</summary>
        /// <param name="ignoreInsteadOfThrow">(optional) Controls what to do with the next registration: ignore or throw exception.
        /// Throws exception by default.</param>
        public IContainer WithNoMoreRegistrationAllowed(bool ignoreInsteadOfThrow = false) =>
            new Container(Rules,
                Ref.Of(_registry.Value.WithNoMoreRegistrationAllowed(ignoreInsteadOfThrow)),
                _singletonScope, _scopeContext, _ownCurrentScope,
                _disposed, _disposeStackTrace, _parent);

        /// <inheritdoc />
        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<Registry.ExpressionCacheSlot> slot) => 
            _registry.Value.GetCachedFactoryExpression(factoryId, request, out slot);

        [MethodImpl((MethodImplOptions) 256)]
        internal void CacheFactoryExpression(int factoryId, Request request, Expression expr, ImMapEntry<Registry.ExpressionCacheSlot> 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<object, Factory>(DefaultKey.Value, factory).One()
                : entry.To<FactoriesEntry>().Factories
                       .Visit(new List<KV<object, Factory>>(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<object, Factory>(DefaultKey.Value, factory).One()
                : entry.To<FactoriesEntry>().Factories
                       .Visit(new List<KV<object, Factory>>(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<object, Factory>[] 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<object, Factory>(singleDefaultFactory).One())
                        : null;

                factories = new[] {new KV<object, Factory>(DefaultKey.Value, singleDefaultFactory)};
            }
            else if (entry is FactoriesEntry e)
            {
                factories = e.Factories.Visit(new List<KV<object, Factory>>(), (x, l) => l.Add(KV.Of(x.Key, x.Value))).ToArray();
            }
            else
            {
                object openGenericEntry;
                factories = Empty<KV<object, Factory>>();
                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<object, Factory> FindFactoryWithTheMinReuseLifespan(KV<object, Factory>[] factories)
        {
            var minLifespan = int.MaxValue;
            var multipleFactories = false;
            KV<object, Factory> 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<KV<object, Factory>> 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<KV<object, Factory>> GetRegistryEntryKeyFactoryPairs(object entry) =>
            entry == null
                ? Empty<KV<object, Factory>>()
                : entry is Factory ? new[] { new KV<object, Factory>(DefaultKey.Value, (Factory)entry) }
                // todo: optimize
                : entry.To<FactoriesEntry>().Factories.Visit(new List<KV<object, Factory>>(), (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<Factory>();
            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<int>();
            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<Factory>();
                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<object, Factory>(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);
        }

        /// <summary>Converts known item into literal expression or wraps it in a constant expression.</summary>
        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<Type>())
                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<Type>()))
                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<object, Factory> Factories;

            // lastDefaultKey may be null
            public FactoriesEntry(DefaultKey lastDefaultKey, ImHashMap<object, Factory> factories)
            {
                LastDefaultKey = lastDefaultKey;
                Factories = factories;
            }

            public static readonly FactoriesEntry Empty =
                new FactoriesEntry(null, ImHashMap<object, Factory>.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<object, Factory>[] CombineRegisteredWithDynamicFactories(
            KV<object, Factory>[] 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<KV<object, Factory>>
        {
            public int Compare(KV<object, Factory> first, KV<object, Factory> 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> _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;
            }

            /// <summary>Called for Injection as dependency.</summary>
            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<ImMap.KValue<Type>> Services;
            // todo: we may use Factory or Factory[] as a value for decorators
            public readonly ImMap<ImMap.KValue<Type>> Decorators; // value is Factory[] 
            public readonly ImMap<ImMap.KValue<Type>> 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<ImMap.KValue<Type>>[] DefaultFactoryCache;

            [MethodImpl((MethodImplOptions)256)]
            public ImMapEntry<ImMap.KValue<Type>> 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<T>(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<ImMap.KValue<Type>>[CACHE_SLOT_COUNT], null);

                ref var map = ref DefaultFactoryCache[serviceTypeHash & CACHE_SLOT_COUNT_MASK];
                if (map == null)
                    Interlocked.CompareExchange(ref map, ImMap<ImMap.KValue<Type>>.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<ImMap.KValue<Type>>[] 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<ImMap.KValue<Type>>[CACHE_SLOT_COUNT], null);

                ref var map = ref KeyedFactoryCache[serviceTypeHash & CACHE_SLOT_COUNT_MASK];
                if (map == null)
                    Interlocked.CompareExchange(ref map, ImMap<ImMap.KValue<Type>>.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<object, Expression>[] ScopedToName;
            }

            /// The int key is the `FactoryID`
            public ImMap<ExpressionCacheSlot>[] FactoryExpressionCache;

            public Expression GetCachedFactoryExpression(
                int factoryId, Request request, out ImMapEntry<Registry.ExpressionCacheSlot> 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<ExpressionCacheSlot> entry = null)
            {
                if (entry == null)
                {
                    if (FactoryExpressionCache == null)
                        Interlocked.CompareExchange(ref FactoryExpressionCache,
                            new ImMap<ExpressionCacheSlot>[CACHE_SLOT_COUNT], null);

                    ref var map = ref FactoryExpressionCache[factoryId & CACHE_SLOT_COUNT_MASK];
                    if (map == null)
                        Interlocked.CompareExchange(ref map, ImMap<ExpressionCacheSlot>.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<object, Expression>[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<ImMap.KValue<Type>> wrapperFactories = null)
                : this(ImMap<ImMap.KValue<Type>>.Empty, ImMap<ImMap.KValue<Type>>.Empty, wrapperFactories ?? ImMap<ImMap.KValue<Type>>.Empty,
                    null, null, null, // caches are initialized to `null` to quickly check that they 
                    IsChangePermitted.Permitted)
            { }

            private Registry(
                ImMap<ImMap.KValue<Type>> services,
                ImMap<ImMap.KValue<Type>> decorators,
                ImMap<ImMap.KValue<Type>> wrappers,
                ImMap<ImMap.KValue<Type>>[] defaultFactoryCache,
                ImMap<ImMap.KValue<Type>>[] keyedFactoryCache,
                ImMap<ExpressionCacheSlot>[] 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<ImMap.KValue<Type>> 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<ImMap.KValue<Type>> decorators) =>
                decorators == Decorators ? this :
                new Registry(Services, decorators, Wrappers, 
                    DefaultFactoryCache.Copy(), KeyedFactoryCache.Copy(), FactoryExpressionCache.Copy(), _isChangePermitted);

            private Registry WithWrappers(ImMap<ImMap.KValue<Type>> wrappers) =>
                wrappers == Wrappers ? this :
                new Registry(Services, Decorators, wrappers, 
                    DefaultFactoryCache.Copy(), KeyedFactoryCache.Copy(), FactoryExpressionCache.Copy(), _isChangePermitted);

            public IEnumerable<ServiceRegistrationInfo> 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<Registry>(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<Factory[]>().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<IMyService>`
                        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<Factory>(), (x, l) => l.Add(x.Value)).ToArray();

                        return factories.GetValueOrDefault(serviceKey)?.One();
                    }
                }
            }

            public bool IsRegistered(Type serviceType, object serviceKey, FactoryType factoryType,
                Func<Factory, bool> condition)
            {
                serviceType = serviceType.ThrowIfNull();
                switch (factoryType)
                {
                    case FactoryType.Wrapper:
                    {
                        // first checking for the explicitly provided say `MyWrapper<IMyService>`
                        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<object>(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<object, Factory>.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<Factory, bool> condition)
            {
                if (_isChangePermitted != IsChangePermitted.Permitted)
                    return _isChangePermitted == IsChangePermitted.Ignored ? this
                        : Throw.For<Registry>(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<Factory[]>().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<Factory, bool> condition = null)
            {
                object removed = null; // Factory or FactoriesEntry or Factory[]
                ImMap<ImMap.KValue<Type>> 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<object, Factory>.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<object, Factory>.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<DefaultKey>().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<ImMap.KValue<Type>>.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<ImMap.KValue<Type>>.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<ExpressionCacheSlot>.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> 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
    {
        /// <summary>Open-generic required service-type</summary>
        public readonly Type RequiredServiceType;

        /// <summary>Optional key</summary>
        public readonly object ServiceKey;

        /// <summary>Constructs the thing</summary>
        public OpenGenericTypeKey(Type requiredServiceType, object serviceKey)
        {
            RequiredServiceType = requiredServiceType.ThrowIfNull();
            ServiceKey = serviceKey;
        }

        /// <inheritdoc />
        public override string ToString() =>
            new StringBuilder(nameof(OpenGenericTypeKey)).Append('(')
                .Print(RequiredServiceType).Append(", ").Print(ServiceKey)
                .Append(')').ToString();

        /// <inheritdoc />
        public override bool Equals(object obj)
        {
            var other = obj as OpenGenericTypeKey;
            return other != null &&
                   other.RequiredServiceType == RequiredServiceType &&
                   Equals(other.ServiceKey, ServiceKey);
        }

        /// <inheritdoc />
        public override int GetHashCode() => Hasher.Combine(RequiredServiceType, ServiceKey);

        /// <inheritdoc />
        public Expression ToExpression(Func<object, Expression> fallbackConverter) =>
            New(_ctor, Constant(RequiredServiceType, typeof(Type)), fallbackConverter(ServiceKey));

        private static readonly ConstructorInfo _ctor = typeof(OpenGenericTypeKey)
            .GetTypeInfo().DeclaredConstructors.First(x => x.GetParameters().Length == 2);
    }

    ///<summary>Hides/wraps object with disposable interface.</summary> 
    public sealed class HiddenDisposable
    {
        internal static ConstructorInfo Ctor = typeof(HiddenDisposable).GetTypeInfo().DeclaredConstructors.First();
        internal static FieldInfo ValueField = typeof(HiddenDisposable).GetTypeInfo().GetDeclaredField(nameof(Value));

        /// <summary>Wrapped value</summary>
        public readonly object Value;

        /// <summary>Wraps the value</summary>
        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;
            }
        }

        /// <summary>Stores parent lambda params and args</summary>
        public sealed class ParentLambdaArgs
        {
            /// <summary> Parent or the `null` for the root </summary>
            public readonly ParentLambdaArgs ParentWithArgs;

            /// <summary> Params </summary>
            public readonly object ParamExprs;

            /// <summary> Args </summary>
            public readonly object ParamValues;

            /// <summary>Constructs with parent parent or `null` for the root</summary>
            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;
        //}

        /// <summary>Interprets passed expression</summary>
        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<object>());
                            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<object>());
                    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<object>());
                    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<ParameterExpression> 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<ParameterExpression>;
                            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<object>(() => 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<object, object>(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<object>(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<object, object, object>((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<object, object>((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<object[], object>(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<object[]>(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<object[], object>(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<object[]>(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<R> ConvertFunc<R>(Func<object> f) => () => (R)f();
        private static readonly MethodInfo _convertFuncMethod = typeof(Interpreter).GetTypeInfo().GetDeclaredMethod(nameof(ConvertFunc));

        internal static Func<T, R> ConvertOneArgFunc<T, R>(Func<object, object> f) => a => (R)f(a);
        private static readonly MethodInfo _convertOneArgFuncMethod   = typeof(Interpreter).GetTypeInfo().GetDeclaredMethod(nameof(ConvertOneArgFunc));
        
        internal static Action<T> ConvertOneArgAction<T>(Action<object> f) => a => f(a);
        private static readonly MethodInfo _convertOneArgActionMethod = typeof(Interpreter).GetTypeInfo().GetDeclaredMethod(nameof(ConvertOneArgAction));

        internal static Func<T0, T1, R> ConvertTwoArgFunc<T0, T1, R>(Func<object, object, object> f) => (a0, a1) => (R)f(a0, a1);
        private static readonly MethodInfo _convertTwoArgFuncMethod = typeof(Interpreter).GetTypeInfo().GetDeclaredMethod(nameof(ConvertTwoArgFunc));

        internal static Action<T0, T1> ConvertTwoArgAction<T0, T1>(Action<object, object> f) => (a0, a1) => f(a0, a1);
        private static readonly MethodInfo _convertTwoArgActionMethod = typeof(Interpreter).GetTypeInfo().GetDeclaredMethod(nameof(ConvertTwoArgAction));

        internal static Func<T0, T1, T2, R> ConvertThreeArgFunc<T0, T1, T2, R>(Func<object[], object> 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<T0, T1, T2> ConvertThreeArgAction<T0, T1, T2>(Action<object[]> f) => (a0, a1, a2) => f(new object[] {a0, a1, a2});
        private static readonly MethodInfo _convertThreeArgActionMethod = typeof(Interpreter).GetTypeInfo().GetDeclaredMethod(nameof(ConvertThreeArgAction));

        internal static Func<T0, T1, T2, T3, R> ConvertFourArgFunc<T0, T1, T2, T3, R>(Func<object[], object> 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<T0, T1, T2, T3> ConvertFourArgAction<T0, T1, T2, T3>(Action<object[]> 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<object>());
                    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<object>());
            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<IScope>(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<IScope>(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<R>(object[] items)
        {
            if (items == null && items.Length == 0)
                return ArrayTools.Empty<R>();

            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));
    }

    /// <summary>Compiles expression to factory delegate.</summary>
    public static class FactoryDelegateCompiler
    {
        /// <summary>Resolver context parameter expression in FactoryDelegate.</summary>
        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;
        }

        /// <summary>Wraps service creation expression (body) into <see cref="FactoryDelegate"/> and returns result lambda expression.</summary>
        public static Expression<FactoryDelegate> WrapInFactoryExpression(this Expression expression) =>
            Lambda<FactoryDelegate>(expression.NormalizeExpression(), FactoryDelegateParamExprs
#if SUPPORTS_FAST_EXPRESSION_COMPILER
                , typeof(object)
#endif
                );

        /// <summary>First wraps the input service expression into lambda expression and
        /// then compiles lambda expression to actual <see cref="FactoryDelegate"/> used for service resolution.</summary>
        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<FactoryDelegate>(expression, FactoryDelegateParamExprs, typeof(object)).ToLambdaExpression()
#else
            return Lambda<FactoryDelegate>(expression, FactoryDelegateParamExprs)
#endif
                .Compile(
#if SUPPORTS_EXPRESSION_COMPILE_WITH_PREFER_INTERPRETATION_PARAM
                    preferInterpretation
#endif
                );
        }

        /// <summary>Compiles lambda expression to actual `FactoryDelegate` wrapper.</summary>
        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<FactoryDelegate>(expression, FactoryDelegateParamExprs, typeof(object)).ToLambdaExpression().Compile();
#else
            return Lambda<FactoryDelegate>(expression, FactoryDelegateParamExprs).Compile();
#endif
        }

        // todo: remove unused
        /// <summary>Restores the expression from LightExpression, or returns itself if already an Expression.</summary>
        public static System.Linq.Expressions.Expression ToExpression(this Expression expr) =>
#if SUPPORTS_FAST_EXPRESSION_COMPILER
            expr.ToExpression();
#else
            expr;
#endif
    }

    /// <summary>Container extended features.</summary>
    public static class ContainerTools
    {
        /// <summary>The default key for services registered into container created by <see cref="CreateFacade"/></summary>
        public const string FacadeKey = "@facade"; // todo: use invisible keys #555

        /// <summary>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</summary>
        public static IContainer CreateFacade(this IContainer container, string facadeKey = FacadeKey) =>
            container.With(rules => rules
                .WithDefaultRegistrationServiceKey(facadeKey)
                .WithFactorySelector(Rules.SelectKeyedOverDefaultFactory(facadeKey)));

        /// <summary>Shares all of container state except the cache and the new rules.</summary>
        public static IContainer With(this IContainer container,
            Func<Rules, Rules> configure = null, IScopeContext scopeContext = null) =>
            container.With(configure?.Invoke(container.Rules) ?? container.Rules, scopeContext ?? container.ScopeContext,
                RegistrySharing.CloneAndDropCache, container.SingletonScope);

        /// <summary>Prepares container for expression generation.</summary>
        public static IContainer WithExpressionGeneration(this IContainer container, bool allowRuntimeState = false) =>
            container.With(rules => rules.WithExpressionGeneration(allowRuntimeState));

        /// <summary>Returns new container with all expression, delegate, items cache removed/reset.
        /// But it will preserve resolved services in Singleton/Current scope.</summary>
        public static IContainer WithoutCache(this IContainer container) =>
            container.With(container.Rules, container.ScopeContext,
                RegistrySharing.CloneAndDropCache, container.SingletonScope);

        /// <summary>Creates new container with state shared with original, except for the singletons and cache.</summary>
        public static IContainer WithoutSingletonsAndCache(this IContainer container) =>
            container.With(container.Rules, container.ScopeContext,
                RegistrySharing.CloneAndDropCache, singletonScope: null);

        /// <summary>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.</summary>
        public static IContainer WithRegistrationsCopy(this IContainer container, bool preserveCache = false) =>
            container.With(container.Rules, container.ScopeContext,
                preserveCache ? RegistrySharing.CloneButKeepCache : RegistrySharing.CloneAndDropCache,
                container.SingletonScope);

        /// <summary>For given instance resolves and sets properties and fields.
        /// It respects <see cref="Rules.PropertiesAndFields"/> rules set per container,
        /// or if rules are not set it uses <see cref="PropertiesAndFields.Auto"/>.</summary>
        public static TService InjectPropertiesAndFields<TService>(this IResolverContext r, TService instance) =>
            r.InjectPropertiesAndFields<TService>(instance, null);

        /// <summary>For given instance resolves and sets properties and fields. You may specify what 
        /// properties and fields.</summary>
        public static TService InjectPropertiesAndFields<TService>(this IResolverContext r, TService instance,
            params string[] propertyAndFieldNames)
        {
            r.InjectPropertiesAndFields(instance, propertyAndFieldNames);
            return instance;
        }

        /// <summary>Creates service using container for injecting parameters without registering anything in <paramref name="container"/>
        /// if the TYPE is not registered yet.</summary>
        /// <param name="container">Container to use for type creation and injecting its dependencies.</param>
        /// <param name="concreteType">Type to instantiate. Wrappers (Func, Lazy, etc.) is also supported.</param>
        /// <param name="setup">Setup for the concrete type, e.g. `TrackDisposableTransient`</param>
        /// <param name="made">(optional) Injection rules to select constructor/factory method, inject parameters, 
        /// properties and fields.</param>
        /// <param name="registrySharing">The default is <see cref="RegistrySharing.CloneButKeepCache"/></param>
        /// <returns>Object instantiated by constructor or object returned by factory method.</returns>
        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<Factory, bool>)(f => f.Setup == setup)
                : setup == null ? (Func<Factory, bool>)(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);
        }

        /// <summary>Creates service using container for injecting parameters without registering anything in <paramref name="container"/>.</summary>
        /// <param name="container">Container to use for type creation and injecting its dependencies.</param>
        /// <param name="concreteType">Type to instantiate. Wrappers (Func, Lazy, etc.) is also supported.</param>
        /// <param name="made">(optional) Injection rules to select constructor/factory method, inject parameters, 
        /// properties and fields.</param>
        /// <param name="registrySharing">The default is <see cref="RegistrySharing.CloneButKeepCache"/></param>
        /// <returns>Object instantiated by constructor or object returned by factory method.</returns>
        public static object New(this IContainer container, Type concreteType, Made made = null,
            RegistrySharing registrySharing = RegistrySharing.CloneButKeepCache) =>
            container.New(concreteType, setup: null, made, registrySharing);

        /// <summary>Creates service using container for injecting parameters without registering anything in <paramref name="container"/>.</summary>
        /// <typeparam name="T">Type to instantiate.</typeparam>
        /// <param name="container">Container to use for type creation and injecting its dependencies.</param>
        /// <param name="made">(optional) Injection rules to select constructor/factory method, inject parameters, properties and fields.</param>
        /// <param name="registrySharing">The default is <see cref="RegistrySharing.CloneButKeepCache"/></param>
        /// <returns>Object instantiated by constructor or object returned by factory method.</returns>
        public static T New<T>(this IContainer container, Made made = null,
            RegistrySharing registrySharing = RegistrySharing.CloneButKeepCache) =>
            (T)container.New(typeof(T), made, registrySharing);

        /// <summary>Creates service given strongly-typed creation expression.
        /// Can be used to invoke arbitrary method returning some value with injecting its parameters from container.</summary>
        /// <typeparam name="T">Method or constructor result type.</typeparam>
        /// <param name="container">Container to use for injecting dependencies.</param>
        /// <param name="made">Creation expression.</param>
        /// <param name="registrySharing">The default is <see cref="RegistrySharing.CloneButKeepCache"/></param>
        /// <returns>Created result.</returns>
        public static T New<T>(this IContainer container, Made.TypedMade<T> made,
            RegistrySharing registrySharing = RegistrySharing.CloneButKeepCache) =>
            (T)container.New(typeof(T), made, registrySharing);

        // todo: vNext: remove, replaced by Registrator.RegisterMapping
        /// <summary>Registers new service type with factory for registered service type.
        /// Throw if no such registered service type in container.</summary>
        /// <param name="container">Container</param> <param name="serviceType">New service type.</param>
        /// <param name="registeredServiceType">Existing registered service type.</param>
        /// <param name="serviceKey">(optional)</param> <param name="registeredServiceKey">(optional)</param>
        /// <remarks>Does nothing if registration is already exists.</remarks>
        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
        /// <summary>Registers new service type with factory for registered service type.
        /// Throw if no such registered service type in container.</summary>
        /// <param name="container">Container</param>
        /// <typeparam name="TService">New service type.</typeparam>
        /// <typeparam name="TRegisteredService">Existing registered service type.</typeparam>
        /// <param name="serviceKey">(optional)</param> <param name="registeredServiceKey">(optional)</param>
        /// <remarks>Does nothing if registration is already exists.</remarks>
        public static void RegisterMapping<TService, TRegisteredService>(this IContainer container,
            object serviceKey = null, object registeredServiceKey = null) =>
            Registrator.RegisterMapping(container,
                typeof(TService), typeof(TRegisteredService), serviceKey, registeredServiceKey);

        // todo: Remove in VNext?
        /// <summary>Forwards to <see cref="Registrator.RegisterPlaceholder"/>.</summary>
        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
        /// <summary>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.</summary>
        /// <remarks>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.</remarks>
        public static void RegisterPlaceholder<TService>(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<Type> implTypes,
            Func<IReuse, Request, IReuse> changeDefaultReuse = null,
            Func<Request, bool> 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<Assembly> implTypeAssemblies,
            Func<IReuse, Request, IReuse> changeDefaultReuse = null,
            Func<Request, bool> condition = null) =>
            container.WithAutoFallbackResolution(implTypeAssemblies.ThrowIfNull()
                     .SelectMany(assembly => assembly.GetLoadedTypes())
                     .Where(Registrator.IsImplementationType).ToArray(), 
                     changeDefaultReuse, condition);

        /// <summary>Provides automatic fallback resolution mechanism for not normally registered
        /// services. Underneath uses <see cref="Rules.WithDynamicRegistrations"/>.</summary>
        public static IContainer WithAutoFallbackDynamicRegistrations(this IContainer container,
            Func<Type, object, IEnumerable<Type>> getImplTypes, Func<Type, Factory> factory = null) =>
            container.ThrowIfNull()
                .With(rules => rules.WithDynamicRegistrationsAsFallback(
                    Rules.AutoFallbackDynamicRegistrations(getImplTypes, factory)));

        /// <summary>Provides automatic fallback resolution mechanism for not normally registered
        /// services. Underneath uses <see cref="Rules.WithDynamicRegistrations"/>.</summary>
        public static IContainer WithAutoFallbackDynamicRegistrations(this IContainer container, params Type[] implTypes) =>
            container.WithAutoFallbackDynamicRegistrations((_, __) => implTypes);

        /// <summary>Provides automatic fallback resolution mechanism for not normally registered
        /// services. Underneath uses <see cref="Rules.WithDynamicRegistrations"/>.</summary>
        public static IContainer WithAutoFallbackDynamicRegistrations(this IContainer container,
            IReuse reuse, params Type[] implTypes) =>
            container.WithAutoFallbackDynamicRegistrations((_, __) => implTypes, implType => new ReflectionFactory(implType, reuse));

        /// <summary>Provides automatic fallback resolution mechanism for not normally registered
        /// services. Underneath uses <see cref="Rules.WithDynamicRegistrations"/>.</summary>
        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));

        /// <summary>Provides automatic fallback resolution mechanism for not normally registered
        /// services. Underneath uses <see cref="Rules.WithDynamicRegistrations"/>.</summary>
        public static IContainer WithAutoFallbackDynamicRegistrations(this IContainer container,
            Func<Type, object, IEnumerable<Assembly>> getImplTypeAssemblies,
            Func<Type, Factory> factory = null) =>
            container.ThrowIfNull().With(rules => rules.WithDynamicRegistrations(
                Rules.AutoFallbackDynamicRegistrations(
                    (serviceType, serviceKey) =>
                    {
                        var assemblies = getImplTypeAssemblies(serviceType, serviceKey);
                        if (assemblies == null)
                            return Empty<Type>();
                        return assemblies
                            .SelectMany(ReflectionTools.GetLoadedTypes)
                            .Where(Registrator.IsImplementationType)
                            .ToArray();
                    },
                    factory)));

        /// <summary>Provides automatic fallback resolution mechanism for not normally registered
        /// services. Underneath uses <see cref="Rules.WithDynamicRegistrations"/>.</summary>
        public static IContainer WithAutoFallbackDynamicRegistrations(this IContainer container,
            params Assembly[] implTypeAssemblies) =>
            container.WithAutoFallbackDynamicRegistrations((_, __) => implTypeAssemblies);

        /// <summary>Provides automatic fallback resolution mechanism for not normally registered
        /// services. Underneath uses <see cref="Rules.WithDynamicRegistrations"/>.</summary>
        public static IContainer WithAutoFallbackDynamicRegistrations(this IContainer container,
            IEnumerable<Assembly> implTypeAssemblies) =>
            container.WithAutoFallbackDynamicRegistrations((_, __) => implTypeAssemblies);

        /// <summary>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.</summary>
        /// <param name="container">Container to work with.</param>
        /// <param name="parameters">(optional) Parameters specification, can be used to proved custom values.</param>
        /// <param name="propertiesAndFields">(optional) Properties and fields specification, can be used to proved custom values.</param>
        /// <returns>New container with adjusted rules.</returns>
        /// <example><code lang="cs"><![CDATA[
        ///     var c = container.WithDependencies(Parameters.Of.Type<string>(_ => "Nya!"));
        ///     var a = c.Resolve<A>(); // where A accepts string parameter in constructor
        ///     Assert.AreEqual("Nya!", a.Message)
        /// ]]></code></example>
        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));

        /// <summary>Result of GenerateResolutionExpressions methods</summary>
        public class GeneratedExpressions
        {
            /// <summary>Resolutions roots</summary>
            public readonly List<KeyValuePair<ServiceInfo, System.Linq.Expressions.Expression<FactoryDelegate>>>
                Roots = new List<KeyValuePair<ServiceInfo, System.Linq.Expressions.Expression<FactoryDelegate>>>();

            /// <summary>Dependency of Resolve calls</summary>
            public readonly List<KeyValuePair<Request, System.Linq.Expressions.Expression>>
                ResolveDependencies = new List<KeyValuePair<Request, System.Linq.Expressions.Expression>>();

            /// <summary>Errors</summary>
            public readonly List<KeyValuePair<ServiceInfo, ContainerException>>
                Errors = new List<KeyValuePair<ServiceInfo, ContainerException>>();
        }

        /// <summary>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 <see cref="Setup.AsResolutionRoot"/>.</summary>
        public static GeneratedExpressions GenerateResolutionExpressions(this IContainer container,
            Func<IEnumerable<ServiceRegistrationInfo>, IEnumerable<ServiceInfo>> 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;
        }

        /// <summary>Generates expressions for provided root services</summary>
        public static GeneratedExpressions GenerateResolutionExpressions(
            this IContainer container, Func<ServiceRegistrationInfo, bool> condition) =>
            container.GenerateResolutionExpressions(regs => regs.Where(condition.ThrowIfNull()).Select(r => r.ToServiceInfo()));

        /// <summary>Generates expressions for provided root services</summary>
        public static GeneratedExpressions GenerateResolutionExpressions(
            this IContainer container, params ServiceInfo[] roots) =>
            container.GenerateResolutionExpressions(roots.ToFunc<IEnumerable<ServiceRegistrationInfo>, IEnumerable<ServiceInfo>>);

        /// <summary>Excluding open-generic registrations, cause you need to provide type arguments to actually create these types.</summary>
        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
        /// <summary>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 <paramref name="condition"/> to define your resolution roots,
        /// otherwise container will try to resolve all registrations, 
        /// which usually is not realistic case to validate.</summary>
        public static KeyValuePair<ServiceInfo, ContainerException>[] Validate(this IContainer container,
            Func<ServiceRegistrationInfo, bool> condition = null)
        {
            var noOpenGenericsWithCondition = condition == null 
                ? (Func<ServiceRegistrationInfo, bool>)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);
        }

        /// <summary>Helps to find potential problems when resolving the <paramref name="roots"/>.
        /// Method will collect the exceptions when resolving or injecting the specific root.
        /// Does not create any actual service objects.
        /// You must specify <paramref name="roots"/> to define your resolution roots,
        /// otherwise container will try to resolve all registrations, 
        /// which usually is not realistic case to validate. </summary>
        public static KeyValuePair<ServiceInfo, ContainerException>[] Validate(
            this IContainer container, params ServiceInfo[] roots)
        {
            var validatingContainer = container.With(rules => rules.ForValidate());

            List<KeyValuePair<ServiceInfo, ContainerException>> 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<KeyValuePair<ServiceInfo, ContainerException>>();
                    errors.Add(root.Pair(ex));
                }
            }

            return errors?.ToArray() ?? ArrayTools.Empty<KeyValuePair<ServiceInfo, ContainerException>>();
        }

        /// <summary>Re-constructs the whole request chain as request creation expression.</summary>
        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);
        }

        /// <summary>Clears delegate and expression cache for specified <typeparamref name="T"/>.
        /// But does not clear instances of already resolved/created singletons and scoped services!</summary>
        public static bool ClearCache<T>(this IContainer container, FactoryType? factoryType = null, object serviceKey = null) =>
            container.ClearCache(typeof(T), factoryType, serviceKey);

        /// <summary>Clears delegate and expression cache for specified service.
        /// But does not clear instances of already resolved/created singletons and scoped services!</summary>
        public static bool ClearCache(this IContainer container, Type serviceType,
            FactoryType? factoryType = null, object serviceKey = null) =>
            container.ClearCache(serviceType, factoryType, serviceKey);
    }

    /// <summary>Interface used to convert reuse instance to expression.</summary>
    public interface IConvertibleToExpression
    {
        /// <summary>Returns expression representation without closure.
        /// Use <paramref name="fallbackConverter"/> to converting the sub-items, constants to container.</summary>
        Expression ToExpression(Func<object, Expression> fallbackConverter);
    }

    /// <summary>Used to represent multiple default service keys.
    /// Exposes <see cref="RegistrationOrder"/> to determine order of service added.</summary>
    public sealed class DefaultKey : IConvertibleToExpression
    {
        /// <summary>Default value.</summary>
        public static readonly DefaultKey Value = new DefaultKey(0);

        /// <summary>Allows to determine service registration order.</summary>
        public readonly int RegistrationOrder;

        /// <summary>Returns the default key with specified registration order.</summary>
        public static DefaultKey Of(int registrationOrder) =>
            registrationOrder == 0 ? Value : new DefaultKey(registrationOrder);

        private static readonly MethodInfo _ofMethod =
            typeof(DefaultKey).GetTypeInfo().GetDeclaredMethod(nameof(Of));

        /// <summary>Converts to expression</summary>
        public Expression ToExpression(Func<object, Expression> fallbackConverter) =>
            Call(_ofMethod, Constant(RegistrationOrder));

        /// <summary>Returns next default key with increased <see cref="RegistrationOrder"/>.</summary>
        public DefaultKey Next() => Of(RegistrationOrder + 1);

        /// <summary>Compares keys based on registration order. The null (represents default) key is considered equal.</summary>
        public override bool Equals(object key) =>
            key == null || (key as DefaultKey)?.RegistrationOrder == RegistrationOrder;

        /// <summary>Returns registration order as hash.</summary>
        public override int GetHashCode() => RegistrationOrder;

        /// <summary>Prints registration order to string.</summary>
        public override string ToString() => GetType().Name + "(" + RegistrationOrder + ")";

        private DefaultKey(int registrationOrder)
        {
            RegistrationOrder = registrationOrder;
        }
    }

    /// <summary>Represents default key for dynamic registrations</summary>
    public sealed class DefaultDynamicKey : IConvertibleToExpression
    {
        /// <summary>Default value.</summary>
        public static readonly DefaultDynamicKey Value = new DefaultDynamicKey(0);

        /// <summary>Associated ID.</summary>
        public readonly int RegistrationOrder;

        /// <summary>Returns dynamic key with specified ID.</summary>
        public static DefaultDynamicKey Of(int registrationOrder) =>
            registrationOrder == 0 ? Value : new DefaultDynamicKey(registrationOrder);

        private static readonly MethodInfo _ofMethod =
            typeof(DefaultDynamicKey).GetTypeInfo().GetDeclaredMethod(nameof(Of));

        /// <summary>Converts to expression</summary>
        public Expression ToExpression(Func<object, Expression> fallbackConverter) =>
            Call(_ofMethod, Constant(RegistrationOrder));

        /// <summary>Returns next dynamic key with increased <see cref="RegistrationOrder"/>.</summary> 
        public DefaultDynamicKey Next() => Of(RegistrationOrder + 1);

        /// <summary>Compares key's IDs. The null (default) key is considered equal!</summary>
        public override bool Equals(object key) =>
            key == null || (key as DefaultDynamicKey)?.RegistrationOrder == RegistrationOrder;

        /// <summary>Returns key index as hash.</summary>
        public override int GetHashCode() => RegistrationOrder;

        /// <summary>Prints registration order to string.</summary>
        public override string ToString() => GetType().Name + "(" + RegistrationOrder + ")";

        private DefaultDynamicKey(int registrationOrder)
        {
            RegistrationOrder = registrationOrder;
        }
    }

    /// <summary>Extends IResolver to provide an access to scope hierarchy.</summary>
    public interface IResolverContext : IResolver, IDisposable
    {
        /// <summary>True if container is disposed.</summary>
        bool IsDisposed { get; }

        /// <summary>Parent context of the scoped context.</summary>
        IResolverContext Parent { get; }

        /// <summary>The root context of the scoped context.</summary>
        IResolverContext Root { get; }

        /// <summary>Singleton scope, always associated with root scope.</summary>
        IScope SingletonScope { get; }

        /// <summary>Optional ambient scope context.</summary>
        IScopeContext ScopeContext { get; }

        /// <summary>Current opened scope. May return the current scope from <see cref="ScopeContext"/> if context is not null.</summary>
        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);

        /// <summary>For given instance resolves and sets properties and fields.</summary>
        void InjectPropertiesAndFields(object instance, string[] propertyAndFieldNames);
    }

    /// <summary>Provides a usable abstractions for <see cref="IResolverContext"/></summary>
    public static class ResolverContext
    {
        /// <summary>Just a sugar that allow to get root or self container.</summary>
        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));

        /// <summary>Returns root or self resolver based on request.</summary>
        public static Expression GetRootOrSelfExpr(Request request) =>
            request.DirectParent.IsSingletonOrDependencyOfSingleton && !request.OpensResolutionScope && !request.IsDirectlyWrappedInFunc()
                ? RootOrSelfExpr
                : FactoryDelegateCompiler.ResolverContextParamExpr;

        /// <summary>Resolver context parameter expression in FactoryDelegate.</summary>
        public static readonly Expression ParentExpr =
            Property(FactoryDelegateCompiler.ResolverContextParamExpr, ParentProperty);

        /// <summary>Resolver parameter expression in FactoryDelegate.</summary>
        public static readonly Expression RootOrSelfExpr =
            Call(typeof(ResolverContext).GetTypeInfo().GetDeclaredMethod(nameof(RootOrSelf)), 
                FactoryDelegateCompiler.ResolverContextParamExpr);

        /// <summary>Resolver parameter expression in FactoryDelegate.</summary>
        public static readonly Expression SingletonScopeExpr =
            Property(FactoryDelegateCompiler.ResolverContextParamExpr,
                typeof(IResolverContext).Property(nameof(IResolverContext.SingletonScope)));

        /// <summary>Access to scopes in FactoryDelegate.</summary>
        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<IScope>(Error.NoCurrentScope, r) : null);

        /// <summary>Gets current scope matching the <paramref name="name"/></summary>
        public static IScope GetNamedScope(this IResolverContext r, object name, bool throwIfNotFound)
        {
            var currentScope = r.CurrentScope;
            if (currentScope == null)
                return throwIfNotFound ? Throw.For<IScope>(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<IScope>(Error.NoMatchedScopeFound, name, currentScope);
        }

        /// <summary>Opens scope with optional name and optional tracking of new scope in a parent scope.</summary>
        /// <param name="r">Parent context to use.</param>
        /// <param name="name">(optional)</param>
        /// <param name="trackInParent">(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.</param>
        /// <returns>Scoped resolver context.</returns>
        /// <example><code lang="cs"><![CDATA[
        /// using (var scope = container.OpenScope())
        /// {
        ///     var handler = scope.Resolve<IHandler>();
        ///     handler.Handle(data);
        /// }
        /// ]]></code></example>
        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<T>(this IResolverContext r, T instance) where T : IDisposable =>
            (T)(r.SingletonScope ?? r.CurrentScope).TrackDisposable(instance);
    }

    /// <summary>The result delegate generated by DryIoc for service creation.</summary>
    public delegate object FactoryDelegate(IResolverContext r);

    /// <summary>The stronly typed delegate for service creation registered as a Wrapper.</summary>
    public delegate TService FactoryDelegate<TService>(IResolverContext r);

    /// <summary>Adds to Container support for:
    /// <list type="bullet">
    /// <item>Open-generic services</item>
    /// <item>Service generics wrappers and arrays using <see cref="Rules.UnknownServiceResolvers"/> extension point.
    /// Supported wrappers include: Func of <see cref="FuncTypes"/>, Lazy, Many, IEnumerable, arrays, Meta, KeyValuePair, DebugExpression.
    /// All wrapper factories are added into collection of <see cref="Wrappers"/>.
    /// unregistered resolution rule.</item>
    /// </list></summary>
    public static class WrappersSupport
    {
        /// <summary>Supported Func types.</summary>
        public static readonly Type[] FuncTypes =
        {
            typeof(Func<>), typeof(Func<,>), typeof(Func<,,>), typeof(Func<,,,>), typeof(Func<,,,,>),
            typeof(Func<,,,,,>), typeof(Func<,,,,,,>), typeof(Func<,,,,,,,>)
        };

        /// <summary>Supported Action types. Yeah, action I can resolve or inject void returning method as action.</summary>
        public static readonly Type[] ActionTypes =
        {
            typeof(Action), typeof(Action<>), typeof(Action<,>), typeof(Action<,,>), typeof(Action<,,,>),
            typeof(Action<,,,,>), typeof(Action<,,,,,>), typeof(Action<,,,,,,>)
        };

        /// <summary>Supported open-generic collection types - all the interfaces implemented by array.</summary>
        public static readonly Type[] SupportedCollectionTypes =
            typeof(object[]).GetImplementedInterfaces().Match(t => t.IsGeneric(), t => t.GetGenericTypeDefinition());

        /// <summary>Returns true if type is supported <see cref="FuncTypes"/>, and false otherwise.</summary>
        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; }

        /// <summary>Registered wrappers by their concrete or generic definition service type.</summary>
        public static readonly ImMap<ImMap.KValue<Type>> Wrappers = BuildSupportedWrappers();

        private static ImMap<ImMap.KValue<Type>> BuildSupportedWrappers()
        {
            var wrappers = ImMap<ImMap.KValue<Type>>.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<Type, Type>)));

            wrappers = wrappers.AddContainerInterfaces();
            return wrappers;
        }

        private static ImMap<ImMap.KValue<Type>> AddContainerInterfaces(this ImMap<ImMap.KValue<Type>> 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<in E> - IHandler<A> is compatible with IHandler<B> 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<Expression>();
            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<object>
                itemType == typeof(object) ? (Expression)resolveManyExpr : Call(_enumerableCastMethod.MakeGenericMethod(itemType), resolveManyExpr));
        }

        private static readonly MethodInfo _enumerableCastMethod =
            typeof(Enumerable).GetTypeInfo().GetDeclaredMethod(nameof(Enumerable.Cast));

        /// <summary>Gets the expression for <see cref="Lazy{T}"/> wrapper.</summary>
        /// <param name="request">The resolution request.</param>
        /// <param name="nullWrapperForUnresolvedService">if set to <c>true</c> then check for service registration before creating resolution expression.</param>
        /// <returns>Expression: <c><![CDATA[r => new Lazy<TService>(() => r.Resolve{TService}(key, ifUnresolved, requiredType))]]></c></returns>
        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<X>(key))
            // or for singleton : r => new Lazy(() => r.Root.Resolve<X>(key))
            var serviceExpr = Resolver.CreateResolutionExpression(serviceRequest);

            // The conversion is required in .NET 3.5 to handle lack of covariance for Func<out T>
            // So that Func<Derived> may be used for Func<Base>
            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<ParameterExpression>()
#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<ParameterExpression>(); // 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<out T> in .NET 3.5
            // So that Func<Derived> may be used for Func<Base>
            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);
        }

        /// <summary>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.</summary>
        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<string, object>;
                    if (metadataDict != null)
                        return mType == typeof(IDictionary<string, object>) || 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<string, object>;
                if (resultMetadataDict != null && metadataType != typeof(IDictionary<string, object>))
                    resultMetadata = resultMetadataDict.Values.FirstOrDefault(m => metadataType.IsTypeOf(m));
            }

            var metadataExpr = container.GetConstantExpression(resultMetadata, metadataType);
            return New(metaCtor, serviceExpr, metadataExpr);
        }
    }

    /// <summary>Represents info required for dynamic registration: service key, factory,
    /// and <see cref="IfAlreadyRegistered"/> option how to combine dynamic with normal registrations.</summary>
    public sealed class DynamicRegistration
    {
        /// <summary>Factory</summary>
        public readonly Factory Factory;

        /// <summary>Optional: will be <see cref="DryIoc.IfAlreadyRegistered.AppendNotKeyed"/> by default.</summary>
        public readonly IfAlreadyRegistered IfAlreadyRegistered;

        /// <summary>Optional service key: if null the default <see cref="DefaultDynamicKey"/> will be used. </summary>
        public readonly object ServiceKey;

        /// <summary>Constructs the info</summary>
        public DynamicRegistration(Factory factory,
            IfAlreadyRegistered ifAlreadyRegistered = IfAlreadyRegistered.AppendNotKeyed, object serviceKey = null)
        {
            Factory = factory.ThrowIfNull().DoNotCache();
            ServiceKey = serviceKey;
            IfAlreadyRegistered = ifAlreadyRegistered;
        }
    }

    /// <summary> Defines resolution/registration rules associated with Container instance. They may be different for different containers.</summary>
    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);
         
        /// <summary>Default value for <see cref="DependencyDepthToSplitObjectGraph"/></summary>
        public const int DefaultDependencyDepthToSplitObjectGraph = 20;

        /// <summary>Nested dependency depth to split an object graph</summary>
        public int DependencyDepthToSplitObjectGraph { get; private set; }

        /// <summary>Sets <see cref="DependencyDepthToSplitObjectGraph"/>.
        /// Set <see cref="int.MaxValue"/> to prevent split.
        /// To disable the limit please use <see cref="WithoutDependencyDepthToSplitObjectGraph"/></summary>
        public Rules WithDependencyDepthToSplitObjectGraph(int depth) =>
            new Rules(_settings, FactorySelector, DefaultReuse,
                _made, DefaultIfAlreadyRegistered, depth < 1 ? 1 : depth,
                DependencyResolutionCallExprs, ItemToExpressionConverter,
                DynamicRegistrationProviders, UnknownServiceResolvers, DefaultRegistrationServiceKey);

        /// <summary>Disables the <see cref="DependencyDepthToSplitObjectGraph"/> limitation.</summary>
        public Rules WithoutDependencyDepthToSplitObjectGraph() => WithDependencyDepthToSplitObjectGraph(int.MaxValue);

        /// <summary>Shorthand to <see cref="Made.FactoryMethod"/></summary>
        public FactoryMethodSelector FactoryMethod => _made.FactoryMethod;

        /// <summary>Shorthand to <see cref="Made.Parameters"/></summary>
        public ParameterSelector Parameters => _made.Parameters;

        /// <summary>Shorthand to <see cref="Made.PropertiesAndFields"/></summary>
        public PropertiesAndFieldsSelector PropertiesAndFields => _made.PropertiesAndFields;

        /// <summary>Instructs to override per-registration made settings with these rules settings.</summary>
        public bool OverrideRegistrationMade =>
            (_settings & Settings.OverrideRegistrationMade) != 0;

        /// <summary>Returns new instance of the rules new Made composed out of
        /// provided factory method, parameters, propertiesAndFields.</summary>
        public Rules With(
            FactoryMethodSelector factoryMethod = null,
            ParameterSelector parameters = null,
            PropertiesAndFieldsSelector propertiesAndFields = null) =>
            With(Made.Of(factoryMethod, parameters, propertiesAndFields));

        /// <summary>Returns new instance of the rules with specified <see cref="Made"/>.</summary>
        /// <param name="made">New Made.Of rules.</param>
        /// <param name="overrideRegistrationMade">Instructs to override registration level Made.Of</param>
        /// <returns>New rules.</returns>
        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);

        /// <summary>Service key to be used instead on `null` in registration.</summary>
        public object DefaultRegistrationServiceKey { get; }

        /// <summary>Sets the <see cref="DefaultRegistrationServiceKey"/></summary>
        public Rules WithDefaultRegistrationServiceKey(object serviceKey) =>
            serviceKey == null ? this :
                new Rules(_settings, FactorySelector, DefaultReuse,
                    _made, DefaultIfAlreadyRegistered, DependencyDepthToSplitObjectGraph,
                    DependencyResolutionCallExprs, ItemToExpressionConverter,
                    DynamicRegistrationProviders, UnknownServiceResolvers, serviceKey);

        /// <summary>Defines single factory selector delegate.</summary>
        /// <param name="request">Provides service request leading to factory selection.</param>
        /// <param name="factories">Registered factories with corresponding key to select from.</param>
        /// <returns>Single selected factory, or null if unable to select.</returns>
        public delegate Factory FactorySelectorRule(Request request, KeyValuePair<object, Factory>[] factories);

        /// <summary>Rules to select single matched factory default and keyed registered factory/factories.
        /// Selectors applied in specified array order, until first returns not null <see cref="Factory"/>.
        /// Default behavior is to throw on multiple registered default factories, cause it is not obvious what to use.</summary>
        public FactorySelectorRule FactorySelector { get; }

        /// <summary>Sets <see cref="FactorySelector"/></summary>
        public Rules WithFactorySelector(FactorySelectorRule rule) =>
            new Rules(_settings | (rule == SelectLastRegisteredFactory ? Settings.SelectLastRegisteredFactory : default(Settings)),
                rule, DefaultReuse, _made, DefaultIfAlreadyRegistered, DependencyDepthToSplitObjectGraph,
                DependencyResolutionCallExprs, ItemToExpressionConverter,
                DynamicRegistrationProviders, UnknownServiceResolvers, DefaultRegistrationServiceKey);

        /// <summary>Select last registered factory from the multiple default.</summary>
        public static FactorySelectorRule SelectLastRegisteredFactory() => SelectLastRegisteredFactory;
        private static Factory SelectLastRegisteredFactory(Request request, KeyValuePair<object, Factory>[] 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
        /// <summary>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.</summary>
        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;

        /// <summary>Specify the method signature for returning multiple keyed factories.
        /// This is dynamic analog to the normal Container Registry.</summary>
        /// <param name="serviceType">Requested service type.</param>
        /// <param name="serviceKey">(optional) If <c>null</c> will request all factories of <paramref name="serviceType"/></param>
        /// <returns>Key-Factory pairs.</returns>
        public delegate IEnumerable<DynamicRegistration> DynamicRegistrationProvider(Type serviceType, object serviceKey);

        /// <summary>Providers for resolving multiple not-registered services. Null by default.</summary>
        public DynamicRegistrationProvider[] DynamicRegistrationProviders { get; private set; }

        // todo: Should I use Settings.UseDynamicRegistrationsAsFallback, 5 tests are failing only 
        /// <summary>Appends dynamic registration rules.</summary>
        public Rules WithDynamicRegistrations(params DynamicRegistrationProvider[] rules) =>
            new Rules(_settings, FactorySelector, DefaultReuse,
                _made, DefaultIfAlreadyRegistered, DependencyDepthToSplitObjectGraph,
                DependencyResolutionCallExprs, ItemToExpressionConverter,
                DynamicRegistrationProviders.Append(rules), UnknownServiceResolvers, DefaultRegistrationServiceKey);

        /// <summary>Appends dynamic registration rules 
        /// And additionally specifies to use dynamic registrations only when no normal registrations found!</summary>
        /// <param name="rules">Rules to append.</param> <returns>New Rules.</returns>
        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;

        /// <summary>Defines delegate to return factory for request not resolved by registered factories or prior rules.
        /// Applied in specified array order until return not null <see cref="Factory"/>.</summary>
        public delegate Factory UnknownServiceResolver(Request request);

        /// <summary>Gets rules for resolving not-registered services. Null by default.</summary>
        public UnknownServiceResolver[] UnknownServiceResolvers { get; private set; }

        /// <summary>Appends resolver to current unknown service resolvers.</summary>
        public Rules WithUnknownServiceResolvers(params UnknownServiceResolver[] rules) =>
            new Rules(_settings, FactorySelector, DefaultReuse,
                _made, DefaultIfAlreadyRegistered, DependencyDepthToSplitObjectGraph,
                DependencyResolutionCallExprs, ItemToExpressionConverter,
                DynamicRegistrationProviders, UnknownServiceResolvers.Append(rules),
                DefaultRegistrationServiceKey);

        /// <summary>Removes specified resolver from unknown service resolvers, and returns new Rules.
        /// If no resolver was found then <see cref="UnknownServiceResolvers"/> will stay the same instance,
        /// so it could be check for remove success or fail.</summary>
        public Rules WithoutUnknownServiceResolver(UnknownServiceResolver rule) =>
            new Rules(_settings, FactorySelector, DefaultReuse,
                _made, DefaultIfAlreadyRegistered, DependencyDepthToSplitObjectGraph,
                DependencyResolutionCallExprs, ItemToExpressionConverter,
                DynamicRegistrationProviders, UnknownServiceResolvers.Remove(rule),
                DefaultRegistrationServiceKey);

        /// <summary>Sugar on top of <see cref="WithUnknownServiceResolvers"/> 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 <see cref="ContainerException"/>.</summary>
        public Rules WithUnknownServiceHandler(Action<Request> handler) =>
            WithUnknownServiceResolvers(request =>
            {
                handler(request);
                return null;
            });

        /// <summary>The alternative is ConcreteTypeDynamicRegistrations</summary>
        public static UnknownServiceResolver AutoResolveConcreteTypeRule(Func<Request, bool> 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;
            };

        /// <summary>Rule to automatically resolves non-registered service type which is: nor interface, nor abstract.
        /// For constructor selection we are using <see cref="DryIoc.FactoryMethod.ConstructorWithResolvableArguments"/>.
        /// The resolution creates transient services.</summary>
        /// <param name="condition">(optional) Condition for requested service type and key.</param>
        /// <param name="reuse">(optional) Reuse for concrete types.</param>
        /// <returns>New rule.</returns>
        public static DynamicRegistrationProvider ConcreteTypeDynamicRegistrations(
            Func<Type, object, bool> 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;
            });

        /// <summary>Automatically resolves non-registered service type which is: nor interface, nor abstract.
        /// The resolution creates Transient services.</summary>
        public Rules WithConcreteTypeDynamicRegistrations(
            Func<Type, object, bool> condition = null, IReuse reuse = null) =>
            WithDynamicRegistrationsAsFallback(ConcreteTypeDynamicRegistrations(condition, reuse));

        /// Replaced with `WithConcreteTypeDynamicRegistrations`
        public Rules WithAutoConcreteTypeResolution(Func<Request, bool> condition = null) =>
            new Rules(_settings | Settings.AutoConcreteTypeResolution, FactorySelector, DefaultReuse,
                _made, DefaultIfAlreadyRegistered, DependencyDepthToSplitObjectGraph,
                DependencyResolutionCallExprs, ItemToExpressionConverter,
                DynamicRegistrationProviders, UnknownServiceResolvers.Append(AutoResolveConcreteTypeRule(condition)),
                DefaultRegistrationServiceKey);

        /// <summary>Creates dynamic fallback registrations for the requested service type
        /// with provided <paramref name="getImplementationTypes"/>.
        /// Fallback means that the dynamic registrations will be applied Only if no normal registrations
        /// exist for the requested service type, hence the "fallback".</summary>
        /// <param name="getImplementationTypes">Implementation types to select for service.</param>
        /// <param name="factory">(optional) Handler to customize the factory, e.g.
        /// specify reuse or setup. Handler should not return <c>null</c>.</param>
        /// <returns>Registration provider.</returns>
        public static DynamicRegistrationProvider AutoFallbackDynamicRegistrations(
            Func<Type, object, IEnumerable<Type>> getImplementationTypes,
            Func<Type, Factory> factory = null)
        {
            // cache factory for implementation type to enable reuse semantics
            var factories = Ref.Of(ImHashMap<Type, Factory>.Empty);

            return (serviceType, serviceKey) =>
            {
                if (!serviceType.IsServiceType())
                    return Enumerable.Empty<DynamicRegistration>();

                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);
                    });
            };
        }

        /// <summary>Obsolete: replaced by <see cref="AutoFallbackDynamicRegistrations"/></summary>
        [Obsolete("Replaced by " + nameof(AutoFallbackDynamicRegistrations), false)]
        public static UnknownServiceResolver AutoRegisterUnknownServiceRule(IEnumerable<Type> implTypes,
            Func<IReuse, Request, IReuse> changeDefaultReuse = null, Func<Request, bool> 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);
            };

        /// <summary>See <see cref="WithDefaultReuse"/></summary>
        public IReuse DefaultReuse { get; }

        /// <summary>The reuse used in case if reuse is unspecified (null) in Register methods.</summary>
        public Rules WithDefaultReuse(IReuse reuse) =>
            new Rules(_settings, FactorySelector, reuse ?? Reuse.Transient,
                _made, DefaultIfAlreadyRegistered, DependencyDepthToSplitObjectGraph,
                DependencyResolutionCallExprs, ItemToExpressionConverter,
                DynamicRegistrationProviders, UnknownServiceResolvers, DefaultRegistrationServiceKey);

        /// <summary>Replaced by WithDefaultReuse because for some cases InsteadOfTransient does not make sense.</summary>
        [Obsolete("Replaced by WithDefaultReuse because for some cases ..InsteadOfTransient does not make sense.", error: false)]
        public Rules WithDefaultReuseInsteadOfTransient(IReuse reuse) => WithDefaultReuse(reuse);

        /// <summary>Given item object and its type should return item "pure" expression presentation,
        /// without side-effects or external dependencies.
        /// e.g. for string "blah" <code lang="cs"><![CDATA[]]>Expression.Constant("blah", typeof(string))</code>.
        /// If unable to convert should return null.</summary>
        public delegate Expression ItemToExpressionConverterRule(object item, Type itemType);

        /// <summary><see cref="WithItemToExpressionConverter"/>.</summary>
        public ItemToExpressionConverterRule ItemToExpressionConverter { get; private set; }

        /// <summary>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.</summary>
        public Rules WithItemToExpressionConverter(ItemToExpressionConverterRule itemToExpressionOrDefault) =>
            new Rules(_settings, FactorySelector, DefaultReuse,
                _made, DefaultIfAlreadyRegistered, DependencyDepthToSplitObjectGraph,
                DependencyResolutionCallExprs, itemToExpressionOrDefault,
                DynamicRegistrationProviders, UnknownServiceResolvers, DefaultRegistrationServiceKey);

        /// <summary><see cref="WithoutThrowIfDependencyHasShorterReuseLifespan"/>.</summary>
        public bool ThrowIfDependencyHasShorterReuseLifespan =>
            (_settings & Settings.ThrowIfDependencyHasShorterReuseLifespan) != 0;

        /// <summary>Turns off throwing exception when dependency has shorter reuse lifespan than its parent or ancestor.</summary>
        /// <returns>New rules with new setting value.</returns>
        public Rules WithoutThrowIfDependencyHasShorterReuseLifespan() =>
            WithSettings(_settings & ~Settings.ThrowIfDependencyHasShorterReuseLifespan);

        /// <summary><see cref="WithoutThrowOnRegisteringDisposableTransient"/></summary>
        public bool ThrowOnRegisteringDisposableTransient =>
            (_settings & Settings.ThrowOnRegisteringDisposableTransient) != 0;

        /// <summary>Turns Off the rule <see cref="ThrowOnRegisteringDisposableTransient"/>.
        /// Allows to register disposable transient but it is up to you to handle their disposal.
        /// You can use <see cref="WithTrackingDisposableTransients"/> to actually track disposable transient in
        /// container, so that disposal will be handled by container.</summary>
        public Rules WithoutThrowOnRegisteringDisposableTransient() =>
            WithSettings(_settings & ~Settings.ThrowOnRegisteringDisposableTransient);

        /// <summary><see cref="WithTrackingDisposableTransients"/></summary>
        public bool TrackingDisposableTransients =>
            (_settings & Settings.TrackingDisposableTransients) != 0;

        /// <summary>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.
        /// </summary>
        /// <remarks>Turning this setting On automatically turns off <see cref="ThrowOnRegisteringDisposableTransient"/>.</remarks>
        public Rules WithTrackingDisposableTransients() =>
            WithSettings((_settings | Settings.TrackingDisposableTransients)
                & ~Settings.ThrowOnRegisteringDisposableTransient);

        /// <summary><see cref="WithoutEagerCachingSingletonForFasterAccess"/>.</summary>
        public bool EagerCachingSingletonForFasterAccess =>
            (_settings & Settings.EagerCachingSingletonForFasterAccess) != 0;

        /// <summary>Turns off optimization: creating singletons during resolution of object graph.</summary>
        public Rules WithoutEagerCachingSingletonForFasterAccess() =>
            WithSettings(_settings & ~Settings.EagerCachingSingletonForFasterAccess);

        /// <summary><see cref="WithExpressionGeneration"/>.</summary>
        public Ref<ImHashMap<Request, System.Linq.Expressions.Expression>> DependencyResolutionCallExprs { get; private set; }

        /// <summary>Indicates that container is used for generation purposes, so it should use less runtime state</summary>
        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);

        /// <summary>Specifies to generate ResolutionCall dependency creation expression and stores the result 
        /// in the-per rules collection.</summary>
        public Rules WithExpressionGeneration(bool allowRuntimeState = false) =>
            new Rules(GetSettingsForExpressionGeneration(allowRuntimeState), FactorySelector, DefaultReuse,
                _made, DefaultIfAlreadyRegistered, DependencyDepthToSplitObjectGraph,
                Ref.Of(ImHashMap<Request, System.Linq.Expressions.Expression>.Empty), ItemToExpressionConverter,
                DynamicRegistrationProviders, UnknownServiceResolvers, DefaultRegistrationServiceKey);

        /// <summary>Indicates that rules are used for the validation, e.g. the rules created in `Validate` method</summary>
        public bool UsedForValidation => (_settings & Settings.UsedForValidation) != 0;

        private Settings GetSettingsForValidation() =>
            _settings & ~Settings.EagerCachingSingletonForFasterAccess
                      & ~Settings.ImplicitCheckForReuseMatchingScope
                      | Settings.UsedForValidation;

        /// <summary>Specifies to generate ResolutionCall dependency creation expression and stores the result 
        /// in the-per rules collection.</summary>
        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);

        /// <summary><see cref="ImplicitCheckForReuseMatchingScope"/></summary>
        public bool ImplicitCheckForReuseMatchingScope =>
            (_settings & Settings.ImplicitCheckForReuseMatchingScope) != 0;

        /// <summary>Removes implicit Factory <see cref="Setup.Condition"/> for non-transient service.
        /// The Condition filters out factory without matching scope.</summary>
        public Rules WithoutImplicitCheckForReuseMatchingScope() =>
            WithSettings(_settings & ~Settings.ImplicitCheckForReuseMatchingScope);

        /// <summary>Removes runtime optimizations preventing an expression generation.</summary>
        public Rules ForExpressionGeneration(bool allowRuntimeState = false) => WithSettings(GetSettingsForExpressionGeneration());

        /// <summary><see cref="WithResolveIEnumerableAsLazyEnumerable"/>.</summary>
        public bool ResolveIEnumerableAsLazyEnumerable =>
            (_settings & Settings.ResolveIEnumerableAsLazyEnumerable) != 0;

        /// <summary>Specifies to resolve IEnumerable as LazyEnumerable.</summary>
        public Rules WithResolveIEnumerableAsLazyEnumerable() =>
            WithSettings(_settings | Settings.ResolveIEnumerableAsLazyEnumerable);

        /// <summary><see cref="WithoutVariantGenericTypesInResolvedCollection"/>.</summary>
        public bool VariantGenericTypesInResolvedCollection =>
            (_settings & Settings.VariantGenericTypesInResolvedCollection) != 0;

        /// <summary>Flag instructs to include covariant compatible types in resolved collection.</summary>
        public Rules WithoutVariantGenericTypesInResolvedCollection() =>
            WithSettings(_settings & ~Settings.VariantGenericTypesInResolvedCollection);

        /// <summary><seew cref="WithDefaultIfAlreadyRegistered"/>.</summary>
        public IfAlreadyRegistered DefaultIfAlreadyRegistered { get; }

        /// <summary>Specifies default setting for container. By default is <see cref="IfAlreadyRegistered.AppendNotKeyed"/>.
        /// Example of use: specify Keep as a container default, then set AppendNonKeyed for explicit collection registrations.</summary>
        public Rules WithDefaultIfAlreadyRegistered(IfAlreadyRegistered rule) =>
            new Rules(_settings, FactorySelector, DefaultReuse,
                _made, rule, DependencyDepthToSplitObjectGraph, DependencyResolutionCallExprs, ItemToExpressionConverter,
                DynamicRegistrationProviders, UnknownServiceResolvers, DefaultRegistrationServiceKey);

        /// <summary><see cref="WithThrowIfRuntimeStateRequired"/>.</summary>
        public bool ThrowIfRuntimeStateRequired =>
            (_settings & Settings.ThrowIfRuntimeStateRequired) != 0;

        /// <summary>Specifies to throw an exception in attempt to resolve service which require runtime state for resolution.
        /// Runtime state may be introduced by RegisterDelegate, RegisterInstance, or registering with non-primitive service key, or metadata.</summary>
        public Rules WithThrowIfRuntimeStateRequired() =>
            WithSettings(_settings | Settings.ThrowIfRuntimeStateRequired);

        /// <summary><see cref="WithCaptureContainerDisposeStackTrace"/>.</summary>
        public bool CaptureContainerDisposeStackTrace =>
            (_settings & Settings.CaptureContainerDisposeStackTrace) != 0;

        /// <summary>Instructs to capture Dispose stack-trace to include it later into <see cref="Error.ContainerIsDisposed"/>
        /// exception for easy diagnostics.</summary>
        public Rules WithCaptureContainerDisposeStackTrace() =>
            WithSettings(_settings | Settings.CaptureContainerDisposeStackTrace);

        /// <summary>Allows Func with args specify its own reuse (sharing) behavior.</summary>
        public bool IgnoringReuseForFuncWithArgs =>
            (_settings & Settings.IgnoringReuseForFuncWithArgs) != 0;

        /// <summary>Allows Func with args specify its own reuse (sharing) behavior.</summary>
        public Rules WithIgnoringReuseForFuncWithArgs() =>
            WithSettings(_settings | Settings.IgnoringReuseForFuncWithArgs);

        /// <summary>Allows Func of service to be resolved even without registered service.</summary>
        public bool FuncAndLazyWithoutRegistration =>
            (_settings & Settings.FuncAndLazyWithoutRegistration) != 0;

        /// <summary>Allows Func of service to be resolved even without registered service.</summary>
        public Rules WithFuncAndLazyWithoutRegistration() =>
            WithSettings(_settings | Settings.FuncAndLazyWithoutRegistration);

        /// Commands to use FastExpressionCompiler - set by default.
        public bool UseFastExpressionCompiler =>
            (_settings & Settings.UseFastExpressionCompilerIfPlatformSupported) != 0;

        /// Fallbacks to system `Expression.Compile()`
        public Rules WithoutFastExpressionCompiler() =>
            WithSettings(_settings & ~Settings.UseFastExpressionCompilerIfPlatformSupported);

        /// Subject-subject
        public bool UseInterpretationForTheFirstResolution =>
            (_settings & Settings.UseInterpretationForTheFirstResolution) != 0;

        /// Fallbacks to system `Expression.Compile()`
        public Rules WithoutInterpretationForTheFirstResolution() =>
            WithSettings(_settings & ~Settings.UseInterpretationForTheFirstResolution & ~Settings.UseInterpretation);

        /// Subject
        public bool UseInterpretation => 
            (_settings & Settings.UseInterpretation) != 0;

        /// <summary>Uses DryIoc own interpretation mechanism or is falling back to `Compile(preferInterpretation: true)`</summary>
        public Rules WithUseInterpretation() => 
            WithSettings(_settings | Settings.UseInterpretation | Settings.UseInterpretationForTheFirstResolution);

        /// <summary>Uses DryIoc own interpretation mechanism or is falling back to `Compile(preferInterpretation: true)`</summary>
        public Rules WithoutUseInterpretation() => 
            WithSettings(_settings & ~Settings.UseInterpretation);

        /// <summary>If Decorator reuse is not set instructs to use `Decorator.SetupWith(useDecarateeReuse: true)`</summary>
        public bool UseDecorateeReuseForDecorators =>
            (_settings & Settings.UseDecorateeReuseForDecorators) != 0;

        /// <summary>If Decorator reuse is not set instructs to use `Decorator.SetupWith(useDecarateeReuse: true)`</summary>
        public Rules WithUseDecorateeReuseForDecorators() =>
            WithSettings(_settings | Settings.UseDecorateeReuseForDecorators);

        /// Outputs most notable non-default rules
        public override string ToString()
        {
            if (this == Default)
                return "Rules.Default";

            string s = "";
            if (_settings != DEFAULT_SETTINGS)
            {
                var addedSettings = _settings & ~DEFAULT_SETTINGS;
                if (addedSettings != 0)
                    s = "Rules with {" + addedSettings + "}";
                var removedSettings = DEFAULT_SETTINGS & ~_settings;
                if (removedSettings != 0)
                    s += (s != "" ? " and without {" : "Rules without {") + removedSettings + "}";
            }

            if (DependencyDepthToSplitObjectGraph != DefaultDependencyDepthToSplitObjectGraph)
                s += " with DepthToSplitObjectGraph=" + DependencyDepthToSplitObjectGraph;

            if (DefaultReuse != null && DefaultReuse != Reuse.Transient)
                s += (s != "" ? NewLine : "Rules ") + " with DefaultReuse=" + DefaultReuse;

            if (FactorySelector != null)
            {
                s += (s != "" ? NewLine : "Rules ") + " with FactorySelector=";
                if (FactorySelector == SelectLastRegisteredFactory)
                    s += nameof(Rules.SelectLastRegisteredFactory);
                else
                    s += "<custom>";
            }

            if (_made != Made.Default)
                s += (s != "" ? NewLine : "Rules ") + " with Made=" + _made;

            return s;
        }

#region Implementation

        private Rules()
        {
            _made = Made.Default;
            _settings = DEFAULT_SETTINGS;
            DefaultReuse = Reuse.Transient;
            DefaultIfAlreadyRegistered = IfAlreadyRegistered.AppendNotKeyed;
            DependencyDepthToSplitObjectGraph = DefaultDependencyDepthToSplitObjectGraph;
        }

        private Rules(Settings settings,
            FactorySelectorRule factorySelector,
            IReuse defaultReuse,
            Made made,
            IfAlreadyRegistered defaultIfAlreadyRegistered,
            int dependencyDepthToSplitObjectGraph,
            Ref<ImHashMap<Request, System.Linq.Expressions.Expression>> dependencyResolutionCallExprs,
            ItemToExpressionConverterRule itemToExpressionConverter,
            DynamicRegistrationProvider[] dynamicRegistrationProviders,
            UnknownServiceResolver[] unknownServiceResolvers,
            object defaultRegistrationServiceKey)
        {
            _settings = settings;
            _made = made;
            FactorySelector = factorySelector;
            DefaultReuse = defaultReuse;
            DefaultIfAlreadyRegistered = defaultIfAlreadyRegistered;
            DependencyDepthToSplitObjectGraph = dependencyDepthToSplitObjectGraph;
            DependencyResolutionCallExprs = dependencyResolutionCallExprs;
            ItemToExpressionConverter = itemToExpressionConverter;
            DynamicRegistrationProviders = dynamicRegistrationProviders;
            UnknownServiceResolvers = unknownServiceResolvers;
            DefaultRegistrationServiceKey = defaultRegistrationServiceKey;
        }

        private Rules WithSettings(Settings newSettings) =>
            new Rules(newSettings,
                FactorySelector, DefaultReuse, _made, DefaultIfAlreadyRegistered, DependencyDepthToSplitObjectGraph,
                DependencyResolutionCallExprs, ItemToExpressionConverter,
                DynamicRegistrationProviders, UnknownServiceResolvers, DefaultRegistrationServiceKey);

        private readonly Made _made;

        [Flags]
        private enum Settings
        {
            ThrowIfDependencyHasShorterReuseLifespan = 1 << 1,
            ThrowOnRegisteringDisposableTransient = 1 << 2,
            TrackingDisposableTransients = 1 << 3,
            ImplicitCheckForReuseMatchingScope = 1 << 4,
            VariantGenericTypesInResolvedCollection = 1 << 5,
            ResolveIEnumerableAsLazyEnumerable = 1 << 6,
            EagerCachingSingletonForFasterAccess = 1 << 7,
            ThrowIfRuntimeStateRequired = 1 << 8,
            CaptureContainerDisposeStackTrace = 1 << 9,
            UseDynamicRegistrationsAsFallbackOnly = 1 << 10,
            IgnoringReuseForFuncWithArgs = 1 << 11,
            OverrideRegistrationMade = 1 << 12,
            FuncAndLazyWithoutRegistration = 1 << 13,
            AutoConcreteTypeResolution = 1 << 14, // informational flag
            SelectLastRegisteredFactory = 1 << 15,// informational flag
            UsedForExpressionGeneration = 1 << 16,
            UseFastExpressionCompilerIfPlatformSupported = 1 << 17,
            UseInterpretationForTheFirstResolution = 1 << 18,
            UseInterpretation = 1 << 19,
            UseDecorateeReuseForDecorators = 1 << 20,
            UsedForValidation = 1 << 21 // informational flag, will appear in exceptions during validation
        }

        private const Settings DEFAULT_SETTINGS
            = Settings.ThrowIfDependencyHasShorterReuseLifespan
            | Settings.ThrowOnRegisteringDisposableTransient
            | Settings.ImplicitCheckForReuseMatchingScope
            | Settings.VariantGenericTypesInResolvedCollection
            | Settings.EagerCachingSingletonForFasterAccess
            | Settings.UseFastExpressionCompilerIfPlatformSupported
            | Settings.UseInterpretationForTheFirstResolution;

        private Settings _settings;

#endregion
    }

    /// <summary>Wraps constructor or factory method optionally with factory instance to create service.</summary>
    public sealed class FactoryMethod
    {
        /// <summary>Constructor or method to use for service creation.</summary>
        public readonly MemberInfo ConstructorOrMethodOrMember;

        /// <summary>Identifies factory service if factory method is instance member.</summary>
        public readonly ServiceInfo FactoryServiceInfo;

        /// Alternatively you may just provide an expression for factory
        public readonly Expression FactoryExpression;

        ///<summary> Contains resolved parameter expressions found when looking for most resolvable constructor</summary> 
        internal readonly Expression[] ResolvedParameterExpressions;

        /// <summary>Wraps method and factory instance.
        /// Where <paramref name="ctorOrMethodOrMember"/> is constructor, static or instance method, property or field.</summary>
        public static FactoryMethod Of(MemberInfo ctorOrMethodOrMember, ServiceInfo factoryInfo = null)
        {
            if (ctorOrMethodOrMember == null)
                Throw.It(Error.PassedCtorOrMemberIsNull);

            if (ctorOrMethodOrMember is ConstructorInfo == false && !ctorOrMethodOrMember.IsStatic())
            {
                if (factoryInfo == null)
                    Throw.It(Error.PassedMemberIsNotStaticButInstanceFactoryIsNull, ctorOrMethodOrMember);
            }
            else
            {
                if (factoryInfo != null)
                    Throw.It(Error.PassedMemberIsStaticButInstanceFactoryIsNotNull, ctorOrMethodOrMember, factoryInfo);
            }

            return new FactoryMethod(ctorOrMethodOrMember, factoryInfo);
        }

        /// <summary>Wraps method and factory instance.
        /// Where <paramref name="methodOrMember"/> is constructor, static or instance method, property or field.</summary>
        public static FactoryMethod Of(MemberInfo methodOrMember, object factoryInstance)
        {
            factoryInstance.ThrowIfNull();
            methodOrMember.ThrowIfNull(Error.PassedCtorOrMemberIsNull);
            if (methodOrMember.IsStatic())
                Throw.It(Error.PassedMemberIsStaticButInstanceFactoryIsNotNull, methodOrMember, factoryInstance);
            return new FactoryMethod(methodOrMember, Constant(factoryInstance));
        }

        /// <summary>Discovers the static factory method or member by name in <typeparamref name="TFactory"/>.
        /// Should play nice with C# <see langword="nameof"/> operator.</summary>
        public static FactoryMethod Of<TFactory>(string methodOrMemberName) =>
            Of(typeof(TFactory).GetAllMembers().FindFirst(m => m.Name == methodOrMemberName));

        /// <summary>Pretty prints wrapped method.</summary>
        public override string ToString()
        {
            var s = new StringBuilder("{")
                .Print(ConstructorOrMethodOrMember.DeclaringType)
                .Append('.').Append(ConstructorOrMethodOrMember);
            if (FactoryServiceInfo != null)
                s.Append(" of factory service ").Append(FactoryServiceInfo);
            if (FactoryExpression != null)
                s.Append(" of factory expression ").Append(FactoryExpression);
            return s.Append('}').ToString();
        }

        private struct CtorWithParameters
        {
            public ConstructorInfo Ctor;
            public ParameterInfo[] Params;
        }

        private static void OrderByParamsLengthDescendingViaInsertionSort(CtorWithParameters[] items)
        {
            int i, j;
            for (i = 1; i < items.Length; i++)
            {
                var tmp = items[i];

                for (j = i; j >= 1 && tmp.Params.Length > items[j - 1].Params.Length; j--)
                {
                    ref var target = ref items[j];
                    var source = items[j - 1];
                    target.Ctor = source.Ctor;
                    target.Params = source.Params;
                }

                ref var x = ref items[j];
                x.Ctor   = tmp.Ctor;
                x.Params = tmp.Params;
            }
        }

        /// <summary>Easy way to specify non-public and most resolvable constructor.</summary>
        /// <param name="mostResolvable">(optional) Instructs to select constructor with max number of params which all are resolvable.</param>
        /// <param name="includeNonPublic">(optional) Consider the non-public constructors.</param>
        /// <returns>Constructor or null if not found.</returns>
        public static FactoryMethodSelector Constructor(bool mostResolvable = false, bool includeNonPublic = false) => request =>
        {
            var implType = request.ImplementationType;
            if (implType == null)
                Throw.It(Error.ImplTypeIsNotSpecifiedForAutoCtorSelection, request);
            
            var ctors = includeNonPublic ? implType.GetInstanceConstructors() : implType.GetPublicInstanceConstructors();
            var ctorCount = ctors.Length;
            if (ctorCount == 0)
                return null;

            // if there is only one constructor then use it
            if (ctorCount == 1)
                return new FactoryMethod(ctors[0]);
             
            // stop here if you need a lookup for most resolvable constructor
            if (!mostResolvable)
                return null;

            var rules = request.Rules;
            var selector = rules.OverrideRegistrationMade
                ? request.Made.Parameters.OverrideWith(rules.Parameters)
                : rules.Parameters.OverrideWith(request.Made.Parameters);
            var paramSelector = selector(request);

            var throwIfCtorNotFound = request.IfUnresolved != IfUnresolved.ReturnDefault;
            if (throwIfCtorNotFound)
                request = request.WithIfUnresolved(IfUnresolved.ReturnDefault);

            var ctorsWithParameters = new CtorWithParameters[ctors.Length];
            if (ctors.Length == 2)
            {
                ref var pos0 = ref ctorsWithParameters[0];
                ref var pos1 = ref ctorsWithParameters[1];

                var ctor0Params = ctors[0].GetParameters();
                var ctor1Params = ctors[1].GetParameters();
                if (ctor1Params.Length > ctor0Params.Length)
                {
                    pos0.Ctor   = ctors[1];
                    pos0.Params = ctor1Params;
                    pos1.Ctor   = ctors[0];
                    pos1.Params = ctor0Params;
                }
                else
                {
                    pos0.Ctor = ctors[0];
                    pos0.Params = ctor0Params;
                    pos1.Ctor = ctors[1];
                    pos1.Params = ctor1Params;
                }
            }
            else
            {
                for (var i = 0; i < ctors.Length; i++)
                {
                    var x = ctors[i];
                    ref var pos = ref ctorsWithParameters[i];
                    pos.Ctor   = x;
                    pos.Params = x.GetParameters();
                }

                OrderByParamsLengthDescendingViaInsertionSort(ctorsWithParameters);
            }

            var mostUsedArgCount = -1;
            ConstructorInfo mostResolvedCtor = null;
            Expression[] mostResolvedExprs   = null;
            for (var c = 0; c < ctorsWithParameters.Length; ++c)
            {
                var parameters = ctorsWithParameters[c].Params;
                if (parameters.Length == 0)
                {
                    mostResolvedCtor = mostResolvedCtor ?? ctorsWithParameters[c].Ctor;
                    break;
                }

                // If the most resolved expressions (constructor) is found and the next one has less parameters, we exit. 
                if (mostResolvedExprs != null && mostResolvedExprs.Length > parameters.Length)
                    break;

                // Otherwise for similar parameters count constructor we prefer the one with most used input args / custom values
                // Should count custom values provided via `Resolve(args)`, `Func<args..>`, `Parameter.Of...(_ -> arg)`, `container.Use(arg)`
                var usedInputArgOrUsedOrCustomValueCount = 0;
                var inputArgs = request.InputArgExprs;
                var argsUsedMask = 0;
                var paramExprs = new Expression[parameters.Length];

                for (var i = 0; i < parameters.Length; i++)
                {
                    var param = parameters[i];
                    if (inputArgs != null)
                    {
                        var inputArgExpr =
                            ReflectionFactory.TryGetExpressionFromInputArgs(param.ParameterType, inputArgs,
                                ref argsUsedMask);
                        if (inputArgExpr != null)
                        {
                            ++usedInputArgOrUsedOrCustomValueCount;
                            paramExprs[i] = inputArgExpr;
                            continue;
                        }
                    }

                    var paramInfo = paramSelector(param) ?? ParameterServiceInfo.Of(param);
                    var paramRequest = request.Push(paramInfo);
                    var paramDetails = paramInfo.Details;
                    var usedOrCustomValExpr =
                        ReflectionFactory.TryGetUsedInstanceOrCustomValueExpression(request, paramRequest,
                            paramDetails);
                    if (usedOrCustomValExpr != null)
                    {
                        ++usedInputArgOrUsedOrCustomValueCount;
                        paramExprs[i] = usedOrCustomValExpr;
                        continue;
                    }

                    var injectedExpr = request.Container.ResolveFactory(paramRequest)?.GetExpressionOrDefault(paramRequest);
                    if (injectedExpr == null ||
                        // When param is an empty array / collection, then we may use a default value instead (#581)
                        paramDetails.DefaultValue != null &&
                        injectedExpr.NodeType == System.Linq.Expressions.ExpressionType.NewArrayInit &&
                        ((NewArrayExpression) injectedExpr).Expressions.Count == 0)
                    {
                        // Check if parameter dependency itself (without propagated parent details)
                        // does not allow default, then stop checking the rest of parameters.
                        if (paramDetails.IfUnresolved == IfUnresolved.Throw)
                        {
                            paramExprs = null;
                            break;
                        }

                        injectedExpr = paramDetails.DefaultValue != null
                            ? request.Container.GetConstantExpression(paramDetails.DefaultValue)
                            : paramRequest.ServiceType.GetDefaultValueExpression();
                    }

                    paramExprs[i] = injectedExpr;
                }

                if (paramExprs != null && usedInputArgOrUsedOrCustomValueCount > mostUsedArgCount)
                {
                    mostUsedArgCount = usedInputArgOrUsedOrCustomValueCount;
                    mostResolvedCtor = ctorsWithParameters[c].Ctor;
                    mostResolvedExprs = paramExprs;
                }
            }

            if (mostResolvedCtor == null)
                return Throw.For<FactoryMethod>(throwIfCtorNotFound, 
                    Error.UnableToFindCtorWithAllResolvableArgs, request.InputArgExprs, request);

            return new FactoryMethod(mostResolvedCtor, mostResolvedExprs);
        };

        /// <summary>Easy way to specify default constructor to be used for resolution.</summary>
        public static FactoryMethodSelector DefaultConstructor(bool includeNonPublic = false) => request =>
            request.ImplementationType.ThrowIfNull(Error.ImplTypeIsNotSpecifiedForAutoCtorSelection, request)
                .GetConstructorOrNull(includeNonPublic, Empty<Type>())
                ?.To(ctor => new FactoryMethod(ctor));

        /// Better be named `ConstructorWithMostResolvableArguments`.
        /// Searches for public constructor with most resolvable parameters or throws <see cref="ContainerException"/> if not found.
        /// Works both for resolving service and `Func{TArgs..., TService}`
        public static readonly FactoryMethodSelector ConstructorWithResolvableArguments =
            Constructor(mostResolvable: true);

        /// <summary>Searches for constructor (including non public ones) with most
        /// resolvable parameters or throws <see cref="ContainerException"/> if not found.
        /// Works both for resolving service and Func{TArgs..., TService}</summary>
        public static readonly FactoryMethodSelector ConstructorWithResolvableArgumentsIncludingNonPublic =
            Constructor(mostResolvable: true, includeNonPublic: true);

        /// <summary>Just creates a thingy from the constructor</summary>
        public FactoryMethod(ConstructorInfo constructor) => ConstructorOrMethodOrMember = constructor;

        private FactoryMethod(MemberInfo constructorOrMethodOrMember, ServiceInfo factoryServiceInfo = null)
        {
            ConstructorOrMethodOrMember = constructorOrMethodOrMember;
            FactoryServiceInfo = factoryServiceInfo;
        }

        internal FactoryMethod(MemberInfo constructorOrMethodOrMember, Expression factoryExpression)
        {
            ConstructorOrMethodOrMember = constructorOrMethodOrMember;
            FactoryExpression = factoryExpression;
        }

        internal FactoryMethod(ConstructorInfo ctor, Expression[] resolvedParameterExpressions)
        {
            ConstructorOrMethodOrMember = ctor;
            ResolvedParameterExpressions = resolvedParameterExpressions;
        }
    }

    /// <summary>Rules how to: <list type="bullet">
    /// <item>Select constructor for creating service with <see cref="FactoryMethod"/>.</item>
    /// <item>Specify how to resolve constructor parameters with <see cref="Parameters"/>.</item>
    /// <item>Specify what properties/fields to resolve and how with <see cref="PropertiesAndFields"/>.</item>
    /// </list></summary>
    public class Made
    {
        /// <summary>Returns delegate to select constructor based on provided request.</summary>
        public FactoryMethodSelector FactoryMethod { get; private set; }

        /// <summary>Return type of strongly-typed factory method expression.</summary>
        public Type FactoryMethodKnownResultType { get; private set; }

        [Flags]
        private enum MadeDetails
        {
            NoConditionals = 0,
            ImplTypeDependsOnRequest = 1 << 1,
            ImplMemberDependsOnRequest = 1 << 3,
            HasCustomDependencyValue = 1 << 4
        }

        private readonly MadeDetails _details;

        /// Has any conditional flags
        public bool IsConditional => _details != MadeDetails.NoConditionals; 

        /// True is made has properties or parameters with custom value.
        /// That's mean the whole made become context based which affects caching.
        public bool HasCustomDependencyValue => (_details & MadeDetails.HasCustomDependencyValue) != 0;

        /// <summary>Indicates that the implementation type depends on request.</summary>
        public bool IsConditionalImplementation => (_details & MadeDetails.ImplTypeDependsOnRequest) != 0;

        /// Indicates that the member depends on request
        public bool IsImplMemberDependsOnRequest => (_details & MadeDetails.ImplMemberDependsOnRequest) != 0;

        /// <summary>Specifies how constructor parameters should be resolved:
        /// parameter service key and type, throw or return default value if parameter is unresolved.</summary>
        public ParameterSelector Parameters { get; private set; }

        /// <summary>Specifies what <see cref="ServiceInfo"/> should be used when resolving property or field.</summary>
        public PropertiesAndFieldsSelector PropertiesAndFields { get; private set; }

        /// <summary>Outputs whatever is possible (known) for Made</summary>
        public override string ToString()
        {
            if (this == Default)
                return "Made.Default";

            var s = "{";
            if (FactoryMethod != null)
            {
                s += (s == "{" ? "" : ", ") + "FactoryMethod=";
                if (FactoryMethod == DryIoc.FactoryMethod.ConstructorWithResolvableArguments)
                    s += nameof(DryIoc.FactoryMethod.ConstructorWithResolvableArguments);
                else if (FactoryMethod == DryIoc.FactoryMethod.ConstructorWithResolvableArgumentsIncludingNonPublic)
                    s += nameof(DryIoc.FactoryMethod.ConstructorWithResolvableArgumentsIncludingNonPublic);
                else
                    s += "<custom>";
            }

            if (FactoryMethodKnownResultType != null)
                s += (s == "{" ? "" : ", ") + "FactoryMethodKnownResultType=" + FactoryMethodKnownResultType;
            if (HasCustomDependencyValue)
                s += (s == "{" ? "" : ", ") + "HasCustomDependencyValue=true";
            if (PropertiesAndFields != null)
                s += (s == "{" ? "" : ", ") + "PropertiesAndFields=<custom>";
            if (Parameters != null)
                s += (s == "{" ? "" : ", ") + "ParameterSelector=<custom>";
            return s + "}";
        }

        /// <summary>Container will use some sensible defaults for service creation.</summary>
        public static readonly Made Default = new Made();

        /// <summary>Creates rules with only <see cref="FactoryMethod"/> specified.</summary>
        public static implicit operator Made(FactoryMethodSelector factoryMethod) =>
            Of(factoryMethod);

        /// <summary>Creates rules with only <see cref="Parameters"/> specified.</summary>
        public static implicit operator Made(ParameterSelector parameters) =>
            Of(parameters: parameters);

        /// <summary>Creates rules with only <see cref="PropertiesAndFields"/> specified.</summary>
        public static implicit operator Made(PropertiesAndFieldsSelector propertiesAndFields) =>
            Of(propertiesAndFields: propertiesAndFields);

        /// <summary>Specifies injections rules for Constructor, Parameters, Properties and Fields. If no rules specified returns <see cref="Default"/> rules.</summary>
        public static Made Of(FactoryMethodSelector factoryMethod = null,
            ParameterSelector parameters = null, PropertiesAndFieldsSelector propertiesAndFields = null,
            bool isConditionalImlementation = false) =>
            factoryMethod == null && parameters == null && propertiesAndFields == null && !isConditionalImlementation ? Default :
            new Made(factoryMethod, parameters, propertiesAndFields, isConditionalImlementation: isConditionalImlementation);

        /// <summary>Specifies injections rules for Constructor, Parameters, Properties and Fields. If no rules specified returns <see cref="Default"/> rules.</summary>
        /// <param name="factoryMethod">Known factory method.</param>
        /// <param name="parameters">(optional)</param> <param name="propertiesAndFields">(optional)</param>
        /// <returns>New injection rules.</returns>
        public static Made Of(FactoryMethod factoryMethod,
            ParameterSelector parameters = null, PropertiesAndFieldsSelector propertiesAndFields = null)
        {
            var methodReturnType = factoryMethod.ThrowIfNull()
                .ConstructorOrMethodOrMember.GetReturnTypeOrDefault();

            // Normalizes open-generic type to open-generic definition,
            // because for base classes and return types it may not be the case (they may be partially closed).
            if (methodReturnType != null && methodReturnType.IsOpenGeneric())
                methodReturnType = methodReturnType.GetGenericTypeDefinition();

            return new Made(factoryMethod.ToFunc<Request, FactoryMethod>, parameters, propertiesAndFields, methodReturnType);
        }

        /// <summary>Creates factory method specification</summary>
        public static Made Of(MemberInfo factoryMethodOrMember, ServiceInfo factoryInfo = null,
            ParameterSelector parameters = null, PropertiesAndFieldsSelector propertiesAndFields = null) =>
            Of(DryIoc.FactoryMethod.Of(factoryMethodOrMember, factoryInfo), parameters, propertiesAndFields);

        /// <summary>Creates factory specification with implementation type, conditionally depending on request.</summary>
        public static Made Of(Func<Request, Type> getImplType,
            ParameterSelector parameters = null, PropertiesAndFieldsSelector propertiesAndFields = null) =>
            Of(r => DryIoc.FactoryMethod.Of(getImplType(r).SingleConstructor()),
                parameters, propertiesAndFields, isConditionalImlementation: true);

        /// <summary>Creates factory specification with method or member selector based on request.
        /// Where <paramref name="getMethodOrMember"/> is method, or constructor, or member selector.</summary>
        public static Made Of(Func<Request, MemberInfo> getMethodOrMember, ServiceInfo factoryInfo = null,
            ParameterSelector parameters = null, PropertiesAndFieldsSelector propertiesAndFields = null) =>
            new Made(r => DryIoc.FactoryMethod.Of(getMethodOrMember(r), factoryInfo),
                parameters, propertiesAndFields, isImplMemberDependsOnRequest: true);

        /// <summary>Creates factory specification with method or member selector based on request.
        /// Where <paramref name="getMethodOrMember"/>Method, or constructor, or member selector.</summary>
        public static Made Of(Func<Request, MemberInfo> getMethodOrMember, Func<Request, ServiceInfo> factoryInfo,
            ParameterSelector parameters = null, PropertiesAndFieldsSelector propertiesAndFields = null) =>
            new Made(r => DryIoc.FactoryMethod.Of(getMethodOrMember(r), factoryInfo(r)), 
                parameters, propertiesAndFields, isImplMemberDependsOnRequest: true);

        /// <summary>Defines how to select constructor from implementation type.
        /// Where <paramref name="getConstructor"/> is delegate taking implementation type as input 
        /// and returning selected constructor info.</summary>
        public static Made Of(Func<Type, ConstructorInfo> getConstructor, ParameterSelector parameters = null,
            PropertiesAndFieldsSelector propertiesAndFields = null) =>
            Of(r => DryIoc.FactoryMethod.Of(getConstructor(r.ImplementationType).ThrowIfNull(Error.GotNullConstructorFromFactoryMethod, r)),
                parameters, propertiesAndFields);

        /// <summary>Defines factory method using expression of constructor call (with properties), or static method call.</summary>
        /// <typeparam name="TService">Type with constructor or static method.</typeparam>
        /// <param name="serviceReturningExpr">Expression tree with call to constructor with properties:
        /// <code lang="cs"><![CDATA[() => new Car(Arg.Of<IEngine>()) { Color = Arg.Of<Color>("CarColor") }]]></code>
        /// or static method call <code lang="cs"><![CDATA[() => Car.Create(Arg.Of<IEngine>())]]></code></param>
        /// <param name="argValues">(optional) Primitive custom values for dependencies.</param>
        /// <returns>New Made specification.</returns>
        public static TypedMade<TService> Of<TService>(
            System.Linq.Expressions.Expression<Func<TService>> serviceReturningExpr,
            params Func<Request, object>[] argValues) =>
            FromExpression<TService>(member => _ => DryIoc.FactoryMethod.Of(member), serviceReturningExpr, argValues);

        /// <summary>Defines creation info from factory method call Expression without using strings.
        /// You can supply any/default arguments to factory method, they won't be used, it is only to find the <see cref="MethodInfo"/>.</summary>
        /// <typeparam name="TFactory">Factory type.</typeparam> <typeparam name="TService">Factory product type.</typeparam>
        /// <param name="getFactoryInfo">Returns or resolves factory instance.</param>
        /// <param name="serviceReturningExpr">Method, property or field expression returning service.</param>
        /// <param name="argValues">(optional) Primitive custom values for dependencies.</param>
        /// <returns>New Made specification.</returns>
        public static TypedMade<TService> Of<TFactory, TService>(
            Func<Request, ServiceInfo.Typed<TFactory>> getFactoryInfo,
            System.Linq.Expressions.Expression<Func<TFactory, TService>> serviceReturningExpr,
            params Func<Request, object>[] argValues)
            where TFactory : class
        {
            getFactoryInfo.ThrowIfNull();
            return FromExpression<TService>(member => request => DryIoc.FactoryMethod.Of(member, getFactoryInfo(request)),
                serviceReturningExpr, argValues);
        }

        /// Composes Made.Of expression with known factory instance and expression to get a service
        public static TypedMade<TService> Of<TFactory, TService>(
            TFactory factoryInstance,
            System.Linq.Expressions.Expression<Func<TFactory, TService>> serviceReturningExpr,
            params Func<Request, object>[] argValues)
            where TFactory : class
        {
            factoryInstance.ThrowIfNull();
            return FromExpression<TService>(
                member => request => DryIoc.FactoryMethod.Of(member, factoryInstance),
                serviceReturningExpr, argValues);
        }

        private static TypedMade<TService> FromExpression<TService>(
            Func<MemberInfo, FactoryMethodSelector> getFactoryMethodSelector,
            System.Linq.Expressions.LambdaExpression serviceReturningExpr, params Func<Request, object>[] argValues)
        {
            var callExpr = serviceReturningExpr.ThrowIfNull().Body;
            if (callExpr.NodeType == System.Linq.Expressions.ExpressionType.Convert) // proceed without Cast expression.
                return FromExpression<TService>(getFactoryMethodSelector,
                    System.Linq.Expressions.Expression.Lambda(((System.Linq.Expressions.UnaryExpression)callExpr).Operand,
                        Empty<System.Linq.Expressions.ParameterExpression>()),
                    argValues);

            MemberInfo ctorOrMethodOrMember;
            IList<System.Linq.Expressions.Expression> argExprs = null;
            IList<System.Linq.Expressions.MemberBinding> memberBindingExprs = null;
            ParameterInfo[] parameters = null;

            if (callExpr.NodeType == System.Linq.Expressions.ExpressionType.New ||
                callExpr.NodeType == System.Linq.Expressions.ExpressionType.MemberInit)
            {
                var newExpr = callExpr as System.Linq.Expressions.NewExpression ?? ((System.Linq.Expressions.MemberInitExpression)callExpr).NewExpression;
                ctorOrMethodOrMember = newExpr.Constructor;
                parameters = newExpr.Constructor.GetParameters();
                argExprs = newExpr.Arguments;
                if (callExpr is System.Linq.Expressions.MemberInitExpression)
                    memberBindingExprs = ((System.Linq.Expressions.MemberInitExpression)callExpr).Bindings;
            }
            else if (callExpr.NodeType == System.Linq.Expressions.ExpressionType.Call)
            {
                var methodCallExpr = (System.Linq.Expressions.MethodCallExpression)callExpr;
                ctorOrMethodOrMember = methodCallExpr.Method;
                parameters = methodCallExpr.Method.GetParameters();
                argExprs = methodCallExpr.Arguments;
            }
            else if (callExpr.NodeType == ExprType.Invoke)
            {
                var invokeExpr = (System.Linq.Expressions.InvocationExpression)callExpr;
                var invokedDelegateExpr = invokeExpr.Expression;
                var invokeMethod = invokedDelegateExpr.Type.GetTypeInfo().GetDeclaredMethod(nameof(Action.Invoke));
                ctorOrMethodOrMember = invokeMethod;
                parameters = invokeMethod.GetParameters();
                argExprs = invokeExpr.Arguments;
            }

            else if (callExpr.NodeType == System.Linq.Expressions.ExpressionType.MemberAccess)
            {
                var member = ((System.Linq.Expressions.MemberExpression)callExpr).Member;
                Throw.If(!(member is PropertyInfo) && !(member is FieldInfo),
                    Error.UnexpectedFactoryMemberExpressionInMadeOf, member, serviceReturningExpr);
                ctorOrMethodOrMember = member;
            }
            else return Throw.For<TypedMade<TService>>(Error.NotSupportedMadeOfExpression, callExpr);

            var hasCustomValue = false;

            var parameterSelector = parameters.IsNullOrEmpty() ? null
                : ComposeParameterSelectorFromArgs(ref hasCustomValue,
                    serviceReturningExpr, parameters, argExprs, argValues);

            var propertiesAndFieldsSelector =
                memberBindingExprs == null || memberBindingExprs.Count == 0 ? null
                : ComposePropertiesAndFieldsSelector(ref hasCustomValue,
                    serviceReturningExpr, memberBindingExprs, argValues);

            return new TypedMade<TService>(getFactoryMethodSelector(ctorOrMethodOrMember),
                parameterSelector, propertiesAndFieldsSelector, hasCustomValue);
        }

        /// <summary>Typed version of <see cref="Made"/> specified with statically typed expression tree.</summary>
        public sealed class TypedMade<TService> : Made
        {
            internal TypedMade(FactoryMethodSelector factoryMethod = null,
                ParameterSelector parameters = null, PropertiesAndFieldsSelector propertiesAndFields = null,
                bool hasCustomValue = false)
                : base(factoryMethod, parameters, propertiesAndFields, typeof(TService), hasCustomValue)
            { }
        }

#region Implementation

        internal Made(
            FactoryMethodSelector factoryMethod = null, ParameterSelector parameters = null, PropertiesAndFieldsSelector propertiesAndFields = null,
            Type factoryMethodKnownResultType = null, bool hasCustomValue = false, bool isConditionalImlementation = false, 
            bool isImplMemberDependsOnRequest = false)
        {
            FactoryMethod = factoryMethod;
            Parameters = parameters;
            PropertiesAndFields = propertiesAndFields;
            FactoryMethodKnownResultType = factoryMethodKnownResultType;

            var details = default(MadeDetails);
            if (hasCustomValue)
                details |= MadeDetails.HasCustomDependencyValue;
            if (isConditionalImlementation)
                details |= MadeDetails.ImplTypeDependsOnRequest;
            if (isImplMemberDependsOnRequest)
                details |= MadeDetails.ImplMemberDependsOnRequest;
            _details = details;
        }

        internal Made(FactoryMethod factoryMethod, Type factoryReturnType)
        {
            FactoryMethod = factoryMethod.ToFunc<Request, FactoryMethod>;
            FactoryMethodKnownResultType = factoryReturnType;
        }

        private static ParameterSelector ComposeParameterSelectorFromArgs(ref bool hasCustomValue,
            System.Linq.Expressions.Expression wholeServiceExpr, ParameterInfo[] paramInfos,
            IList<System.Linq.Expressions.Expression> argExprs,
            params Func<Request, object>[] argValues)
        {
            var paramSelector = DryIoc.Parameters.Of;
            for (var i = 0; i < argExprs.Count; i++)
            {
                var paramInfo = paramInfos[i];
                var methodCallExpr = argExprs[i] as System.Linq.Expressions.MethodCallExpression;
                if (methodCallExpr != null)
                {
                    Throw.If(methodCallExpr.Method.DeclaringType != typeof(Arg),
                        Error.UnexpectedExpressionInsteadOfArgMethodInMadeOf, methodCallExpr, wholeServiceExpr);

                    if (methodCallExpr.Method.Name == Arg.ArgIndexMethodName)
                    {
                        var getArgValue = GetArgCustomValueProvider(wholeServiceExpr, methodCallExpr, argValues);
                        paramSelector = paramSelector.Details((r, p) => p.Equals(paramInfo) ? ServiceDetails.Of(getArgValue(r)) : null);
                        hasCustomValue = true;
                    }
                    else // handle service details
                    {
                        var defaultValue = paramInfo.IsOptional ? paramInfo.DefaultValue : null;
                        var argDetails = GetArgServiceDetails(wholeServiceExpr,
                            methodCallExpr, paramInfo.ParameterType, IfUnresolved.Throw, defaultValue);
                        paramSelector = paramSelector.Details((r, p) => p.Equals(paramInfo) ? argDetails : null);
                    }
                }
                else
                {
                    var customValue = GetArgExpressionValueOrThrow(wholeServiceExpr, argExprs[i]);
                    paramSelector = paramSelector.Details((r, p) => p.Equals(paramInfo) ? ServiceDetails.Of(customValue) : null);
                }
            }
            return paramSelector;
        }

        private static PropertiesAndFieldsSelector ComposePropertiesAndFieldsSelector(ref bool hasCustomValue,
            System.Linq.Expressions.Expression wholeServiceExpr, IList<System.Linq.Expressions.MemberBinding> memberBindings,
            params Func<Request, object>[] argValues)
        {
            var propertiesAndFields = DryIoc.PropertiesAndFields.Of;
            for (var i = 0; i < memberBindings.Count; i++)
            {
                var memberAssignment = (memberBindings[i] as System.Linq.Expressions.MemberAssignment).ThrowIfNull();
                var member = memberAssignment.Member;

                var methodCallExpr = memberAssignment.Expression as System.Linq.Expressions.MethodCallExpression;
                if (methodCallExpr == null) // not an Arg.Of: e.g. constant or variable
                {
                    var customValue = GetArgExpressionValueOrThrow(wholeServiceExpr, memberAssignment.Expression);
                    propertiesAndFields = propertiesAndFields.OverrideWith(r =>
                        PropertyOrFieldServiceInfo.Of(member).WithDetails(ServiceDetails.Of(customValue)).One());
                }
                else
                {
                    Throw.If(methodCallExpr.Method.DeclaringType != typeof(Arg),
                        Error.UnexpectedExpressionInsteadOfArgMethodInMadeOf, methodCallExpr, wholeServiceExpr);

                    if (methodCallExpr.Method.Name == Arg.ArgIndexMethodName) // handle custom value
                    {
                        var getArgValue = GetArgCustomValueProvider(wholeServiceExpr, methodCallExpr, argValues);
                        propertiesAndFields = propertiesAndFields.OverrideWith(req => 
                            PropertyOrFieldServiceInfo.Of(member).WithDetails(ServiceDetails.Of(getArgValue(req))).One());
                        hasCustomValue = true;
                    }
                    else
                    {
                        var memberType = member.GetReturnTypeOrDefault();
                        var argServiceDetails = GetArgServiceDetails(wholeServiceExpr, methodCallExpr, memberType, IfUnresolved.ReturnDefault, null);
                        propertiesAndFields = propertiesAndFields.OverrideWith(r =>
                            PropertyOrFieldServiceInfo.Of(member).WithDetails(argServiceDetails).One());
                    }
                }
            }
            return propertiesAndFields;
        }

        private static Func<Request, object> GetArgCustomValueProvider(
            System.Linq.Expressions.Expression wholeServiceExpr,
            System.Linq.Expressions.MethodCallExpression methodCallExpr, Func<Request, object>[] argValues)
        {
            Throw.If(argValues.IsNullOrEmpty(), Error.ArgValueIndexIsProvidedButNoArgValues, wholeServiceExpr);

            var argIndex = (int)GetArgExpressionValueOrThrow(wholeServiceExpr, methodCallExpr.Arguments[0]);
            if (argIndex < 0 || argIndex >= argValues.Length)
                Throw.It(Error.ArgValueIndexIsOutOfProvidedArgValues, argIndex, argValues, wholeServiceExpr);

            return argValues[argIndex];
        }

        private static ServiceDetails GetArgServiceDetails(
            System.Linq.Expressions.Expression wholeServiceExpr,
            System.Linq.Expressions.MethodCallExpression methodCallExpr,
            Type dependencyType, IfUnresolved defaultIfUnresolved, object defaultValue)
        {
            var requiredServiceType = methodCallExpr.Method.GetGenericArguments().Last();
            if (requiredServiceType == dependencyType)
                requiredServiceType = null;

            var serviceKey = default(object);
            var metadataKey = default(string);
            var metadata = default(object);
            var ifUnresolved = defaultIfUnresolved;

            var hasPrevArg = false;

            var argExprs = methodCallExpr.Arguments;
            if (argExprs.Count == 2 &&
                argExprs[0].Type == typeof(string) &&
                argExprs[1].Type != typeof(IfUnresolved)) // matches the Of overload for metadata
            {
                metadataKey = (string)GetArgExpressionValueOrThrow(wholeServiceExpr, argExprs[0]);
                metadata = GetArgExpressionValueOrThrow(wholeServiceExpr, argExprs[1]);
            }
            else
            {
                for (var a = 0; a < argExprs.Count; a++)
                {
                    var argValue = GetArgExpressionValueOrThrow(wholeServiceExpr, argExprs[a]);
                    if (argValue != null)
                    {
                        if (argValue is IfUnresolved)
                        {
                            ifUnresolved = (IfUnresolved)argValue;
                            if (hasPrevArg) // the only possible argument is default value.
                            {
                                defaultValue = serviceKey;
                                serviceKey = null;
                            }
                        }
                        else
                        {
                            serviceKey = argValue;
                            hasPrevArg = true;
                        }
                    }
                }
            }

            return ServiceDetails.Of(requiredServiceType, serviceKey, ifUnresolved, defaultValue, metadataKey, metadata);
        }

        private static object GetArgExpressionValueOrThrow(
            System.Linq.Expressions.Expression wholeServiceExpr,
            System.Linq.Expressions.Expression argExpr)
        {
            var valueExpr = argExpr as System.Linq.Expressions.ConstantExpression;
            if (valueExpr != null)
                return valueExpr.Value;

            var convert = argExpr as System.Linq.Expressions.UnaryExpression; // e.g. (object)SomeEnum.Value
            if (convert != null && convert.NodeType == ExprType.Convert)
                return GetArgExpressionValueOrThrow(wholeServiceExpr,
                    convert.Operand as System.Linq.Expressions.ConstantExpression);

            var member = argExpr as System.Linq.Expressions.MemberExpression;
            if (member != null)
            {
                var memberOwner = member.Expression as System.Linq.Expressions.ConstantExpression;
                if (memberOwner != null && memberOwner.Type.IsClosureType() && member.Member is FieldInfo)
                    return ((FieldInfo)member.Member).GetValue(memberOwner.Value);
            }

            var newArrExpr = argExpr as System.Linq.Expressions.NewArrayExpression;
            if (newArrExpr != null)
            {
                var itemExprs = newArrExpr.Expressions;
                var items = new object[itemExprs.Count];
                for (var i = 0; i < itemExprs.Count; i++)
                    items[i] = GetArgExpressionValueOrThrow(wholeServiceExpr, itemExprs[i]);

                return Converter.ConvertMany(items, newArrExpr.Type.GetElementType());
            }

            return Throw.For<object>(Error.UnexpectedExpressionInsteadOfConstantInMadeOf,
                argExpr, wholeServiceExpr);
        }

#endregion
    }

    /// <summary>Class for defining parameters/properties/fields service info in <see cref="Made"/> expressions.
    /// Arg methods are NOT actually called, they just used to reflect service info from call expression.</summary>
    public static class Arg
    {
        /// <summary>Specifies required service type of parameter or member. If required type is the same as parameter/member type,
        /// the method is just a placeholder to help detect constructor or factory method, and does not have additional meaning.</summary>
        public static TRequired Of<TRequired>() => default(TRequired);

        /// <summary>Specifies both service and required service types.</summary>
        public static TService Of<TService, TRequired>() => default(TService);

        /// <summary>Specifies required service type of parameter or member. Plus specifies if-unresolved policy.</summary>
        public static TRequired Of<TRequired>(IfUnresolved ifUnresolved) => default(TRequired);

        /// <summary>Specifies both service and required service types.</summary>
        public static TService Of<TService, TRequired>(IfUnresolved ifUnresolved) => default(TService);

        /// <summary>Specifies required service type of parameter or member. Plus specifies service key.</summary>
        public static TRequired Of<TRequired>(object serviceKey) => default(TRequired);

        /// <summary>Specifies both service and required service types.</summary>
        public static TService Of<TService, TRequired>(object serviceKey) => default(TService);

        /// <summary>Specifies required service type of parameter or member. Plus specifies service key.</summary>
        public static TRequired Of<TRequired>(string metadataKey, object metadata) => default(TRequired);

        /// <summary>Specifies both service and required service types.</summary>
        public static TService Of<TService, TRequired>(string metadataKey, object metadata) => default(TService);

        /// <summary>Specifies required service type of parameter or member. Plus specifies if-unresolved policy. Plus specifies service key.</summary>
        public static TRequired Of<TRequired>(IfUnresolved ifUnresolved, object serviceKey) => default(TRequired);

        /// <summary>Specifies both service and required service types.</summary>
        public static TService Of<TService, TRequired>(IfUnresolved ifUnresolved, object serviceKey) => default(TService);

        /// <summary>Specifies required service type, default value and <see cref="IfUnresolved.ReturnDefault"/>.</summary>
        public static TRequired Of<TRequired>(TRequired defaultValue, IfUnresolved ifUnresolved) => default(TRequired);

        /// <summary>Specifies required service type, default value and <see cref="IfUnresolved.ReturnDefault"/>.</summary>
        public static TRequired Of<TRequired>(TRequired defaultValue, IfUnresolved ifUnresolved, object serviceKey) => default(TRequired);

        /// <summary>Specifies argument index starting from 0 to use corresponding custom value factory,
        /// similar to String.Format <c>"{0}, {1}, etc"</c>.</summary>
        public static T Index<T>(int argIndex) => default(T);

        /// <summary>Name is close to method itself to not forget when renaming the method.</summary>
        public static string ArgIndexMethodName = "Index";
    }

    /// <summary>Contains <see cref="IRegistrator"/> extension methods to simplify general use cases.</summary>
    public static class Registrator
    {
        /// <summary>The base method for registering servce with its implementation factory. Allows to specify all possible options.</summary>
        public static void Register(this IRegistrator registrator, Type serviceType, Factory factory,
            IfAlreadyRegistered? ifAlreadyRegistered = null, object serviceKey = null) =>
            registrator.Register(factory, serviceType, serviceKey, ifAlreadyRegistered, false);

        /// <summary>Registers service <paramref name="serviceType"/> with corresponding <paramref name="implementationType"/>.</summary>
        public static void Register(this IRegistrator registrator, Type serviceType, Type implementationType,
            IReuse reuse = null, Made made = null, Setup setup = null, IfAlreadyRegistered? ifAlreadyRegistered = null,
            object serviceKey = null) =>
            registrator.Register(new ReflectionFactory(implementationType, reuse, made, setup),
                serviceType, serviceKey, ifAlreadyRegistered, false);

        /// <summary>Registers service of <paramref name="serviceAndMayBeImplementationType"/>.
        /// ServiceType may be the same as <paramref name="serviceAndMayBeImplementationType"/>.</summary>
        public static void Register(this IRegistrator registrator, Type serviceAndMayBeImplementationType,
            IReuse reuse = null, Made made = null, Setup setup = null, IfAlreadyRegistered? ifAlreadyRegistered = null,
            object serviceKey = null) =>
            registrator.Register(new ReflectionFactory(serviceAndMayBeImplementationType, reuse, made, setup),
                serviceAndMayBeImplementationType, serviceKey, ifAlreadyRegistered, false);

        /// <summary>Registers service of <typeparamref name="TService"/> type
        /// implemented by <typeparamref name="TImplementation"/> type.</summary>
        public static void Register<TService, TImplementation>(this IRegistrator registrator,
            IReuse reuse = null, Made made = null, Setup setup = null, IfAlreadyRegistered? ifAlreadyRegistered = null,
            object serviceKey = null)
            where TImplementation : TService =>
            registrator.Register(new ReflectionFactory(typeof(TImplementation), reuse, made, setup),
                typeof(TService), serviceKey, ifAlreadyRegistered, isStaticallyChecked: true);

        /// <summary>Registers implementation type <typeparamref name="TImplementation"/> with itself as service type.</summary>
        public static void Register<TImplementation>(this IRegistrator registrator,
            IReuse reuse = null, Made made = null, Setup setup = null, IfAlreadyRegistered? ifAlreadyRegistered = null,
            object serviceKey = null) =>
            registrator.Register<TImplementation, TImplementation>(reuse, made, setup, ifAlreadyRegistered, serviceKey);

        /// <summary>Registers service type returned by Made expression.</summary>
        public static void Register<TService, TMadeResult>(this IRegistrator registrator,
            Made.TypedMade<TMadeResult> made, IReuse reuse = null, Setup setup = null,
            IfAlreadyRegistered? ifAlreadyRegistered = null, object serviceKey = null) where TMadeResult : TService =>
            registrator.Register(new ReflectionFactory(default(Type), reuse, made, setup),
                typeof(TService), serviceKey, ifAlreadyRegistered, isStaticallyChecked: true);

        /// <summary>Registers service returned by Made expression.</summary>
        public static void Register<TService>(this IRegistrator registrator,
            Made.TypedMade<TService> made, IReuse reuse = null, Setup setup = null,
            IfAlreadyRegistered? ifAlreadyRegistered = null, object serviceKey = null) =>
            registrator.Register<TService, TService>(made, reuse, setup, ifAlreadyRegistered, serviceKey);

        /// <summary>
        /// Registers the instance creating a "normal" DryIoc registration so you can check it via `IsRegestered`, 
        /// apply wrappers and decorators, etc.
        /// Additionally, if instance is `IDisposable`, then it tracks it in a singleton scope.
        /// NOTE: Look at the `Use` method to put instance directly into current or singleton scope,
        /// though without ability to use decorators and wrappers on it.
        /// </summary>
        public static void RegisterInstance(this IRegistrator registrator, bool isChecked, Type serviceType, object instance, 
            IfAlreadyRegistered? ifAlreadyRegistered = null, Setup setup = null, object serviceKey = null)
        {
            registrator.Register(new RegisteredInstanceFactory(instance, DryIoc.Reuse.Singleton, setup),
                serviceType, serviceKey, ifAlreadyRegistered, isStaticallyChecked: false);

            // done after registration to pass all the registration validation checks
            if (instance is IDisposable && (setup == null || (!setup.PreventDisposal && !setup.WeaklyReferenced)))
                (registrator as IResolverContext)?.SingletonScope.TrackDisposable(instance);
        }

         /// <summary>
        /// Registers the instance creating a "normal" DryIoc registration so you can check it via `IsRegestered`, 
        /// apply wrappers and decorators, etc.
        /// Additionally, if instance is `IDisposable`, then it tracks it in a singleton scope.
        /// NOTE: Look at the `Use` method to put instance directly into current or singleton scope,
        /// though without ability to use decorators and wrappers on it.
        /// </summary>
        public static void RegisterInstance(this IRegistrator registrator, Type serviceType, object instance, 
            IfAlreadyRegistered? ifAlreadyRegistered = null, Setup setup = null, object serviceKey = null) =>
            registrator.RegisterInstance(false, serviceType, instance, ifAlreadyRegistered, setup, serviceKey);

        /// <summary>
        /// Registers the instance creating a "normal" DryIoc registration so you can check it via `IsRegestered`, 
        /// apply wrappers and decorators, etc.
        /// Additionally, if instance is `IDisposable`, then it tracks it in a singleton scope.
        /// NOTE: Look at the `Use` method to put instance directly into current or singleton scope,
        /// though without ability to use decorators and wrappers on it.
        /// </summary>
        public static void RegisterInstance<T>(this IRegistrator registrator, T instance,
            IfAlreadyRegistered? ifAlreadyRegistered = null, Setup setup = null, object serviceKey = null) =>
            registrator.RegisterInstance(true, typeof(T), instance, ifAlreadyRegistered, setup, serviceKey);

        /// <summary>
        /// Registers the instance with possible multiple service types creating a "normal" DryIoc registration 
        /// so you can check it via `IsRegestered` for each service type, 
        /// apply wrappers and decorators, etc.
        /// Additionally, if instance is `IDisposable`, then it tracks it in a singleton scope.
        /// NOTE: Look at the `Use` method to put instance directly into current or singleton scope,
        /// though without ability to use decorators and wrappers on it.
        /// </summary>
        public static void RegisterInstanceMany(this IRegistrator registrator, Type implType, object instance,
            bool nonPublicServiceTypes = false,
            IfAlreadyRegistered? ifAlreadyRegistered = null, Setup setup = null, object serviceKey = null)
        {
            instance.ThrowIfNull();
            if (implType != null)
                instance.ThrowIfNotInstanceOf(implType);
            else
                implType = instance.GetType();

            var serviceTypes = implType.GetImplementedServiceTypes(nonPublicServiceTypes);

            if (serviceTypes.Length == 0)
                Throw.It(Error.NoServicesWereRegisteredByRegisterMany, implType.One());
            
            var factory = new RegisteredInstanceFactory(instance, DryIoc.Reuse.Singleton, setup);
            foreach (var serviceType in serviceTypes)
                registrator.Register(factory, serviceType, serviceKey, ifAlreadyRegistered, isStaticallyChecked: true);

            if (instance is IDisposable && (setup == null || (!setup.PreventDisposal && !setup.WeaklyReferenced)))
                (registrator as IResolverContext)?.SingletonScope.TrackDisposable(instance);
        }

        /// <summary>
        /// Registers the instance with possible multiple service types creating a "normal" DryIoc registration 
        /// so you can check it via `IsRegestered` for each service type, 
        /// apply wrappers and decorators, etc.
        /// Additionally, if instance is `IDisposable`, then it tracks it in a singleton scope.
        /// NOTE: Look at the `Use` method to put instance directly into current or singleton scope,
        /// though without ability to use decorators and wrappers on it.
        /// </summary>
        public static void RegisterInstanceMany<T>(this IRegistrator registrator, T instance,
            bool nonPublicServiceTypes = false,
            IfAlreadyRegistered? ifAlreadyRegistered = null, Setup setup = null, object serviceKey = null) =>
            registrator.RegisterInstanceMany(instance.GetType(), instance,
                nonPublicServiceTypes, ifAlreadyRegistered, setup, serviceKey);

        /// <summary>
        /// Registers the instance with possible multiple service types creating a "normal" DryIoc registration 
        /// so you can check it via `IsRegestered` for each service type, 
        /// apply wrappers and decorators, etc.
        /// Additionally, if instance is `IDisposable`, then it tracks it in a singleton scope.
        /// NOTE: Look at the `Use` method to put instance directly into current or singleton scope,
        /// though without ability to use decorators and wrappers on it.
        /// </summary>
        public static void RegisterInstanceMany(this IRegistrator registrator, Type[] serviceTypes, object instance,
            IfAlreadyRegistered? ifAlreadyRegistered = null, Setup setup = null, object serviceKey = null)
        {
            var instanceType = instance.GetType();
            if (serviceTypes.IsNullOrEmpty())
                Throw.It(Error.NoServicesWereRegisteredByRegisterMany, instance);

            var factory = new RegisteredInstanceFactory(instance, DryIoc.Reuse.Singleton, setup);

            foreach (var serviceType in serviceTypes)
            {
                serviceType.ThrowIfNotImplementedBy(instanceType);
                registrator.Register(factory, serviceType, serviceKey, ifAlreadyRegistered, isStaticallyChecked: true);
            }

            if (instance is IDisposable && (setup == null || (!setup.PreventDisposal && !setup.WeaklyReferenced)))
                (registrator as IResolverContext)?.SingletonScope.TrackDisposable(instance);
        }

        /// <summary>List of types excluded by default from RegisterMany convention.</summary>
        public static readonly string[] ExcludedGeneralPurposeServiceTypes =
        {
            "System.Object",
            "System.IDisposable",
            "System.ValueType",
            "System.ICloneable",
            "System.IEquatable",
            "System.IComparable",
            "System.Runtime.Serialization.ISerializable",
            "System.Collections.IStructuralEquatable",
            "System.Collections.IEnumerable",
            "System.Collections.IList",
            "System.Collections.ICollection",
        };

        /// <summary>Checks that type is not in the list of <see cref="ExcludedGeneralPurposeServiceTypes"/>.</summary>
        public static bool IsExcludedGeneralPurposeServiceType(this Type type) =>
            ExcludedGeneralPurposeServiceTypes.IndexOf((type.Namespace + "." + type.Name).Split('`')[0]) != -1;

        /// <summary>Checks that type can be used a service type.</summary>
        public static bool IsServiceType(this Type type) =>
            !type.IsPrimitive() && !type.IsCompilerGenerated() && !type.IsExcludedGeneralPurposeServiceType();

        /// <summary>Checks if type can be used as implementation type for reflection factory,
        /// and therefore registered to container. Usually used to discover implementation types from assembly.</summary>
        public static bool IsImplementationType(this Type type) =>
            type.IsClass() && !type.IsAbstract() && !type.IsCompilerGenerated();

        /// <summary>Returns only those types that could be used as service types of <paramref name="type"/>.
        /// It means that for open-generic <paramref name="type"/> its service type should supply all type arguments.</summary>
        public static Type[] GetImplementedServiceTypes(this Type type, bool nonPublicServiceTypes = false)
        {
            var implementedTypes = type.GetImplementedTypes(ReflectionTools.AsImplementedType.SourceType);

            var serviceTypes = nonPublicServiceTypes
                ? implementedTypes.Match(t => t.IsServiceType())
                : implementedTypes.Match(t => t.IsPublicOrNestedPublic() && t.IsServiceType());
            
            if (type.IsGenericDefinition())
                serviceTypes = serviceTypes.Match(type.GetGenericParamsAndArgs(),
                    (paramsAndArgs, x) => x.ContainsAllGenericTypeParameters(paramsAndArgs),
                    (_, x) => x.GetGenericDefinitionOrNull());

            return serviceTypes;
        }

        /// <summary>The same `GetImplementedServiceTypes` but instead of collecting the service types just check the <paramref name="serviceType"/> is implemented</summary>
        public static bool IsImplementingServiceType(this Type type, Type serviceType)
        {
            if (serviceType == type || serviceType == typeof(object))
                return true;

            var implTypeInfo = type.GetTypeInfo();
            if (!implTypeInfo.IsGenericTypeDefinition)
            {
                if (serviceType.IsInterface())
                {
                    foreach (var iface in implTypeInfo.ImplementedInterfaces)
                        if (iface == serviceType)
                            return true;
                }
                else
                {
                    var baseType = implTypeInfo.BaseType;
                    for (; baseType != null && baseType != typeof(object); baseType = baseType.GetTypeInfo().BaseType)
                        if (serviceType == baseType)
                            return true;
                }
            }
            else if (serviceType.IsGenericDefinition())
            {
                var implTypeParams = implTypeInfo.GenericTypeParameters;
                if (serviceType.IsInterface())
                {
                    foreach (var iface in implTypeInfo.ImplementedInterfaces)
                        if (iface.GetGenericDefinitionOrNull() == serviceType &&
                            iface.ContainsAllGenericTypeParameters(implTypeParams))
                            return true;
                }
                else
                {
                    var baseType = implTypeInfo.BaseType;
                    for (; baseType != null && baseType != typeof(object); baseType = baseType.GetTypeInfo().BaseType)
                        if (baseType.GetGenericDefinitionOrNull() == serviceType &&
                            baseType.ContainsAllGenericTypeParameters(implTypeParams))
                            return true;
                }
            }

            return false;
        }

        /// <summary>Returns the sensible services automatically discovered for RegisterMany implementation type.
        /// Excludes the collection wrapper interfaces. The <paramref name="type"/> may be concrete, abstract or
        /// generic definition.</summary>
        public static Type[] GetRegisterManyImplementedServiceTypes(this Type type, bool nonPublicServiceTypes = false) =>
            GetImplementedServiceTypes(type, nonPublicServiceTypes)
                .Match(t => !t.IsGenericDefinition() || WrappersSupport.SupportedCollectionTypes.IndexOfReference(t) == -1);

        /// <summary>Returns the types suitable to be an implementation types for <see cref="ReflectionFactory"/>:
        /// actually a non abstract and not compiler generated classes.</summary>
        public static IEnumerable<Type> GetImplementationTypes(this Assembly assembly) =>
            Portable.GetAssemblyTypes(assembly).Where(IsImplementationType);

        /// <summary>Returns the types suitable to be an implementation types for <see cref="ReflectionFactory"/>:
        /// actually a non abstract and not compiler generated classes.</summary>
        public static IEnumerable<Type> GetImplementationTypes(this Assembly assembly, Func<Type, bool> condition) =>
            Portable.GetAssemblyTypes(assembly).Where(t => condition(t) && t.IsImplementationType());

        /// <summary>Sugar, so you can say <code lang="cs"><![CDATA[r.RegisterMany<X>(Registrator.Interfaces)]]></code></summary>
        public static Func<Type, bool> Interfaces = ReflectionTools.IsInterface;

        /// <summary>Checks if <paramref name="type"/> implements a service type,
        /// along the checking if <paramref name="type"/> is a valid implementation type.</summary>
        public static bool ImplementsServiceType(this Type type, Type serviceType) =>
            type.IsImplementationType() && type.IsImplementingServiceType(serviceType);

        /// <summary>Checks if <paramref name="type"/> implements a service type,
        /// along the checking if <paramref name="type"/> and service type
        /// are valid implementation and service types.</summary>
        public static bool ImplementsServiceType<TService>(this Type type) =>
            type.ImplementsServiceType(typeof(TService));

        /// <summary>Wraps the implementation type in factory.</summary>
        public static Factory ToFactory(this Type implType) => new ReflectionFactory(implType);

        /// <summary>Wraps the implementation type in factory plus allow to provide factory parameters.</summary>
        public static Factory ToFactory(this Type implType, IReuse reuse, Made made = null, Setup setup = null) =>
            new ReflectionFactory(implType, reuse, made, setup);

        /// <summary>A primary (basic) method for batch registering of implementations with possibly many service types.
        /// The default factory is the <see cref="ReflectionFactory"/> with default reuse.</summary>
        public static void RegisterMany(this IRegistrator registrator,
            IEnumerable<Type> implTypes, Func<Type, Type[]> getServiceTypes,
            Func<Type, Factory> getImplFactory = null, Func<Type, Type, object> getServiceKey = null,
            IfAlreadyRegistered? ifAlreadyRegistered = null)
        {
            getImplFactory = getImplFactory ?? ToFactory;

            bool isSomeoneRegistered = false;
            bool anyImplTypes = false;
            foreach (var implType in implTypes)
            {
                anyImplTypes = true;
                var serviceTypes = getServiceTypes(implType);
                if (!serviceTypes.IsNullOrEmpty())
                {
                    var factory = getImplFactory(implType);
                    for (var i = 0; i < serviceTypes.Length; i++)
                    {
                        var t = serviceTypes[i];
                        registrator.Register(t, factory, ifAlreadyRegistered, getServiceKey?.Invoke(implType, t));
                        isSomeoneRegistered = true;
                    }
                }
            }

            if (anyImplTypes && !isSomeoneRegistered)
                Throw.It(Error.NoServicesWereRegisteredByRegisterMany, implTypes);
        }

        /// <summary>Batch registers implementation with possibly many service types.</summary>
        public static void RegisterMany(this IRegistrator registrator,
            Type[] serviceTypes, Type implType,
            IReuse reuse = null, Made made = null, Setup setup = null,
            IfAlreadyRegistered? ifAlreadyRegistered = null, object serviceKey = null) =>
            registrator.RegisterMany(new[] { implType }, serviceTypes.ToFunc<Type, Type[]>,
                t => t.ToFactory(reuse, made, setup), (_, __) => serviceKey, ifAlreadyRegistered);

        /// <summary>Batch registers assemblies of implementation types with possibly many service types.
        /// The default factory is the <see cref="ReflectionFactory"/> with default reuse.</summary>
        public static void RegisterMany(this IRegistrator registrator,
            IEnumerable<Assembly> implTypeAssemblies, Func<Type, Type[]> getServiceTypes,
            Func<Type, Factory> getImplFactory = null, Func<Type, Type, object> getServiceKey = null,
            IfAlreadyRegistered? ifAlreadyRegistered = null) =>
            registrator.RegisterMany(implTypeAssemblies.ThrowIfNull().SelectMany(GetImplementationTypes),
                getServiceTypes, getImplFactory, getServiceKey, ifAlreadyRegistered);

        /// <summary>Registers many implementations with their auto-figured service types.</summary>
        public static void RegisterMany(this IRegistrator registrator,
            IEnumerable<Assembly> implTypeAssemblies, Func<Type, bool> serviceTypeCondition,
            IReuse reuse = null, Made made = null, Setup setup = null, IfAlreadyRegistered? ifAlreadyRegistered = null,
            bool nonPublicServiceTypes = false, object serviceKey = null) =>
            registrator.RegisterMany(implTypeAssemblies.ThrowIfNull().SelectMany(GetImplementationTypes),
                reuse, made, setup, ifAlreadyRegistered, serviceTypeCondition, nonPublicServiceTypes, serviceKey);

        /// <summary>Registers many implementations with auto-figured service types.</summary>
        public static void RegisterMany(this IRegistrator registrator, IEnumerable<Type> implTypes,
            IReuse reuse = null, Made made = null, Setup setup = null, IfAlreadyRegistered? ifAlreadyRegistered = null,
            Func<Type, bool> serviceTypeCondition = null, bool nonPublicServiceTypes = false,
            object serviceKey = null) =>
            registrator.RegisterMany(implTypes,
                t => t.GetRegisterManyImplementedServiceTypes(nonPublicServiceTypes).Match(serviceTypeCondition ?? Fun.Always),
                reuse == null && made == null && setup == null ? default(Func<Type, Factory>) : t => t.ToFactory(reuse, made, setup),
                serviceKey == null ? default(Func<Type, Type, object>) : (i, s) => serviceKey,
                ifAlreadyRegistered);

        /// <summary>Registers single registration for all implemented public interfaces and base classes.</summary>
        public static void RegisterMany<TImplementation>(this IRegistrator registrator,
            IReuse reuse = null, Made made = null, Setup setup = null, IfAlreadyRegistered? ifAlreadyRegistered = null,
            Func<Type, bool> serviceTypeCondition = null, bool nonPublicServiceTypes = false,
            object serviceKey = null) =>
            registrator.RegisterMany(typeof(TImplementation).One(),
                reuse, made, setup, ifAlreadyRegistered, serviceTypeCondition, nonPublicServiceTypes, serviceKey);

        /// <summary>Registers single registration for all implemented public interfaces and base classes.</summary>
        public static void RegisterMany<TMadeResult>(this IRegistrator registrator,
            Made.TypedMade<TMadeResult> made,
            IReuse reuse = null, Setup setup = null, IfAlreadyRegistered? ifAlreadyRegistered = null,
            Func<Type, bool> serviceTypeCondition = null, bool nonPublicServiceTypes = false,
            object serviceKey = null) =>
            registrator.RegisterMany<TMadeResult>(reuse, made.ThrowIfNull(), setup,
                ifAlreadyRegistered, serviceTypeCondition, nonPublicServiceTypes, serviceKey);

        /// <summary>Registers a factory delegate for creating an instance of <typeparamref name="TService"/>.
        /// Delegate can use resolver context parameter to resolve any required dependencies, e.g.:
        /// <code lang="cs"><![CDATA[container.RegisterDelegate<ICar>(r => new Car(r.Resolve<IEngine>()))]]></code></summary>
        /// <remarks>The alternative to this method please consider using <see cref="Made"/> instead:
        /// <code lang="cs"><![CDATA[container.Register<ICar>(Made.Of(() => new Car(Arg.Of<IEngine>())))]]></code>.
        /// </remarks>
        public static void RegisterDelegate<TService>(this IRegistrator registrator,
            Func<IResolverContext, TService> factoryDelegate,
            IReuse reuse = null, Setup setup = null, IfAlreadyRegistered? ifAlreadyRegistered = null,
            object serviceKey = null) =>
            registrator.Register(new DelegateFactory(factoryDelegate.ToFactoryDelegate, reuse, setup),
                typeof(TService), serviceKey, ifAlreadyRegistered, isStaticallyChecked: true);

        /// <summary>Registers delegate to be injected by container avoiding the ServiceLocator anti-pattern</summary>
        public static void RegisterDelegate<TService>(
            this IRegistrator r, Func<TService> factory,
            IReuse reuse = null, Setup setup = null, IfAlreadyRegistered? ifAlreadyRegistered = null, object serviceKey = null) =>
            RegisterDelegateFunc(r, typeof(TService), factory, reuse, setup, ifAlreadyRegistered, serviceKey);

        /// <summary>Registers delegate with explicit arguments to be injected by container avoiding the ServiceLocator anti-pattern</summary>
        public static void RegisterDelegate<TDep1, TService>(
            this IRegistrator r, Func<TDep1, TService> factory,
            IReuse reuse = null, Setup setup = null, IfAlreadyRegistered? ifAlreadyRegistered = null, object serviceKey = null) =>
            RegisterDelegateFunc(r, typeof(TService), factory, reuse, setup, ifAlreadyRegistered, serviceKey);

        /// <summary>Registers delegate with explicit arguments to be injected by container avoiding the ServiceLocator anti-pattern</summary>
        public static void RegisterDelegate<TDep1>(
            this IRegistrator r, Type serviceType, Func<TDep1, object> factory,
            IReuse reuse = null, Setup setup = null, IfAlreadyRegistered? ifAlreadyRegistered = null, object serviceKey = null) =>
            RegisterDelegateFunc<Func<TDep1, object>>(r, serviceType,
                dep1 => factory(dep1).ThrowIfNotInstanceOf(serviceType, Error.RegisteredDelegateResultIsNotOfServiceType),
                reuse, setup, ifAlreadyRegistered, serviceKey);

        /// <summary>Registers delegate with explicit arguments to be injected by container avoiding the ServiceLocator anti-pattern</summary>
        public static void RegisterDelegate<TDep1, TDep2, TService>(
            this IRegistrator r, Func<TDep1, TDep2, TService> factory,
            IReuse reuse = null, Setup setup = null, IfAlreadyRegistered? ifAlreadyRegistered = null, object serviceKey = null) =>
            RegisterDelegateFunc(r, typeof(TService), factory, reuse, setup, ifAlreadyRegistered, serviceKey);

        /// <summary>Registers delegate with explicit arguments to be injected by container avoiding the ServiceLocator anti-pattern</summary>
        public static void RegisterDelegate<TDep1, TDep2, TDep3, TService>(
            this IRegistrator r, Func<TDep1, TDep2, TDep3, TService> factory,
            IReuse reuse = null, Setup setup = null, IfAlreadyRegistered? ifAlreadyRegistered = null, object serviceKey = null) =>
            RegisterDelegateFunc(r, typeof(TService), factory, reuse, setup, ifAlreadyRegistered, serviceKey);

        /// <summary>Registers delegate with explicit arguments to be injected by container avoiding the ServiceLocator anti-pattern</summary>
        public static void RegisterDelegate<TDep1, TDep2, TDep3, TDep4, TService>(
            this IRegistrator r, Func<TDep1, TDep2, TDep3, TDep4, TService> factory,
            IReuse reuse = null, Setup setup = null, IfAlreadyRegistered? ifAlreadyRegistered = null, object serviceKey = null) =>
            RegisterDelegateFunc(r, typeof(TService), factory, reuse, setup, ifAlreadyRegistered, serviceKey);

        /// <summary>Registers delegate with explicit arguments to be injected by container avoiding the ServiceLocator anti-pattern</summary>
        public static void RegisterDelegate<TDep1, TDep2, TDep3, TDep4, TDep5, TService>(
            this IRegistrator r, Func<TDep1, TDep2, TDep3, TDep4, TDep5, TService> factory,
            IReuse reuse = null, Setup setup = null, IfAlreadyRegistered? ifAlreadyRegistered = null, object serviceKey = null) =>
            RegisterDelegateFunc(r, typeof(TService), factory, reuse, setup, ifAlreadyRegistered, serviceKey);

        /// <summary>Registers delegate with explicit arguments to be injected by container avoiding the ServiceLocator anti-pattern</summary>
        public static void RegisterDelegate<TDep1, TDep2, TDep3, TDep4, TDep5, TDep6, TService>(
            this IRegistrator r, Func<TDep1, TDep2, TDep3, TDep4, TDep5, TDep6, TService> factory,
            IReuse reuse = null, Setup setup = null, IfAlreadyRegistered? ifAlreadyRegistered = null, object serviceKey = null) =>
            RegisterDelegateFunc(r, typeof(TService), factory, reuse, setup, ifAlreadyRegistered, serviceKey);

        /// <summary>Registers delegate with explicit arguments to be injected by container avoiding the ServiceLocator anti-pattern</summary>
        public static void RegisterDelegate<TDep1, TDep2, TDep3, TDep4, TDep5, TDep6, TDep7, TService>(
            this IRegistrator r, Func<TDep1, TDep2, TDep3, TDep4, TDep5, TDep6, TDep7, TService> factory,
            IReuse reuse = null, Setup setup = null, IfAlreadyRegistered? ifAlreadyRegistered = null, object serviceKey = null) =>
            RegisterDelegateFunc(r, typeof(TService), factory, reuse, setup, ifAlreadyRegistered, serviceKey);

        private const string InvokeMethodName = "Invoke";
        private static void RegisterDelegateFunc<TFunc>(IRegistrator r, Type serviceType,
            TFunc factory, IReuse reuse, Setup setup, IfAlreadyRegistered? ifAlreadyRegistered, object serviceKey)
        {
            var invokeMethod = typeof(TFunc).GetTypeInfo().GetDeclaredMethod(InvokeMethodName);
            var made = new Made(new FactoryMethod(invokeMethod, Constant(factory)), serviceType);
            r.Register(new ReflectionFactory(serviceType, reuse, made, setup),
                serviceType, serviceKey, ifAlreadyRegistered, isStaticallyChecked: true);
        }

        /// Minimizes the number of allocations when converting from Func to named delegate
        public static object ToFactoryDelegate<TService>(this Func<IResolverContext, TService> f, IResolverContext r) => f(r);

        /// Lifts the result to the factory delegate without allocations on capturing value in lambda closure
        public static object ToFactoryDelegate(this object result, IResolverContext _) => result;

        /// <summary>Registers a factory delegate for creating an instance of <paramref name="serviceType"/>.
        /// Delegate can use resolver context parameter to resolve any required dependencies, e.g.:
        /// <code lang="cs"><![CDATA[container.RegisterDelegate<ICar>(r => new Car(r.Resolve<IEngine>()))]]></code></summary>
        /// <remarks>IMPORTANT: The method should be used as the last resort only! Though powerful it is a black-box for container,
        /// which prevents diagnostics, plus it is easy to get memory leaks (due variables captured in delegate closure),
        /// and impossible to use in compile-time scenarios.
        /// Consider using <see cref="Made"/> instead:
        /// <code lang="cs"><![CDATA[container.Register<ICar>(Made.Of(() => new Car(Arg.Of<IEngine>())))]]></code>
        /// </remarks>
        public static void RegisterDelegate(this IRegistrator registrator,
            Type serviceType, Func<IResolverContext, object> factoryDelegate,
            IReuse reuse = null, Setup setup = null, IfAlreadyRegistered? ifAlreadyRegistered = null,
            object serviceKey = null)
        {
            if (serviceType.IsOpenGeneric())
                Throw.It(Error.RegisteringOpenGenericRequiresFactoryProvider, serviceType);

            FactoryDelegate checkedDelegate = r => factoryDelegate(r)
                .ThrowIfNotInstanceOf(serviceType, Error.RegisteredDelegateResultIsNotOfServiceType);

            var factory = new DelegateFactory(checkedDelegate, reuse, setup);

            registrator.Register(factory, serviceType, serviceKey, ifAlreadyRegistered, isStaticallyChecked: false);
        }

        /// A special performant version mostly for integration with other libraries,
        /// that already check compatiblity between delegate result and the service type
        public static void RegisterDelegate(this IRegistrator registrator,
            bool isChecked, Type serviceType, Func<IResolverContext, object> factoryDelegate,
            IReuse reuse = null, Setup setup = null, IfAlreadyRegistered? ifAlreadyRegistered = null,
            object serviceKey = null) =>
            registrator.Register(new DelegateFactory(factoryDelegate.ToFactoryDelegate, reuse, setup), 
                serviceType, serviceKey, ifAlreadyRegistered, isStaticallyChecked: true);

        /// <summary>Registers decorator function that gets decorated value as input and returns decorator.
        /// Note: Delegate decorator will use <see cref="Reuse"/> of decoratee service.</summary>
        public static void RegisterDelegateDecorator<TService>(this IRegistrator registrator,
            Func<IResolverContext, Func<TService, TService>> getDecorator, Func<Request, bool> condition = null)
        {
            getDecorator.ThrowIfNull();

            // unique key to bind decorator factory and decorator registrations
            var factoryKey = new object();

            registrator.RegisterDelegate(
                _ => new DecoratorDelegateFactory<TService>(getDecorator),
                serviceKey: factoryKey);

            registrator.Register(Made.Of(
                _ => ServiceInfo.Of<DecoratorDelegateFactory<TService>>(serviceKey: factoryKey),
                f => f.Decorate(Arg.Of<TService>(), Arg.Of<IResolverContext>())),
                setup: Setup.DecoratorWith(condition, useDecorateeReuse: true));
        }

        internal sealed class DecoratorDelegateFactory<TDecoratee>
        {
            private readonly Func<IResolverContext, Func<TDecoratee, TDecoratee>> _getDecorator;
            public DecoratorDelegateFactory(Func<IResolverContext, Func<TDecoratee, TDecoratee>> getDecorator) { _getDecorator = getDecorator; }
            public TDecoratee Decorate(TDecoratee decoratee, IResolverContext r) => _getDecorator(r).Invoke(decoratee);
        }

        /// Will become OBSOLETE! in the next major version:
        /// Please use `RegisterInstance` or `Use` method instead.
        public static void UseInstance<TService>(this IResolverContext r, TService instance,
            bool preventDisposal = false, bool weaklyReferenced = false, object serviceKey = null) =>
            r.UseInstance(typeof(TService), instance, IfAlreadyRegistered.Replace, preventDisposal, weaklyReferenced, serviceKey);

        /// Will become OBSOLETE! in the next major version:
        /// Please use `RegisterInstance` or `Use` method instead.
        public static void UseInstance<TService>(this IRegistrator r, TService instance,
            bool preventDisposal = false, bool weaklyReferenced = false, object serviceKey = null) =>
            r.UseInstance(typeof(TService), instance, IfAlreadyRegistered.Replace, preventDisposal, weaklyReferenced, serviceKey);

        /// Will become OBSOLETE! in the next major version:
        /// Please use `RegisterInstance` or `Use` method instead.
        public static void UseInstance<TService>(this IContainer c, TService instance,
            bool preventDisposal = false, bool weaklyReferenced = false, object serviceKey = null) =>
            c.UseInstance(typeof(TService), instance, IfAlreadyRegistered.Replace, preventDisposal, weaklyReferenced, serviceKey);

        /// Will become OBSOLETE! in the next major version:
        /// Please use `RegisterInstance` or `Use` method instead.
        public static void UseInstance(this IResolverContext r, Type serviceType, object instance,
            bool preventDisposal = false, bool weaklyReferenced = false, object serviceKey = null) =>
            r.UseInstance(serviceType, instance, IfAlreadyRegistered.Replace, preventDisposal, weaklyReferenced, serviceKey);

        /// Will become OBSOLETE! in the next major version:
        /// Please use `RegisterInstance` or `Use` method instead.
        public static void UseInstance(this IRegistrator r, Type serviceType, object instance,
            bool preventDisposal = false, bool weaklyReferenced = false, object serviceKey = null) =>
            r.UseInstance(serviceType, instance, IfAlreadyRegistered.Replace, preventDisposal, weaklyReferenced, serviceKey);

        /// Will become OBSOLETE! in the next major version:
        /// Please use `RegisterInstance` or `Use` method instead.
        public static void UseInstance(this IContainer c, Type serviceType, object instance,
            bool preventDisposal = false, bool weaklyReferenced = false, object serviceKey = null) =>
            c.UseInstance(serviceType, instance, IfAlreadyRegistered.Replace, preventDisposal, weaklyReferenced, serviceKey);

        /// Will become OBSOLETE! in the next major version:
        /// Please use `RegisterInstance` or `Use` method instead.
        public static void UseInstance<TService>(this IResolverContext r, TService instance, IfAlreadyRegistered ifAlreadyRegistered,
            bool preventDisposal = false, bool weaklyReferenced = false, object serviceKey = null) =>
            r.UseInstance(typeof(TService), instance, ifAlreadyRegistered, preventDisposal, weaklyReferenced, serviceKey);

        /// Will become OBSOLETE! in the next major version:
        /// Please use `RegisterInstance` or `Use` method instead.
        public static void UseInstance(this IResolverContext r, Type serviceType, object instance, IfAlreadyRegistered ifAlreadyRegistered,
            bool preventDisposal = false, bool weaklyReferenced = false, object serviceKey = null) =>
            r.UseInstance(serviceType, instance, ifAlreadyRegistered, preventDisposal, weaklyReferenced, serviceKey);

        /// Will become OBSOLETE! in the next major version:
        /// Please use `RegisterInstance` or `Use` method instead.
        public static void UseInstance(this IRegistrator r, Type serviceType, object instance, IfAlreadyRegistered ifAlreadyRegistered,
            bool preventDisposal = false, bool weaklyReferenced = false, object serviceKey = null) =>
            r.UseInstance(serviceType, instance, ifAlreadyRegistered, preventDisposal, weaklyReferenced, serviceKey);

        /// <summary>
        /// Will become OBSOLETE in the next major version!
        /// Please use `RegisterInstance` or `Use` method instead.
        /// </summary>
        public static void UseInstance(this IContainer c, Type serviceType, object instance, IfAlreadyRegistered ifAlreadyRegistered,
            bool preventDisposal = false, bool weaklyReferenced = false, object serviceKey = null) =>
            c.UseInstance(serviceType, instance, ifAlreadyRegistered, preventDisposal, weaklyReferenced, serviceKey);

        /// <summary>Adding the factory directly to scope for resolution</summary> 
        public static void Use<TService>(this IResolverContext r, Func<IResolverContext, TService> factory) =>
            r.Use(typeof(TService), factory.ToFactoryDelegate);

        /// <summary>Adding the instance directly to the scope for resolution</summary>
        public static void Use(this IResolverContext r, Type serviceType, object instance) =>
            r.Use(serviceType, instance.ToFactoryDelegate);

        /// <summary>Adding the instance directly to the scope for resolution</summary> 
        public static void Use<TService>(this IResolverContext r, TService instance) =>
            r.Use(typeof(TService), instance.ToFactoryDelegate);

        /// <summary>Adding the factory directly to the scope for resolution</summary>
        public static void Use<TService>(this IRegistrator r, Func<IResolverContext, TService> factory) =>
            r.Use(typeof(TService), factory.ToFactoryDelegate);

        /// <summary>Adding the instance directly to scope for resolution</summary>
        public static void Use(this IRegistrator r, Type serviceType, object instance) =>
            r.Use(serviceType, instance.ToFactoryDelegate);

        /// <summary>Adding the instance directly to scope for resolution</summary> 
        public static void Use<TService>(this IRegistrator r, TService instance) =>
            r.Use(typeof(TService), instance.ToFactoryDelegate);

        /// <summary>Adding the factory directly to scope for resolution</summary> 
        public static void Use<TService>(this IContainer c, Func<IResolverContext, TService> factory) =>
            ((IResolverContext)c).Use(typeof(TService), factory.ToFactoryDelegate);

        /// <summary>Adding the instance directly to scope for resolution</summary>
        public static void Use(this IContainer c, Type serviceType, object instance) =>
            ((IResolverContext)c).Use(serviceType, instance.ToFactoryDelegate);

        /// <summary>Adding the instance directly to scope for resolution</summary>
        public static void Use<TService>(this IContainer c, TService instance) =>
            ((IResolverContext)c).Use(typeof(TService), instance.ToFactoryDelegate);

        /// <summary>
        /// Registers initializing action that will be called after service is resolved 
        /// just before returning it to the caller.  You can register multiple initializers for single service.
        /// Or you can register initializer for <see cref="Object"/> type to be applied 
        /// for all services and use <paramref name="condition"/> to specify the target services.
        /// </summary>
        public static void RegisterInitializer<TTarget>(this IRegistrator registrator,
            Action<TTarget, IResolverContext> initialize, Func<Request, bool> condition = null)
        {
            initialize.ThrowIfNull();
            registrator.Register<object>(
                made: Made.Of(r => _initializerMethod.MakeGenericMethod(typeof(TTarget), r.ServiceType),
                    // specify ResolverContext as parameter to prevent applying initializer for injected resolver too
                    parameters: Parameters.Of
                        .Type(r => r.IsSingletonOrDependencyOfSingleton && !r.OpensResolutionScope
                            ? r.Container.RootOrSelf() : r.Container)
                        .Type(initialize.ToFunc<Request, Action<TTarget, IResolverContext>>)),
                setup: Setup.DecoratorWith(
                    r => r.ServiceType.IsAssignableTo<TTarget>() && (condition == null || condition(r)),
                    useDecorateeReuse: true, // issue BitBucket #230 - ensures the initialization to happen once on construction 
                    preventDisposal: true)); // issue #215 - ensures that the initialized / decorated object does not added for the disposal twice
        }

        private static readonly MethodInfo _initializerMethod =
            typeof(Registrator).SingleMethod(nameof(Initializer), includeNonPublic: true);

        internal static TService Initializer<TTarget, TService>(
            TService service, IResolverContext resolver, Action<TTarget, IResolverContext> initialize) where TService : TTarget
        {
            initialize(service, resolver);
            return service;
        }

        /// <summary>Registers dispose action for reused target service.</summary>
        public static void RegisterDisposer<TService>(this IRegistrator registrator,
            Action<TService> dispose, Func<Request, bool> condition = null)
        {
            dispose.ThrowIfNull();

            var disposerKey = new object();

            registrator.RegisterDelegate(_ => new Disposer<TService>(dispose),
                serviceKey: disposerKey,
                // tracking instead of parent reuse, so that I can use one disposer for multiple services
                setup: Setup.With(trackDisposableTransient: true));

            var disposerType = typeof(Disposer<>).MakeGenericType(typeof(TService));
            registrator.Register<object>(
                made: Made.Of(
                    r => disposerType.SingleMethod("TrackForDispose").MakeGenericMethod(r.ServiceType),
                    ServiceInfo.Of(disposerType, serviceKey: disposerKey)),
                setup: Setup.DecoratorWith(
                    r => r.ServiceType.IsAssignableTo<TService>() && (condition == null || condition(r)),
                    useDecorateeReuse: true));
        }

        internal sealed class Disposer<T> : IDisposable
        {
            private readonly Action<T> _dispose;
            private int _state;
            private const int TRACKED = 1, DISPOSED = 2;
            private T _item;

            public Disposer(Action<T> dispose)
            {
                _dispose = dispose;
            }

            public S TrackForDispose<S>(S item) where S : T
            {
                if (Interlocked.CompareExchange(ref _state, TRACKED, 0) != 0)
                    Throw.It(Error.DisposerTrackForDisposeError, _state == TRACKED ? " tracked" : "disposed");
                _item = item;
                return item;
            }

            public void Dispose()
            {
                if (Interlocked.CompareExchange(ref _state, DISPOSED, TRACKED) != TRACKED)
                    return;
                var item = _item;
                if (item != null)
                {
                    _dispose(item);
                    _item = default(T);
                }
            }
        }

        /// <summary>Returns true if <paramref name="serviceType"/> is registered in container OR
        /// its open generic definition is registered in container.
        /// The additional implementation factory <paramref name="condition"/> may be specified to narrow the search.</summary>
        public static bool IsRegistered(this IRegistrator registrator, Type serviceType,
            object serviceKey = null, FactoryType factoryType = FactoryType.Service, Func<Factory, bool> condition = null) =>
            registrator.IsRegistered(serviceType, serviceKey, factoryType, condition);

        /// <summary>Returns true if <typeparamref name="TService"/> is registered in container OR
        /// its open generic definition is registered in container.
        /// The additional implementation factory <paramref name="condition"/> may be specified to narrow the search.</summary>
        public static bool IsRegistered<TService>(this IRegistrator registrator,
            object serviceKey = null, FactoryType factoryType = FactoryType.Service, Func<Factory, bool> condition = null) =>
            registrator.IsRegistered(typeof(TService), serviceKey, factoryType, condition);

        /// <summary>Removes specified registration from container.
        /// It also tries to remove the cached resolutions for the removed registration, But it may not work depending on context.
        /// Check the docs for more info: https://bitbucket.org/dadhi/dryioc/wiki/UnregisterAndResolutionCache </summary>
        public static void Unregister(this IRegistrator registrator, Type serviceType,
            object serviceKey = null, FactoryType factoryType = FactoryType.Service, Func<Factory, bool> condition = null) =>
            registrator.Unregister(serviceType, serviceKey, factoryType, condition);

        /// <summary>Removes specified registration from container.
        /// It also tries to remove the cached resolutions for the removed registration, But it may not work depending on context.
        /// Check the docs for more info: https://bitbucket.org/dadhi/dryioc/wiki/UnregisterAndResolutionCache </summary>
        public static void Unregister<TService>(this IRegistrator registrator,
            object serviceKey = null, FactoryType factoryType = FactoryType.Service, Func<Factory, bool> condition = null) =>
            registrator.Unregister(typeof(TService), serviceKey, factoryType, condition);

        /// <summary>Registers new service type with factory for registered service type.
        /// Throw if no such registered service type in container.</summary>
        /// <param name="registrator">Registrator</param> <param name="serviceType">New service type.</param>
        /// <param name="registeredServiceType">Existing registered service type.</param>
        /// <param name="serviceKey">(optional)</param> <param name="registeredServiceKey">(optional)</param>
        /// <param name="factoryType">(optional) By default is <see cref="FactoryType.Service"/></param>
        /// <remarks>Does nothing if registration is already exists.</remarks>
        public static void RegisterMapping(this IRegistrator registrator, Type serviceType, Type registeredServiceType,
            object serviceKey = null, object registeredServiceKey = null, FactoryType factoryType = FactoryType.Service)
        {
            var factories = registrator.GetRegisteredFactories(registeredServiceType, registeredServiceKey, factoryType);

            if (factories.IsNullOrEmpty())
                Throw.It(Error.RegisterMappingNotFoundRegisteredService, registeredServiceType, registeredServiceKey);

            if (factories.Length > 1)
                Throw.It(Error.RegisterMappingUnableToSelectFromMultipleFactories, serviceType, serviceKey, factories);

            registrator.Register(factories[0], serviceType, serviceKey, IfAlreadyRegistered.Keep, false);
        }

        /// <summary>Registers new service type with factory for registered service type.
        /// Throw if no such registered service type in container.</summary>
        /// <param name="registrator">Registrator</param>
        /// <typeparam name="TService">New service type.</typeparam>
        /// <typeparam name="TRegisteredService">Existing registered service type.</typeparam>
        /// <param name="serviceKey">(optional)</param> <param name="registeredServiceKey">(optional)</param>
        /// <param name="factoryType">(optional) By default is <see cref="FactoryType.Service"/></param>
        /// <remarks>Does nothing if registration is already exists.</remarks>
        public static void RegisterMapping<TService, TRegisteredService>(this IRegistrator registrator,
            object serviceKey = null, object registeredServiceKey = null, FactoryType factoryType = FactoryType.Service) =>
            registrator.RegisterMapping(typeof(TService), typeof(TRegisteredService), serviceKey, registeredServiceKey);

        /// <summary>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.</summary>
        /// <remarks>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.</remarks>
        public static void RegisterPlaceholder(this IRegistrator registrator, Type serviceType,
            IfAlreadyRegistered? ifAlreadyRegistered = null, object serviceKey = null) =>
            registrator.Register(FactoryPlaceholder.Default, serviceType, serviceKey, ifAlreadyRegistered, true);
    }

    /// <summary>Extension methods for <see cref="IResolver"/>.</summary>
    public static class Resolver
    {
        internal static readonly MethodInfo ResolveFastMethod =
            typeof(IResolver).Method(nameof(IResolver.Resolve), typeof(Type), typeof(IfUnresolved));

        internal static readonly MethodInfo ResolveMethod =
            typeof(IResolver).Method(nameof(IResolver.Resolve), typeof(Type), typeof(object),
                typeof(IfUnresolved), typeof(Type), typeof(Request), typeof(object[]));

        internal static readonly MethodInfo ResolveManyMethod =
            typeof(IResolver).GetTypeInfo().GetDeclaredMethod(nameof(IResolver.ResolveMany));

        /// <summary>Resolves instance of service type from container. Throws exception if unable to resolve.</summary>
        public static object Resolve(this IResolver resolver, Type serviceType) =>
            resolver.Resolve(serviceType, IfUnresolved.Throw);

        /// <summary>Resolves instance of service type from container.</summary>
        public static object Resolve(this IResolver resolver, Type serviceType, IfUnresolved ifUnresolved) =>
            resolver.Resolve(serviceType, ifUnresolved);

        /// <summary>Resolves instance of type TService from container.</summary>
        public static TService Resolve<TService>(this IResolver resolver,
            IfUnresolved ifUnresolved = IfUnresolved.Throw) =>
            (TService)resolver.Resolve(typeof(TService), ifUnresolved);

        /// <summary>Tries to resolve instance of service type from container.</summary>
        public static object Resolve(this IResolver resolver, Type serviceType, bool ifUnresolvedReturnDefault) =>
            resolver.Resolve(serviceType, ifUnresolvedReturnDefault ? IfUnresolved.ReturnDefault : IfUnresolved.Throw);

        /// <summary>Tries to resolve instance of TService from container.</summary>
        public static object Resolve<TService>(this IResolver resolver, bool ifUnresolvedReturnDefault) =>
            resolver.Resolve(typeof(TService), ifUnresolvedReturnDefault);

        /// <summary>Returns instance of <paramref name="serviceType"/> searching for <paramref name="requiredServiceType"/>.
        /// In case of <paramref name="serviceType"/> being generic wrapper like Func, Lazy, IEnumerable, etc. 
        /// <paramref name="requiredServiceType"/> allow you to specify wrapped service type.</summary>
        /// <example><code lang="cs"><![CDATA[
        ///     container.Register<IService, Service>();
        ///     var services = container.Resolve(typeof(IEnumerable<object>), typeof(IService));
        /// ]]></code></example>
        public static object Resolve(this IResolver resolver, Type serviceType, Type requiredServiceType,
            IfUnresolved ifUnresolved = IfUnresolved.Throw, object[] args = null, object serviceKey = null) =>
            resolver.Resolve(serviceType, serviceKey, ifUnresolved, requiredServiceType, Request.Empty, args);

        /// <summary>Returns instance of <typeparamref name="TService"/> searching for <paramref name="requiredServiceType"/>.
        /// In case of <typeparamref name="TService"/> being generic wrapper like Func, Lazy, IEnumerable, etc. 
        /// <paramref name="requiredServiceType"/> allow you to specify wrapped service type.</summary>
        /// <example><code lang="cs"><![CDATA[
        ///     container.Register<IService, Service>();
        ///     var services = container.Resolve<IEnumerable<object>>(typeof(IService));
        /// ]]></code></example>
        public static TService Resolve<TService>(this IResolver resolver, Type requiredServiceType,
            IfUnresolved ifUnresolved = IfUnresolved.Throw, object[] args = null, object serviceKey = null) =>
            (TService)resolver.Resolve(typeof(TService), serviceKey, ifUnresolved, requiredServiceType, Request.Empty, args);

        /// <summary>Returns instance of <typeparamref name="TService"/> searching for <typeparamref name="TRequiredService"/>.
        /// In case of <typeparamref name="TService"/> being generic wrapper like Func, Lazy, IEnumerable, etc. 
        /// <typeparamref name="TRequiredService"/> allow you to specify wrapped service type.</summary>
        /// <example><code lang="cs"><![CDATA[
        ///     container.Register<IService, Service>();
        ///     var services = container.Resolve<IEnumerable<object>, IService>();
        /// ]]></code></example>
        public static TService Resolve<TService, TRequiredService>(this IResolver resolver,
            IfUnresolved ifUnresolved = IfUnresolved.Throw, object[] args = null, object serviceKey = null) =>
            (TService)resolver.Resolve(typeof(TService), serviceKey, ifUnresolved, typeof(TRequiredService), Request.Empty, args);

        /// <summary>Returns instance of <paramref name="serviceType"/> searching for <paramref name="requiredServiceType"/>.
        /// In case of <paramref name="serviceType"/> being generic wrapper like Func, Lazy, IEnumerable, etc., <paramref name="requiredServiceType"/>
        /// could specify wrapped service type.</summary>
        /// <remarks>Using <paramref name="requiredServiceType"/> implicitly support Covariance for generic wrappers even in .Net 3.5.</remarks>
        /// <example><code lang="cs"><![CDATA[
        ///     container.Register<IService, Service>();
        ///     var services = container.Resolve(typeof(Lazy<object>), "someKey", requiredServiceType: typeof(IService));
        /// ]]></code></example>
        public static object Resolve(this IResolver resolver, Type serviceType, object serviceKey,
            IfUnresolved ifUnresolved = IfUnresolved.Throw, Type requiredServiceType = null,
            object[] args = null) =>
            resolver.Resolve(serviceType, serviceKey, ifUnresolved, requiredServiceType, Request.Empty, args);

        /// <summary>Returns instance of <typepsaramref name="TService"/> type.</summary>
        /// <typeparam name="TService">The type of the requested service.</typeparam>
        /// <returns>The requested service instance.</returns>
        /// <remarks>Using <paramref name="requiredServiceType"/> implicitly support Covariance for generic wrappers even in .Net 3.5.</remarks>
        public static TService Resolve<TService>(this IResolver resolver, object serviceKey,
            IfUnresolved ifUnresolved = IfUnresolved.Throw, Type requiredServiceType = null,
            object[] args = null) =>
            (TService)resolver.Resolve(typeof(TService), serviceKey, ifUnresolved, requiredServiceType, Request.Empty, args);

        /// <summary>Resolves the service supplying all or some of its dependencies 
        /// (including nested) with the <paramref name="args"/>. The rest of dependencies is injected from
        /// container.</summary>
        public static object Resolve(this IResolver resolver, Type serviceType, object[] args,
            IfUnresolved ifUnresolved = IfUnresolved.Throw, Type requiredServiceType = null,
            object serviceKey = null) =>
            resolver.Resolve(serviceType, serviceKey, ifUnresolved, requiredServiceType, Request.Empty, args);

        /// <summary>Resolves the service supplying all or some of its dependencies 
        /// (including nested) with the <paramref name="args"/>. The rest of dependencies is injected from
        /// container.</summary>
        public static TService Resolve<TService>(this IResolver resolver, object[] args,
            IfUnresolved ifUnresolved = IfUnresolved.Throw, Type requiredServiceType = null,
            object serviceKey = null) =>
            (TService)resolver.Resolve(typeof(TService), serviceKey, ifUnresolved, requiredServiceType, Request.Empty, args);

        /// <summary>Returns all registered services instances including all keyed and default registrations.
        /// Use <paramref name="behavior"/> to return either all registered services at the moment of resolve (dynamic fresh view) or
        /// the same services that were returned with first <see cref="ResolveMany{TService}"/> call (fixed view).</summary>
        /// <typeparam name="TService">Return collection item type. 
        /// It denotes registered service type if <paramref name="requiredServiceType"/> is not specified.</typeparam>
        /// <remarks>The same result could be achieved by directly calling:
        /// <code lang="cs"><![CDATA[
        ///     container.Resolve<LazyEnumerable<IService>>();  // for dynamic result - default behavior
        ///     container.Resolve<IService[]>();                // for fixed array
        ///     container.Resolve<IEnumerable<IService>>();     // same as fixed array
        /// ]]></code>
        /// </remarks>
        public static IEnumerable<TService> ResolveMany<TService>(this IResolver resolver,
            Type requiredServiceType = null, ResolveManyBehavior behavior = ResolveManyBehavior.AsLazyEnumerable,
            object[] args = null, object serviceKey = null) =>
            behavior == ResolveManyBehavior.AsLazyEnumerable
                ? resolver.ResolveMany(typeof(TService), serviceKey, requiredServiceType, Request.Empty, args).Cast<TService>()
                : resolver.Resolve<IEnumerable<TService>>(serviceKey, IfUnresolved.Throw, requiredServiceType, args);

        /// <summary>Returns all registered services as objects, including all keyed and default registrations.</summary>
        public static IEnumerable<object> ResolveMany(this IResolver resolver, Type serviceType,
            ResolveManyBehavior behavior = ResolveManyBehavior.AsLazyEnumerable,
            object[] args = null, object serviceKey = null) =>
            resolver.ResolveMany<object>(serviceType, behavior, args, serviceKey);

        internal static readonly ConstructorInfo ResolutionScopeNameCtor =
            typeof(ResolutionScopeName).GetTypeInfo().DeclaredConstructors.First();

        internal static Expression CreateResolutionExpression(Request request, bool opensResolutionScope = false)
        {
            if (request.Rules.DependencyResolutionCallExprs != null &&
                request.Factory != null && !request.Factory.HasRuntimeState)
                PopulateDependencyResolutionCallExpressions(request);

            var container = request.Container;
            var serviceType = request.ServiceType;

            var serviceTypeExpr = Constant(serviceType, typeof(Type));
            var ifUnresolvedExpr = Constant(request.IfUnresolved, typeof(IfUnresolved));
            var requiredServiceTypeExpr = Constant(request.RequiredServiceType, typeof(Type));
            var serviceKeyExpr = container.GetConstantExpression(request.ServiceKey, typeof(object));

            var resolverExpr = ResolverContext.GetRootOrSelfExpr(request);

            if (opensResolutionScope)
            {
                // Generates the code below. That means the service opening the scope is scoped to this scope.
                //
                // r => r.OpenScope(new ResolutionScopeName(serviceType, serviceKey), trackInParent: true)
                //       .Resolve(serviceType, serviceKey)
                //
                var actualServiceTypeExpr = Constant(request.GetActualServiceType(), typeof(Type));
                var scopeNameExpr = New(ResolutionScopeNameCtor, actualServiceTypeExpr, serviceKeyExpr);
                var trackInParent = Constant(true);

                resolverExpr = Call(ResolverContext.OpenScopeMethod,
                    FactoryDelegateCompiler.ResolverContextParamExpr, scopeNameExpr, trackInParent);
            }

            var parentFlags = default(RequestFlags);
            if (opensResolutionScope)
                parentFlags |= RequestFlags.OpensResolutionScope;
            if ((request.Flags & RequestFlags.StopRecursiveDependencyCheck) != 0)
                parentFlags |= RequestFlags.StopRecursiveDependencyCheck;

            // Only parent is converted to be passed to Resolve.
            // The current request is formed by rest of Resolve parameters.
            var preResolveParentExpr = container.GetRequestExpression(request.DirectParent, parentFlags);

            var resolveCallExpr = Call(resolverExpr, ResolveMethod, serviceTypeExpr, serviceKeyExpr,
                ifUnresolvedExpr, requiredServiceTypeExpr, preResolveParentExpr, request.GetInputArgsExpr());

            if (serviceType == typeof(object))
                return resolveCallExpr;

            return Convert(resolveCallExpr, serviceType);
        }

        private static void PopulateDependencyResolutionCallExpressions(Request request)
        {
            // Actually calls nested Resolve and stores produced expression in collection inside the container Rules.
            // Stops on recursive dependency, e.g. 
            // `new A(new Lazy<B>(r => r.Resolve<B>())` and `new B(new A())`
            for (var p = request.DirectParent; !p.IsEmpty; p = p.DirectParent)
                if (p.FactoryID == request.FactoryID)
                    return;

            var factory = request.Container.ResolveFactory(request);
            if (factory == null || factory is FactoryPlaceholder)
                return;

            // Prevents infinite recursion when generating the resolution dependency #579
            if ((request.Flags & RequestFlags.IsGeneratedResolutionDependencyExpression) != 0)
                return;

            request.Flags |= RequestFlags.IsGeneratedResolutionDependencyExpression;

            var factoryExpr = factory.GetExpressionOrDefault(request)?.NormalizeExpression();
            if (factoryExpr == null)
                return;

            request.Container.Rules.DependencyResolutionCallExprs.Swap(request, factoryExpr, 
                (x, req, facExpr) => x.AddOrUpdate(req, facExpr
#if SUPPORTS_FAST_EXPRESSION_COMPILER
                        .ToExpression()
#endif
                ));
        }
    }

    /// <summary>Specifies result of <see cref="Resolver.ResolveMany{TService}"/>: either dynamic(lazy) or fixed view.</summary>
    public enum ResolveManyBehavior
    {
        /// <summary>Lazy/dynamic item resolve.</summary>
        AsLazyEnumerable,
        /// <summary>Fixed array of item at time of resolve, newly registered/removed services won't be listed.</summary>
        AsFixedArray
    }

    /// <summary>Provides information required for service resolution: service type
    /// and optional <see cref="ServiceDetails"/></summary>
    public interface IServiceInfo
    {
        /// <summary>The required piece of info: service type.</summary>
        Type ServiceType { get; }

        /// <summary>Additional optional details: service key, if-unresolved policy, required service type.</summary>
        ServiceDetails Details { get; }

        /// <summary>Creates info from service type and details.</summary>
        IServiceInfo Create(Type serviceType, ServiceDetails details);
    }

    /// <summary>Provides optional service resolution details: service key, required service type, what return when service is unresolved,
    /// default value if service is unresolved, custom service value.</summary>
    public class ServiceDetails
    {
        /// Default details if not specified, use default setting values, e.g. <see cref="DryIoc.IfUnresolved.Throw"/>
        public static readonly ServiceDetails Default = 
            new ServiceDetails(null, IfUnresolved.Throw, null, null, null, null, false);

        /// Default details with <see cref="DryIoc.IfUnresolved.ReturnDefault"/> option.
        public static readonly ServiceDetails IfUnresolvedReturnDefault =
            new ServiceDetails(null, IfUnresolved.ReturnDefault, null, null, null, null, false);

        /// Default details with <see cref="DryIoc.IfUnresolved.ReturnDefaultIfNotRegistered"/> option.
        public static readonly ServiceDetails IfUnresolvedReturnDefaultIfNotRegistered =
            new ServiceDetails(null, IfUnresolved.ReturnDefaultIfNotRegistered, null, null, null, null, false);

        /// <summary>Creates new details out of provided settings, or returns default if all settings have default value.</summary>
        public static ServiceDetails Of(Type requiredServiceType = null,
            object serviceKey = null, IfUnresolved ifUnresolved = IfUnresolved.Throw,
            object defaultValue = null, string metadataKey = null, object metadata = null)
        {
            if (defaultValue != null)
            {
                // IfUnresolved.Throw does not make sense when default value is provided, so normalizing it to ReturnDefault
                if (ifUnresolved == IfUnresolved.Throw)
                    ifUnresolved = IfUnresolved.ReturnDefault;
            }
            else if (requiredServiceType == null && serviceKey == null &&
                     metadataKey == null && metadata == null)
            {
                if (ifUnresolved == IfUnresolved.Throw)
                    return Default;
                if (ifUnresolved == IfUnresolved.ReturnDefault)
                    return IfUnresolvedReturnDefault;
                if (ifUnresolved == IfUnresolved.ReturnDefaultIfNotRegistered)
                    return IfUnresolvedReturnDefaultIfNotRegistered;
            }

            return new ServiceDetails(requiredServiceType, ifUnresolved,
                serviceKey, metadataKey, metadata, defaultValue, hasCustomValue: false);
        }

        /// <summary>Sets custom value for service. This setting is orthogonal to the rest.
        /// Using default value with invalid ifUnresolved.Throw option to indicate custom value.</summary>
        public static ServiceDetails Of(object value) =>
            new ServiceDetails(null, IfUnresolved.Throw, null, null, null, value, hasCustomValue: true);

        /// <summary>Service type to search in registry. Should be assignable to user requested service type.</summary>
        public readonly Type RequiredServiceType;

        /// <summary>Service key provided with registration.</summary>
        public readonly object ServiceKey;

        /// <summary>Metadata key to find in metadata dictionary in resolved service.</summary>
        public readonly string MetadataKey;

        /// <summary>Metadata value to find in resolved service.</summary>
        public readonly object Metadata;

        /// <summary>Policy to deal with unresolved request.</summary>
        public readonly IfUnresolved IfUnresolved;

        /// <summary>Indicates that the custom value is specified.</summary>
        public readonly bool HasCustomValue;

        /// <summary>Either default or custom value depending on <see cref="IfUnresolved"/> setting.</summary>
        private readonly object _value;

        /// <summary>Value to use in case <see cref="IfUnresolved"/> is set to not Throw.</summary>
        public object DefaultValue => IfUnresolved != IfUnresolved.Throw ? _value : null;

        /// <summary>Custom value specified for dependency. The IfUnresolved.Throw is the marker of custom value comparing to default value.</summary>
        public object CustomValue => IfUnresolved == IfUnresolved.Throw ? _value : null;

        /// <summary>Pretty prints service details to string for debugging and errors.</summary> <returns>Details string.</returns>
        public override string ToString()
        {
            var s = new StringBuilder();

            if (HasCustomValue)
                return s.Append("{CustomValue=").Print(CustomValue ?? "null").Append("}").ToString();

            if (RequiredServiceType != null)
                s.Append("RequiredServiceType=").Print(RequiredServiceType);
            if (ServiceKey != null)
                (s.Length == 0 ? s.Append('{') : s.Append(", ")).Append("ServiceKey=").Print(ServiceKey);
            if (MetadataKey != null || Metadata != null)
                (s.Length == 0 ? s.Append('{') : s.Append(", ")).Append("Metadata=").Append(MetadataKey.Pair(Metadata));
            if (IfUnresolved != IfUnresolved.Throw)
            {
                s = (s.Length == 0 ? s.Append('{') : s.Append(", ")).Print(IfUnresolved);
                s = _value == null ? s : s.Append(", DefaultValue=").Print(_value);
            }

            return (s.Length == 0 ? s : s.Append('}')).ToString();
        }

        private ServiceDetails(Type requiredServiceType, IfUnresolved ifUnresolved,
            object serviceKey, string metadataKey, object metadata,
            object value, bool hasCustomValue)
        {
            RequiredServiceType = requiredServiceType;
            IfUnresolved = ifUnresolved;
            ServiceKey = serviceKey;
            MetadataKey = metadataKey;
            Metadata = metadata;
            _value = value;
            HasCustomValue = hasCustomValue;
        }
    }

    /// <summary>Contains tools for combining or propagating of <see cref="IServiceInfo"/> independent of its concrete implementations.</summary>
    public static class ServiceInfoTools
    {
        /// <summary>Creates service info with new type but keeping the details.</summary>
        public static IServiceInfo With(this IServiceInfo source, Type serviceType) =>
            source.Create(serviceType, source.Details);

        /// <summary>Creates new info with new IfUnresolved behavior or returns the original info if behavior is not different,
        /// or the passed info is not a <see cref="ServiceDetails.HasCustomValue"/>.</summary>
        public static IServiceInfo WithIfUnresolved(this IServiceInfo source, IfUnresolved ifUnresolved)
        {
            var details = source.Details;
            if (details.IfUnresolved == ifUnresolved || details.HasCustomValue)
                return source;

            if (details == ServiceDetails.Default)
                details = ifUnresolved == IfUnresolved.ReturnDefault
                    ? ServiceDetails.IfUnresolvedReturnDefault
                    : ServiceDetails.IfUnresolvedReturnDefaultIfNotRegistered;
            else
                details = ServiceDetails.Of(details.RequiredServiceType, details.ServiceKey,
                    ifUnresolved, details.DefaultValue, details.MetadataKey, details.Metadata);

            return source.Create(source.ServiceType, details);
        }

        // todo: Should be renamed or better to be removed, the whole operation should be hidden behind abstraction
        /// <summary>Combines service info with details. The main goal is to combine service and required service type.</summary>
        public static T WithDetails<T>(this T serviceInfo, ServiceDetails details)
            where T : IServiceInfo
        {
            details = details ?? ServiceDetails.Default;
            var sourceDetails = serviceInfo.Details;
            if (!details.HasCustomValue &&
                sourceDetails != ServiceDetails.Default &&
                sourceDetails != details)
            {
                var serviceKey = details.ServiceKey ?? sourceDetails.ServiceKey;
                var metadataKey = details.MetadataKey ?? sourceDetails.MetadataKey;
                var metadata = metadataKey == details.MetadataKey ? details.Metadata : sourceDetails.Metadata;
                var defaultValue = details.DefaultValue ?? sourceDetails.DefaultValue;

                details = ServiceDetails.Of(details.RequiredServiceType, serviceKey,
                    details.IfUnresolved, defaultValue, metadataKey, metadata);
            }

            var serviceType = serviceInfo.ServiceType;
            var requiredServiceType = details.RequiredServiceType;

            if (requiredServiceType != null && requiredServiceType == serviceType)
                details = ServiceDetails.Of(null,
                    details.ServiceKey, details.IfUnresolved, details.DefaultValue,
                    details.MetadataKey, details.Metadata);

            // if service type unchanged and details absent, or details are the same return original info, otherwise create new one
            return serviceType == serviceInfo.ServiceType
                && (details == null || details == serviceInfo.Details)
                ? serviceInfo
                : (T)serviceInfo.Create(serviceType, details);
        }

        // todo: May operate directly on ServiceType and ServiceDetails instead of IServiceInfo interface
        /// <summary>Enables propagation/inheritance of info between dependency and its owner:
        /// for instance <see cref="ServiceDetails.RequiredServiceType"/> for wrappers.</summary>
        public static IServiceInfo InheritInfoFromDependencyOwner(this IServiceInfo dependency,
            IServiceInfo owner, IContainer container, FactoryType ownerType = FactoryType.Service)
        {
            var ownerDetails = owner.Details;
            if (ownerDetails == null || ownerDetails == ServiceDetails.Default)
                return dependency;

            var dependencyDetails = dependency.Details;

            var ownerIfUnresolved = ownerDetails.IfUnresolved;
            var ifUnresolved = dependencyDetails.IfUnresolved;
            if (ownerIfUnresolved == IfUnresolved.ReturnDefault) // ReturnDefault is always inherited
                ifUnresolved = ownerIfUnresolved;

            var serviceType = dependency.ServiceType;
            var requiredServiceType = dependencyDetails.RequiredServiceType;
            var ownerRequiredServiceType = ownerDetails.RequiredServiceType;

            var serviceKey = dependencyDetails.ServiceKey;
            var metadataKey = dependencyDetails.MetadataKey;
            var metadata = dependencyDetails.Metadata;

            // Inherit some things through wrappers and decorators
            if (ownerType == FactoryType.Wrapper ||
                ownerType == FactoryType.Decorator &&
                container.GetWrappedType(serviceType, requiredServiceType).IsAssignableTo(owner.ServiceType))
            {
                if (ownerIfUnresolved == IfUnresolved.ReturnDefaultIfNotRegistered)
                    ifUnresolved = ownerIfUnresolved;

                if (serviceKey == null)
                    serviceKey = ownerDetails.ServiceKey;

                if (metadataKey == null && metadata == null)
                {
                    metadataKey = ownerDetails.MetadataKey;
                    metadata = ownerDetails.Metadata;
                }
            }

            if (ownerType != FactoryType.Service && ownerRequiredServiceType != null &&
                requiredServiceType == null) // if only dependency does not have its own
                requiredServiceType = ownerRequiredServiceType;

            if (serviceKey == dependencyDetails.ServiceKey &&
                metadataKey == dependencyDetails.MetadataKey && metadata == dependencyDetails.Metadata &&
                ifUnresolved == dependencyDetails.IfUnresolved && requiredServiceType == dependencyDetails.RequiredServiceType)
                return dependency;

            if (serviceType == requiredServiceType)
                requiredServiceType = null;

            var serviceDetails = ServiceDetails.Of(requiredServiceType,
                serviceKey, ifUnresolved, dependencyDetails.DefaultValue,
                metadataKey, metadata);

            return dependency.Create(serviceType, serviceDetails);
        }

        /// <summary>Returns required service type if it is specified and assignable to service type,
        /// otherwise returns service type.</summary>
        public static Type GetActualServiceType(this IServiceInfo info)
        {
            var requiredServiceType = info.Details.RequiredServiceType;
            return requiredServiceType != null && 
                info.ServiceType.GetTypeInfo().IsAssignableFrom(requiredServiceType.GetTypeInfo())
                ? requiredServiceType : info.ServiceType;
        }

        /// <summary>Appends info string representation into provided builder.</summary>
        public static StringBuilder Print(this StringBuilder s, IServiceInfo info)
        {
            s.Print(info.ServiceType);
            var details = info.Details.ToString();
            return details == string.Empty ? s : s.Append(' ').Append(details);
        }
    }

    /// <summary>Represents custom or resolution root service info, there is separate representation for parameter,
    /// property and field dependencies.</summary>
    public class ServiceInfo : IServiceInfo
    {
        /// <summary>Empty service info for convenience.</summary>
        public static readonly IServiceInfo Empty = new ServiceInfo(null);

        /// <summary>Creates info out of provided settings</summary>
        public static ServiceInfo Of(Type serviceType,
            IfUnresolved ifUnresolved = IfUnresolved.Throw, object serviceKey = null) =>
            Of(serviceType, null, ifUnresolved, serviceKey);

        /// <summary>Creates info out of provided settings</summary>
        public static ServiceInfo Of(Type serviceType, Type requiredServiceType,
            IfUnresolved ifUnresolved = IfUnresolved.Throw, object serviceKey = null,
            string metadataKey = null, object metadata = null)
        {
            (serviceType ?? requiredServiceType).ThrowIfNull();

            // remove unnecessary details if service and required type are the same
            if (serviceType == requiredServiceType)
                requiredServiceType = null;

            return serviceKey == null && requiredServiceType == null
                && metadataKey == null && metadata == null
                ? (ifUnresolved == IfUnresolved.Throw ? new ServiceInfo(serviceType)
                    : ifUnresolved == IfUnresolved.ReturnDefault ? new WithDetails(serviceType, ServiceDetails.IfUnresolvedReturnDefault)
                    : new WithDetails(serviceType, ServiceDetails.IfUnresolvedReturnDefaultIfNotRegistered))
                : new WithDetails(serviceType,
                ServiceDetails.Of(requiredServiceType, serviceKey, ifUnresolved, null, metadataKey, metadata));
        }

        /// <summary>Creates service info using typed <typeparamref name="TService"/>.</summary>
        public static Typed<TService> Of<TService>(IfUnresolved ifUnresolved = IfUnresolved.Throw, object serviceKey = null) =>
            serviceKey == null && ifUnresolved == IfUnresolved.Throw
                ? new Typed<TService>()
                : new TypedWithDetails<TService>(ServiceDetails.Of(null, serviceKey, ifUnresolved));

        /// <summary>Strongly-typed version of Service Info.</summary> <typeparam name="TService">Service type.</typeparam>
        public class Typed<TService> : ServiceInfo
        {
            /// <summary>Creates service info object.</summary>
            public Typed() : base(typeof(TService)) { }
        }

        /// <summary>Type of service to create. Indicates registered service in registry.</summary>
        public Type ServiceType { get; }

        /// <summary>Shortcut access to service key</summary>
        public object ServiceKey => Details.ServiceKey;

        /// <summary>Additional settings. If not specified uses <see cref="ServiceDetails.Default"/>.</summary>
        public virtual ServiceDetails Details => ServiceDetails.Default;

        /// <summary>Creates info from service type and details.</summary>
        public IServiceInfo Create(Type serviceType, ServiceDetails details) =>
            details == ServiceDetails.Default ? new ServiceInfo(serviceType) : new WithDetails(serviceType, details);

        /// <summary>Prints info to string using <see cref="ServiceInfoTools.Print"/>.</summary> <returns>Printed string.</returns>
        public override string ToString() =>
            new StringBuilder().Print(this).ToString();

#region Implementation

        private ServiceInfo(Type serviceType) { ServiceType = serviceType; }

        private class WithDetails : ServiceInfo
        {
            public override ServiceDetails Details => _details;
            public WithDetails(Type serviceType, ServiceDetails details) : base(serviceType) { _details = details; }
            private readonly ServiceDetails _details;
        }

        private class TypedWithDetails<TService> : Typed<TService>
        {
            public override ServiceDetails Details => _details;
            public TypedWithDetails(ServiceDetails details) { _details = details; }
            private readonly ServiceDetails _details;
        }

#endregion
    }

    /// <summary>Provides <see cref="IServiceInfo"/> for parameter,
    /// by default using parameter name as <see cref="IServiceInfo.ServiceType"/>.</summary>
    /// <remarks>For parameter default setting <see cref="ServiceDetails.IfUnresolved"/> is <see cref="IfUnresolved.Throw"/>.</remarks>
    public class ParameterServiceInfo : IServiceInfo
    {
        /// <summary>Creates service info from parameter alone, setting service type to parameter type,
        /// and setting resolution policy to <see cref="IfUnresolved.ReturnDefault"/> if parameter is optional.</summary>
        /// <param name="parameter">Parameter to create info for.</param>
        /// <returns>Parameter service info.</returns>
        [MethodImpl((MethodImplOptions)256)]
        public static ParameterServiceInfo Of(ParameterInfo parameter)
        {
            if (!parameter.IsOptional)
                return new ParameterServiceInfo(parameter);
            return new WithDetails(parameter, parameter.DefaultValue == null 
                ? ServiceDetails.IfUnresolvedReturnDefault 
                : ServiceDetails.Of(ifUnresolved: IfUnresolved.ReturnDefault, defaultValue: parameter.DefaultValue));
        }

        /// <summary>Service type specified by <see cref="ParameterInfo.ParameterType"/>.</summary>
        public virtual Type ServiceType => Parameter.ParameterType;

        /// <summary>Optional service details.</summary>
        public virtual ServiceDetails Details => ServiceDetails.Default;

        /// <summary>Creates info from service type and details.</summary>
        public IServiceInfo Create(Type serviceType, ServiceDetails details) =>
            serviceType == ServiceType ? new WithDetails(Parameter, details) : new TypeWithDetails(Parameter, serviceType, details);

        /// <summary>Parameter info.</summary>
        public readonly ParameterInfo Parameter;

        /// <summary>Prints info to string using <see cref="ServiceInfoTools.Print"/>.</summary> <returns>Printed string.</returns>
        public override string ToString() =>
            new StringBuilder().Print(this).Append(" as parameter ").Print(Parameter.Name).ToString();

#region Implementation

        private ParameterServiceInfo(ParameterInfo parameter) { Parameter = parameter; }

        private class WithDetails : ParameterServiceInfo
        {
            public override ServiceDetails Details { get { return _details; } }
            public WithDetails(ParameterInfo parameter, ServiceDetails details)
                : base(parameter)
            { _details = details; }
            private readonly ServiceDetails _details;
        }

        private sealed class TypeWithDetails : WithDetails
        {
            public override Type ServiceType { get { return _serviceType; } }
            public TypeWithDetails(ParameterInfo parameter, Type serviceType, ServiceDetails details)
                : base(parameter, details)
            { _serviceType = serviceType; }
            private readonly Type _serviceType;
        }

#endregion
    }

    /// <summary>Base class for property and field dependency info.</summary>
    public abstract class PropertyOrFieldServiceInfo : IServiceInfo
    {
        /// <summary>Create member info out of provide property or field.</summary>
        /// <param name="member">Member is either property or field.</param> <returns>Created info.</returns>
        public static PropertyOrFieldServiceInfo Of(MemberInfo member) =>
            member.ThrowIfNull() is PropertyInfo
                ? (PropertyOrFieldServiceInfo)new Property((PropertyInfo)member)
                : new Field((FieldInfo)member);

        /// <summary>The required service type. It will be either <see cref="FieldInfo.FieldType"/> or <see cref="PropertyInfo.PropertyType"/>.</summary>
        public abstract Type ServiceType { get; }

        /// <summary>Optional details: service key, if-unresolved policy, required service type.</summary>
        public virtual ServiceDetails Details => ServiceDetails.IfUnresolvedReturnDefaultIfNotRegistered;

        /// <summary>Creates info from service type and details.</summary>
        /// <param name="serviceType">Required service type.</param> <param name="details">Optional details.</param> <returns>Create info.</returns>
        public abstract IServiceInfo Create(Type serviceType, ServiceDetails details);

        /// <summary>Either <see cref="PropertyInfo"/> or <see cref="FieldInfo"/>.</summary>
        public abstract MemberInfo Member { get; }

        /// <summary>Sets property or field value on provided holder object.</summary>
        /// <param name="holder">Holder of property or field.</param> <param name="value">Value to set.</param>
        public abstract void SetValue(object holder, object value);

#region Implementation

        private class Property : PropertyOrFieldServiceInfo
        {
            public override Type ServiceType => _property.PropertyType;

            public override IServiceInfo Create(Type serviceType, ServiceDetails details) =>
                serviceType == ServiceType ? new WithDetails(_property, details) : new TypeWithDetails(_property, serviceType, details);

            public override MemberInfo Member => _property;

            public override void SetValue(object holder, object value) => _property.SetValue(holder, value, null);

            public override string ToString() =>
                new StringBuilder().Print(this).Append(" as property ").Print(_property.Name).ToString();

            private readonly PropertyInfo _property;
            public Property(PropertyInfo property) { _property = property; }

            private class WithDetails : Property
            {
                public override ServiceDetails Details { get; }

                public WithDetails(PropertyInfo property, ServiceDetails details) : base(property) { Details = details; }
            }

            private sealed class TypeWithDetails : WithDetails
            {
                public override Type ServiceType { get; }

                public TypeWithDetails(PropertyInfo property, Type serviceType, ServiceDetails details)
                    : base(property, details) { ServiceType = serviceType; }
            }
        }

        private class Field : PropertyOrFieldServiceInfo
        {
            public override Type ServiceType => _field.FieldType;

            public override IServiceInfo Create(Type serviceType, ServiceDetails details) =>
                serviceType == null ? new WithDetails(_field, details) : new TypeWithDetails(_field, serviceType, details);

            public override MemberInfo Member => _field;

            public override void SetValue(object holder, object value) => _field.SetValue(holder, value);

            public override string ToString() =>
                new StringBuilder().Print(this).Append(" as field ").Print(_field.Name).ToString();

            private readonly FieldInfo _field;
            public Field(FieldInfo field) { _field = field; }

            private class WithDetails : Field
            {
                public override ServiceDetails Details { get; }

                public WithDetails(FieldInfo field, ServiceDetails details) : base(field) { Details = details; }
            }

            private sealed class TypeWithDetails : WithDetails
            {
                public override Type ServiceType { get; }

                public TypeWithDetails(FieldInfo field, Type serviceType, ServiceDetails details) : base(field, details)
                { ServiceType = serviceType; }
            }
        }

#endregion
    }

    /// <summary>Stored check results of two kinds: inherited down dependency chain and not.</summary>
    [Flags]
    public enum RequestFlags
    {
        /// <summary>Not inherited</summary>
        TracksTransientDisposable = 1 << 1,

        /// <summary>Inherited</summary>
        IsSingletonOrDependencyOfSingleton = 1 << 3,

        /// <summary>Inherited</summary>
        IsWrappedInFunc = 1 << 4,

        /// <summary>Indicates that the request is the one from Resolve call.</summary>
        IsResolutionCall = 1 << 5,

        /// <summary>Non inherited</summary>
        OpensResolutionScope = 1 << 6,

        /// <summary>Non inherited</summary>
        StopRecursiveDependencyCheck = 1 << 7,

        /// <summary>Non inherited. Marks the expression to be added to generated resolutions to prevent infinite recursion</summary>
        IsGeneratedResolutionDependencyExpression = 1 << 8,

        /// <summary>Non inherited. Indicates the root service inside the function.</summary>
        IsDirectlyWrappedInFunc = 1 << 9
    }

    /// Helper extension methods to use on the bunch of factories instead of lambdas to minimize allocations
    internal static class RequestTools
    {
        public static bool MatchFactoryConditionAndMetadata(this Request request, Factory factory)
        {
            if (!factory.CheckCondition(request))
                return false;

            var metadataKey = request.MetadataKey;
            var metadata = request.Metadata;
            return (metadataKey == null && metadata == null) || factory.Setup.MatchesMetadata(metadataKey, metadata);
        }

        public static bool MatchFactoryConditionAndMetadata(this Request r, KV<object, Factory> f) => 
            r.MatchFactoryConditionAndMetadata(f.Value);

        public static bool MatchFactoryReuse(this Request r, KV<object, Factory> f) => 
            f.Value.Reuse?.CanApply(r) ?? true;

        public static bool MatchGeneratedFactory(this Request r, KV<object, Factory> f) =>
            f.Value.FactoryGenerator == null || f.Value.FactoryGenerator.GetGeneratedFactory(r, ifErrorReturnDefault: true) != null;
    }

    internal sealed class RequestStack
    {
        public static RequestStack Get(int index = 0)
        {
            var capacity = 4;
            while (index >= capacity)
                capacity <<= 1;
            return new RequestStack(capacity);
        }

        public Request[] Items;
        private RequestStack(int capacity) => Items = new Request[capacity];

        public ref Request GetOrPushRef(int index)
        {
            if (index < Items.Length)
                return ref Items[index];

            Items = Expand(Items, index);
            return ref Items[index];
        }

        private static Request[] Expand(Request[] items, int index)
        {
            var count = items.Length;
            var newCount = count << 1;

            // ensure that the index is always in range
            while (index >= newCount)
                newCount <<= 1;

            var newItems = new Request[newCount]; // count x 2
            Array.Copy(items, 0, newItems, 0, count);
            return newItems;
        }
    }

    /// <summary>Tracks the requested service and resolved factory details in a chain of nested dependencies.</summary>
    public sealed class Request : IEnumerable<Request>
    {
        internal static readonly RequestFlags InheritedFlags
            = RequestFlags.IsSingletonOrDependencyOfSingleton
            | RequestFlags.IsWrappedInFunc;

        private const RequestFlags DefaultFlags = default;

        /// <summary>Empty terminal request.</summary>
        public static readonly Request Empty =
            new Request(null, null, 0, null, DefaultFlags, ServiceInfo.Empty, null);

        internal static readonly Expression EmptyRequestExpr =
            Field(null, typeof(Request).Field(nameof(Empty)));

        /// <summary>Empty request which opens resolution scope.</summary>
        public static readonly Request EmptyOpensResolutionScope =
            new Request(null, null, 0, null, DefaultFlags | RequestFlags.OpensResolutionScope | RequestFlags.IsResolutionCall, 
                ServiceInfo.Empty, null);

        internal static readonly Expression EmptyOpensResolutionScopeRequestExpr =
            Field(null, typeof(Request).Field(nameof(EmptyOpensResolutionScope)));

        /// <summary>Creates the Resolve request. The container initiated the Resolve is stored with request.</summary>
        public static Request Create(IContainer container, IServiceInfo serviceInfo,
            Request preResolveParent = null, RequestFlags flags = DefaultFlags, object[] inputArgs = null)
        {
            var serviceType = serviceInfo.ThrowIfNull().ServiceType;
            if (serviceType != null && serviceType.IsOpenGeneric())
                Throw.It(Error.ResolvingOpenGenericServiceTypeIsNotPossible, serviceType);

            flags |= RequestFlags.IsResolutionCall;

            // inherit some flags and service details from parent (if any)
            preResolveParent = preResolveParent ?? Empty;
            if (!preResolveParent.IsEmpty)
            {
                serviceInfo = serviceInfo.InheritInfoFromDependencyOwner(
                    preResolveParent._serviceInfo, container, preResolveParent.FactoryType);

                flags |= preResolveParent.Flags & InheritedFlags;
            }

            var inputArgExprs = inputArgs?.Map(a => Constant(a));

            var stack = RequestStack.Get();
            ref var req = ref stack.GetOrPushRef(0);

            // we are re-starting the dependency depth count from `1`
            if (req == null)
                req =  new Request(container, preResolveParent, 1, stack, flags, serviceInfo, inputArgExprs);
            else
                req.SetServiceInfo(container, preResolveParent, 1, stack, flags, serviceInfo, inputArgExprs);
            return req;
        }

        /// <summary>Creates the Resolve request. The container initiated the Resolve is stored with request.</summary>
        public static Request Create(IContainer container, Type serviceType,
            object serviceKey = null, IfUnresolved ifUnresolved = IfUnresolved.Throw, Type requiredServiceType = null,
            Request preResolveParent = null, RequestFlags flags = DefaultFlags, object[] inputArgs = null) =>
            Create(container, ServiceInfo.Of(serviceType, requiredServiceType, ifUnresolved, serviceKey),
                preResolveParent, flags, inputArgs);

        // todo: Make a property in v5.0
        /// <summary>Available in runtime only, provides access to container initiated the request.</summary>
        public IContainer Container;

        /// <summary>Request immediate parent.</summary>
        public Request DirectParent;

        internal RequestStack RequestStack;
        internal int IndexInStack => DependencyDepth - 1;

        // note: mutable because of RequestFlags.AddedToResolutionExpressions
        /// <summary>Persisted request conditions</summary>
        public RequestFlags Flags;

        /// mutable, so that the ServiceKey or IfUnresolved can be changed in place.
        internal IServiceInfo _serviceInfo;

        /// <summary>Service details part of service info</summary>
        public ServiceDetails ServiceDetails => _serviceInfo.Details;

        //internal IServiceInfo _serviceDetails; // todo: use this as much as possible instead of `_serviceInfo` to avoid virtual calls

        /// <summary>Input arguments provided with `Resolve`</summary>
        internal Expression[] InputArgExprs;

        /// <summary>Runtime known resolve factory, otherwise is <c>null</c></summary>
        internal Factory Factory;

        /// <summary>Resolved factory ID, used to identify applied decorator.</summary>
        public int FactoryID { get; private set; }

        // based on FactoryID
        private int _hashCode;

        /// <summary>Type of factory: Service, Wrapper, or Decorator.</summary>
        public FactoryType FactoryType { get; private set; }

        /// <summary>Service implementation type if known.</summary>
        public Type ImplementationType => _factoryImplType ?? Factory?.ImplementationType;
        private Type _factoryImplType;

        /// <summary>Service reuse.</summary>
        public IReuse Reuse { get; private set; }

        /// <summary>ID of decorated factory in case of decorator factory type</summary>
        public int DecoratedFactoryID { get; private set; }

        /// <summary>Number of nested dependencies. Set with each new Push.</summary>
        public int DependencyDepth;

        /// <summary>Indicates that request is empty initial request.</summary>
        public bool IsEmpty => DirectParent == null;

        /// <summary>Returns true if request is First in First Resolve call.</summary>
        public bool IsResolutionRoot => DirectParent != null && DirectParent.DirectParent == null;

        /// <summary>Returns true if request is First in Resolve call.</summary>
        public bool IsResolutionCall => DirectParent != null && (Flags & RequestFlags.IsResolutionCall) != 0;

        /// <summary>Not the root resolution call.</summary>
        public bool IsNestedResolutionCall => IsResolutionCall && DirectParent.DirectParent != null;

        /// <summary>Returns true if request is First in First Resolve call.</summary>
        public bool OpensResolutionScope => DirectParent != null && (DirectParent.Flags & RequestFlags.OpensResolutionScope) != 0;

        /// <summary>Checks if the request Or its parent is wrapped in Func.
        /// Use <see cref="IsDirectlyWrappedInFunc"/> for the direct Func wrapper.</summary>
        public bool IsWrappedInFunc() => (Flags & RequestFlags.IsWrappedInFunc) != 0;

        /// <summary>Checks if the request is directly wrapped in Func</summary>
        public bool IsDirectlyWrappedInFunc() => (Flags & RequestFlags.IsDirectlyWrappedInFunc) != 0;

        /// <summary>Checks if request has parent with service type of Func with arguments.</summary>
        public bool IsWrappedInFuncWithArgs() => InputArgExprs != null;

        /// <summary>Returns expression for func arguments.</summary>
        public Expression GetInputArgsExpr() =>
            InputArgExprs == null ? Constant(null, typeof(object[]))
            : (Expression)NewArrayInit(typeof(object), InputArgExprs.Map(x => x.Type.IsValueType() ? Convert(x, typeof(object)) : x));

        /// <summary>Indicates that requested service is transient disposable that should be tracked.</summary>
        public bool TracksTransientDisposable => (Flags & RequestFlags.TracksTransientDisposable) != 0;

        /// <summary>Indicates the request is singleton or has singleton upper in dependency chain.</summary>
        public bool IsSingletonOrDependencyOfSingleton => (Flags & RequestFlags.IsSingletonOrDependencyOfSingleton) != 0;

        /// [Obsolete("Unused - hides more than abstracts")]
        public bool ShouldSplitObjectGraph() =>
            FactoryType == FactoryType.Service &&
            DependencyDepth > Rules.DependencyDepthToSplitObjectGraph;

        /// <summary>Current scope</summary>
        public IScope CurrentScope => Container.CurrentScope;

        /// <summary>Singletons</summary>
        public IScope SingletonScope => Container.SingletonScope;

        /// <summary>Shortcut to issued container rules.</summary>
        public Rules Rules => Container.Rules;

        /// <summary>(optional) Made spec used for resolving request.</summary>
        public Made Made => Factory?.Made;

        /// <summary>Returns service parent skipping wrapper if any. To get direct parent use <see cref="DirectParent"/>.</summary>
        public Request Parent
        {
            get
            {
                var p = DirectParent;
                if (p != null)
                    while (p.DirectParent != null && p.FactoryType == FactoryType.Wrapper)
                        p = p.DirectParent;
                return p;
            }
        }

        /// <summary>Requested service type.</summary>
        public Type ServiceType => _serviceInfo.ServiceType;

        /// <summary>Compatible required or service type.</summary>
        public Type GetActualServiceType() => _actualServiceType;
        private Type _actualServiceType;

        /// <summary>Optional service key to identify service of the same type.</summary>
        public object ServiceKey => _serviceInfo.Details.ServiceKey;

        /// <summary>Metadata key to find in metadata dictionary in resolved service.</summary>
        public string MetadataKey => _serviceInfo.Details.MetadataKey;

        /// <summary>Metadata or the value (if key specified) to find in resolved service.</summary>
        public object Metadata => _serviceInfo.Details.Metadata;

        /// <summary>Policy to deal with unresolved service.</summary>
        public IfUnresolved IfUnresolved => _serviceInfo.Details.IfUnresolved;

        /// <summary>Required service type if specified.</summary>
        public Type RequiredServiceType => _serviceInfo.Details.RequiredServiceType;

        /// <summary>Relative number representing reuse lifespan.</summary>
        public int ReuseLifespan => Reuse?.Lifespan ?? 0;

        /// <summary>Known implementation, or otherwise actual service type.</summary>
        public Type GetKnownImplementationOrServiceType() => ImplementationType ?? GetActualServiceType();

        /// <summary>Creates new request with provided info, and links current request as a parent.
        /// Allows to set some additional flags. Existing/parent request should be resolved to 
        /// factory via `WithResolvedFactory` before pushing info into it.</summary>
        public Request Push(IServiceInfo info, RequestFlags additionalFlags = DefaultFlags)
        {
            if (FactoryID == 0)
                Throw.It(Error.PushingToRequestWithoutFactory, info, this);

            var flags = Flags & InheritedFlags | additionalFlags;
            var serviceInfo = info.ThrowIfNull().InheritInfoFromDependencyOwner(_serviceInfo, Container, FactoryType);

            var stack = RequestStack;
            var indexInStack = IndexInStack + 1;
            if (stack == null)
            {
                stack = RequestStack.Get(indexInStack);

                // traverse all the requests up including the resolution root and set the new stack to them
                Request parent = null;
                do
                {
                    parent = parent == null ? this : parent.DirectParent;
                    parent.RequestStack = stack;
                }
                while ((parent.Flags & RequestFlags.IsResolutionCall) == 0 && !parent.DirectParent.IsEmpty);
            }

            ref var req = ref stack.GetOrPushRef(indexInStack);
            if (req == null)
                req  = new Request(Container, this, DependencyDepth + 1, RequestStack, flags, serviceInfo, InputArgExprs);
            else
                req.SetServiceInfo(Container, this, DependencyDepth + 1, RequestStack, flags, serviceInfo, InputArgExprs);
            return req;
        }

        /// <summary>Composes service description into <see cref="IServiceInfo"/> and Pushes the new request.</summary>
        public Request Push(Type serviceType, object serviceKey = null,
            IfUnresolved ifUnresolved = IfUnresolved.Throw, Type requiredServiceType = null, RequestFlags flags = DefaultFlags) =>
            Push(ServiceInfo.Of(serviceType.ThrowIfNull().ThrowIf(serviceType.IsOpenGeneric(), Error.ResolvingOpenGenericServiceTypeIsNotPossible),
                requiredServiceType, ifUnresolved, serviceKey), flags);

#region Used in generated expression

        /// <summary>Creates info by supplying all the properties and chaining it with current (parent) info.</summary>
        public Request Push(Type serviceType, int factoryID, Type implementationType, IReuse reuse) =>
            Push(serviceType, null, null, null, null, IfUnresolved.Throw,
                factoryID, FactoryType.Service, implementationType, reuse, DefaultFlags, 0);

        internal static readonly Lazy<MethodInfo> PushMethodWith4Args = Lazy.Of(() =>
            typeof(Request).Method(nameof(Push), typeof(Type), typeof(int), typeof(Type), typeof(IReuse)));

        /// <summary>Creates info by supplying all the properties and chaining it with current (parent) info.</summary>
        public Request Push(Type serviceType, Type requiredServiceType, object serviceKey,
            int factoryID, FactoryType factoryType, Type implementationType, IReuse reuse, RequestFlags flags) =>
            Push(serviceType, requiredServiceType, serviceKey, null, null, IfUnresolved.Throw,
                factoryID, factoryType, implementationType, reuse, flags, 0);

        internal static readonly Lazy<MethodInfo> PushMethodWith8Args = Lazy.Of(() =>
            typeof(Request).Method(nameof(Push), typeof(Type), typeof(Type), typeof(object),
                typeof(int), typeof(FactoryType), typeof(Type), typeof(IReuse), typeof(RequestFlags)));

        /// <summary>Creates info by supplying all the properties and chaining it with current (parent) info.</summary>
        public Request Push(Type serviceType, Type requiredServiceType, object serviceKey, IfUnresolved ifUnresolved,
            int factoryID, FactoryType factoryType, Type implementationType, IReuse reuse, RequestFlags flags,
            int decoratedFactoryID) =>
            Push(serviceType, requiredServiceType, serviceKey, null, null, ifUnresolved,
                factoryID, factoryType, implementationType, reuse, flags, decoratedFactoryID);

        internal static readonly Lazy<MethodInfo> PushMethodWith10Args = Lazy.Of(() =>
            typeof(Request).Method(nameof(Push),
                typeof(Type), typeof(Type), typeof(object), typeof(IfUnresolved),
                typeof(int), typeof(FactoryType), typeof(Type), typeof(IReuse), typeof(RequestFlags), typeof(int)));

        /// <summary>Creates info by supplying all the properties and chaining it with current (parent) info.</summary>
        public Request Push(
            Type serviceType, Type requiredServiceType, object serviceKey, string metadataKey, object metadata, IfUnresolved ifUnresolved, 
            int factoryID, FactoryType factoryType, Type implementationType, IReuse reuse, RequestFlags flags, int decoratedFactoryID)
        {
            return new Request(Container, this, DependencyDepth + 1, null, flags,
                ServiceInfo.Of(serviceType, requiredServiceType, ifUnresolved, serviceKey, metadataKey, metadata),
                InputArgExprs, implementationType, null, // factory cannot be supplied in generated code
                factoryID, factoryType, reuse, decoratedFactoryID);
        }

        internal static readonly Lazy<MethodInfo> PushMethodWith12Args = Lazy.Of(() =>
            typeof(Request).Method(nameof(Push),
            typeof(Type), typeof(Type), typeof(object), typeof(string), typeof(object), typeof(IfUnresolved),
            typeof(int), typeof(FactoryType), typeof(Type), typeof(IReuse), typeof(RequestFlags), typeof(int)));

#endregion

        /// <summary>Allow to switch current service info to the new one, e.g. in decorators.
        /// If info did not change then return the same this request.</summary>
        public Request WithChangedServiceInfo(Func<IServiceInfo, IServiceInfo> getInfo)
        {
            var newServiceInfo = getInfo(_serviceInfo);
            return newServiceInfo == _serviceInfo ? this
                : new Request(Container, 
                    DirectParent, DependencyDepth, RequestStack, Flags, newServiceInfo, InputArgExprs,
                    _factoryImplType, Factory, FactoryID, FactoryType, Reuse, DecoratedFactoryID);
        }

        /// Produces the new request with the changed `ifUnresolved` or returns original request otherwise
        public Request WithIfUnresolved(IfUnresolved ifUnresolved) =>
            IfUnresolved == ifUnresolved ? this
                : new Request(Container,
                    DirectParent, DependencyDepth, RequestStack, Flags, 
                    _serviceInfo.WithIfUnresolved(ifUnresolved), InputArgExprs,
                    _factoryImplType, Factory, FactoryID, FactoryType, Reuse, DecoratedFactoryID);

        // todo: in place mutation?
        /// <summary>Updates the flags</summary>
        public Request WithFlags(RequestFlags newFlags) =>
            new Request(Container,
                DirectParent, DependencyDepth, RequestStack, newFlags, _serviceInfo, InputArgExprs,
                _factoryImplType, Factory, FactoryID, FactoryType, Reuse, DecoratedFactoryID);

        // note: Mutates the request, required for proper caching
        /// <summary>Sets service key to passed value. Required for multiple default services to change null key to
        /// actual <see cref="DefaultKey"/></summary>
        public void ChangeServiceKey(object serviceKey)
        {
            var info = _serviceInfo;
            var details = info.Details;
            _serviceInfo = info.Create(info.ServiceType,
                ServiceDetails.Of(details.RequiredServiceType, serviceKey, details.IfUnresolved, details.DefaultValue));
        }

        /// <summary>Prepends input arguments to existing arguments in request. It is done because the
        /// nested Func/Action input argument has a priority over outer argument.
        /// The arguments are provided by Func and Action wrappers, or by `args` parameter in Resolve call.</summary>
        public Request WithInputArgs(Expression[] inputArgs) =>
            new Request(Container, 
                DirectParent, DependencyDepth, RequestStack, Flags, _serviceInfo, inputArgs.Append(InputArgExprs),
                _factoryImplType, Factory, FactoryID, FactoryType, Reuse, DecoratedFactoryID);

        /// <summary>Returns new request with set implementation details.</summary>
        /// <param name="factory">Factory to which request is resolved.</param>
        /// <param name="skipRecursiveDependencyCheck">(optional) does not check for recursive dependency.
        /// Use with caution. Make sense for Resolution expression.</param>
        /// <param name="skipCaptiveDependencyCheck">(optional) allows to skip reuse mismatch aka captive dependency check.</param>
        /// <param name="copyRequest">Make a defensive copy of request.</param>
        /// <returns>New request with set factory.</returns>
        public Request WithResolvedFactory(Factory factory,
            bool skipRecursiveDependencyCheck = false, bool skipCaptiveDependencyCheck = false, bool copyRequest = false)
        {
            var factoryId   = factory.FactoryID;
            var factoryType = factory.FactoryType;
            var decoratedFactoryID = 0;
            if (Factory != null) // resolving the factory for the second time, usually happens in decorators
            {
                if (Factory.FactoryID == factoryId)
                    return this; // stop resolving to the same factory twice
                if (factoryType == FactoryType.Decorator && Factory.FactoryType != FactoryType.Decorator)
                    decoratedFactoryID = FactoryID;
            }

            // It is required to nullify the transient disposable tracking when factory is resolved multiple times,
            // e.g. for the decorator
            var flags = Flags & ~RequestFlags.TracksTransientDisposable;
            if (skipRecursiveDependencyCheck)
                flags |= RequestFlags.StopRecursiveDependencyCheck;

            var reuse = IsWrappedInFuncWithArgs() && Rules.IgnoringReuseForFuncWithArgs
                ? DryIoc.Reuse.Transient
                : factory.Reuse ?? CalculateDefaultReuse(factory);

            var reuseLifespan = reuse.Lifespan;
            skipCaptiveDependencyCheck = skipCaptiveDependencyCheck
                || reuseLifespan == 0 || !Rules.ThrowIfDependencyHasShorterReuseLifespan || factory.Setup.OpenResolutionScope;

            skipRecursiveDependencyCheck = skipRecursiveDependencyCheck 
                || factoryType != FactoryType.Service;

            // For the dependency we nned to check the recursive and the captive dependency
            for (var parent = DirectParent; 
                !parent.IsEmpty && (!skipCaptiveDependencyCheck || !skipRecursiveDependencyCheck); 
                parent = parent.DirectParent)
            {
                if (!skipCaptiveDependencyCheck)
                {
                    if (parent.OpensResolutionScope ||
                        // ignores the ScopedOrSingleton inside Scoped or Singleton
                        (reuse as CurrentScopeReuse)?.ScopedOrSingleton == true && parent.Reuse is SingletonReuse ||
                        parent.FactoryType == FactoryType.Wrapper && parent.GetActualServiceType().IsFunc())
                        skipCaptiveDependencyCheck = true;
                    else if (
                        parent.FactoryType == FactoryType.Service &&
                        parent.ReuseLifespan > reuseLifespan)
                        Throw.It(Error.DependencyHasShorterReuseLifespan, PrintCurrent(), reuse, parent);
                }

                if (!skipRecursiveDependencyCheck)
                {
                    if ((parent.Flags & RequestFlags.StopRecursiveDependencyCheck) != 0)
                        skipRecursiveDependencyCheck = true; // stops further upward checking
                    else if (parent.FactoryID == factoryId)
                        Throw.It(Error.RecursiveDependencyDetected, Print(factoryId));
                }
            }

            if (reuse == DryIoc.Reuse.Singleton)
            {
                flags |= RequestFlags.IsSingletonOrDependencyOfSingleton;
            }
            else if (reuse == DryIoc.Reuse.Transient) // check for disposable transient
            {
                reuse = GetTransientDisposableTrackingReuse(factory);
                if (reuse != DryIoc.Reuse.Transient)
                    flags |= RequestFlags.TracksTransientDisposable;
            }

            if (copyRequest)
            {
                IsolateRequestChain();
                return new Request(Container, DirectParent, DependencyDepth, null, flags, _serviceInfo,
                    InputArgExprs, null, factory, factoryId, factoryType, reuse, decoratedFactoryID);
            }

            Flags = flags;
            SetResolvedFactory(null, factory, factoryId, factoryType, reuse, decoratedFactoryID);
            return this;
        }

        /// <summary>Check for the parents.</summary>
        public bool HasRecursiveParent(int factoryID)
        {
            for (var parent = DirectParent; !parent.IsEmpty; parent = parent.DirectParent)
            {
                if ((parent.Flags & RequestFlags.StopRecursiveDependencyCheck) != 0)
                    break; // stops further upward checking
                if (parent.FactoryID == factoryID)
                    return true;
            }
            return false;
        }

        private IReuse CalculateDefaultReuse(Factory factory)
        {
            if (factory.Setup.UseParentReuse)
                return GetFirstParentNonTransientReuseUntilFunc();

            if (factory.FactoryType == FactoryType.Decorator)
            {
                if (factory.Setup.To<Setup.DecoratorSetup>().UseDecorateeReuse ||
                    Rules.UseDecorateeReuseForDecorators) 
                    return Reuse; // use reuse of resolved service factory for decorator
            }

            return factory.FactoryType == FactoryType.Wrapper 
                ? DryIoc.Reuse.Transient : Rules.DefaultReuse;
        }

        private IReuse GetTransientDisposableTrackingReuse(Factory factory)
        {
            // Track transient disposable in parent scope (if any), or open scope (if any)
            var setup = factory.Setup;
            var tracksTransientDisposable =
                !setup.PreventDisposal &&
                (setup.TrackDisposableTransient || !setup.AllowDisposableTransient && Rules.TrackingDisposableTransients) &&
                (factory.ImplementationType ?? GetActualServiceType()).IsAssignableTo<IDisposable>();

            if (!tracksTransientDisposable)
                return DryIoc.Reuse.Transient;

            var parentReuse = GetFirstParentNonTransientReuseUntilFunc();
            if (parentReuse != DryIoc.Reuse.Transient)
                return parentReuse;

            if (IsWrappedInFunc())
                return DryIoc.Reuse.Transient;

            // If no parent with reuse found, then track in current open scope or in singletons scope
            return DryIoc.Reuse.ScopedOrSingleton;
        }

        private IReuse GetFirstParentNonTransientReuseUntilFunc()
        {
            for (var parent = DirectParent; parent.DirectParent != null; parent = parent.DirectParent)
            {
                if (parent.FactoryType == FactoryType.Wrapper && parent.GetActualServiceType().IsFunc())
                    break;
                if (parent.FactoryType != FactoryType.Wrapper && parent.Reuse != DryIoc.Reuse.Transient)
                    return parent.Reuse;
            }

            return DryIoc.Reuse.Transient;
        }

        /// <summary>If request corresponds to dependency injected into parameter,
        /// then method calls <paramref name="parameter"/> handling and returns its result.
        /// If request corresponds to property or field, then method calls respective handler.
        /// If request does not correspond to dependency, then calls <paramref name="root"/> handler.</summary>
        public TResult Is<TResult>(
            Func<TResult> root = null,
            Func<ParameterInfo, TResult> parameter = null,
            Func<PropertyInfo, TResult> property = null,
            Func<FieldInfo, TResult> field = null)
        {
            var info = _serviceInfo;
            if (info is ParameterServiceInfo)
            {
                if (parameter != null)
                    return parameter(((ParameterServiceInfo)info).Parameter);
            }
            else if (info is PropertyOrFieldServiceInfo)
            {
                var propertyOrFieldServiceInfo = (PropertyOrFieldServiceInfo)info;
                var propertyInfo = propertyOrFieldServiceInfo.Member as PropertyInfo;
                if (propertyInfo != null)
                {
                    if (property != null)
                        return property(propertyInfo);
                }
                else if (field != null)
                    return field((FieldInfo)propertyOrFieldServiceInfo.Member);
            }
            else if (root != null)
                return root();

            return default(TResult);
        }

        /// <summary>Obsolete: now request is directly implements the <see cref="IEnumerable{T}"/>.</summary>
        public IEnumerable<Request> Enumerate() => this;

        /// <summary>Enumerates self and all request stack parents.</summary>
        public IEnumerator<Request> GetEnumerator()
        {
            for (var r = this; !r.IsEmpty; r = r.DirectParent)
                yield return r;
        }

        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

        /// <summary>Prints current request info only (no parents printed) to provided builder.</summary>
        public StringBuilder PrintCurrent(StringBuilder s = null)
        {
            s = s ?? new StringBuilder();

            if (IsEmpty)
                return s.Append("<empty request>");

            if (IsNestedResolutionCall)
                s.Append("Resolution call dependency ");
            else if (IsResolutionRoot)
                s.Append("Resolution root ");

            if (FactoryID != 0) // request is with resolved factory
            {
                if (Reuse != DryIoc.Reuse.Transient)
                    s.Append(Reuse is SingletonReuse ? "singleton" : "scoped").Append(' ');

                if (FactoryType != FactoryType.Service)
                    s.Append(FactoryType.ToString().ToLower()).Append(' ');

                var implType = ImplementationType;
                if (implType != null && implType != ServiceType)
                    s.Print(implType).Append(": ");
            }

            s.Append(_serviceInfo);

            if (Factory != null && Factory is ReflectionFactory == false)
                s.Append(' ').Append(Factory.GetType().Name).Append(' ');

            if (FactoryID != 0)
                s.Append(" FactoryId=").Append(FactoryID);

            if (DecoratedFactoryID != 0)
                s.Append(" decorating FactoryId=").Append(DecoratedFactoryID);

            if (!InputArgExprs.IsNullOrEmpty())
                s.AppendFormat(" with passed arguments [{0}]", InputArgExprs);

            // todo: exclude IsResolutionCall cause it is printed by above
            if (Flags != default(RequestFlags))
                s.Append(" (").Append(Flags).Append(')');

            return s;
        }

        /// <summary>Prints full stack of requests starting from current one using <see cref="PrintCurrent"/>.</summary>
        public StringBuilder Print(int recursiveFactoryID = 0)
        {
            if (IsEmpty)
                return new StringBuilder("<empty request>");

            var s = PrintCurrent(new StringBuilder());

            s = recursiveFactoryID == 0 ? s : s.Append(" <--recursive");
            foreach (var parent in DirectParent)
            {
                s = parent.PrintCurrent(s.AppendLine().Append("  in "));
                if (parent.FactoryID == recursiveFactoryID)
                    s = s.Append(" <--recursive");
            }

            if (Container != null)
                s.AppendLine().Append("  from ").Append(Container);

            return s;
        }

        /// <summary>Prints whole request chain.</summary>
        public override string ToString() => Print().ToString();

        /// <summary>Returns true if request info and passed object are equal, and their parents recursively are equal.</summary>
        public override bool Equals(object obj) => Equals(obj as Request);

        /// <summary>Returns true if request info and passed info are equal, and their parents recursively are equal.</summary>
        public bool Equals(Request other) =>
            other != null && EqualsWithoutParent(other)
                && (DirectParent == null && other.DirectParent == null
                || (DirectParent != null && DirectParent.EqualsWithoutParent(other.DirectParent)));

        // todo: Should we include InputArgs and DecoratedFactoryID and what about flags? 
        // todo: Should we add and rely on Equals of ServiceInfo and Reuse?
        // todo: The equals calculated differently comparing to HashCode, may be we can use FactoryID for Equals as well?
        /// <summary>Compares self properties but not the parents.</summary>
        public bool EqualsWithoutParent(Request other) =>
            other.ServiceType == ServiceType
            && other.RequiredServiceType == RequiredServiceType
            && other.IfUnresolved == IfUnresolved

            && other.FactoryType == FactoryType
            && other.ImplementationType == ImplementationType

            && Equals(other.ServiceKey, ServiceKey)
            && Equals(other.MetadataKey, MetadataKey)
            && Equals(other.Metadata, Metadata)

            // todo: Move to Reuse?
            && other.Reuse?.GetType() == Reuse?.GetType()
            && other.Reuse?.Lifespan == Reuse?.Lifespan
            && Equals(other.Reuse?.Name, Reuse?.Name);

        /// <summary>Calculates the combined hash code based on factory IDs.</summary>
        public override int GetHashCode() => _hashCode;

        // Initial request without factory info yet
        private Request(IContainer container, Request parent, 
            int dependencyDepth, RequestStack stack, RequestFlags flags, IServiceInfo serviceInfo, Expression[] inputArgExprs)
        {
            DirectParent = parent;
            DependencyDepth = dependencyDepth;
            RequestStack = stack;

            _serviceInfo = serviceInfo;
            _actualServiceType = serviceInfo.GetActualServiceType();

            Flags = flags;

            // runtime state
            InputArgExprs = inputArgExprs;
            Container     = container;
        }

        // Request with resolved factory state
        private Request(IContainer container, 
            Request parent, int dependencyDepth, RequestStack stack,
            RequestFlags flags, IServiceInfo serviceInfo, Expression[] inputArgExprs,
            Type factoryImplType, Factory factory, int factoryID, FactoryType factoryType, IReuse reuse, int decoratedFactoryID)
            : this(container, parent, dependencyDepth, stack, flags, serviceInfo, inputArgExprs)
        {
            SetResolvedFactory(factoryImplType, factory , factoryID, factoryType, reuse, decoratedFactoryID);
        }

        /// Severe the connection with the request pool up to the parent so that noone can change the Request state
        internal Request IsolateRequestChain()
        {
            Request r = null;
            do
            {
                r = r == null ? this : r.DirectParent;
                if (r.RequestStack != null)
                {
                    // severe the requests links with the stack
                    r.RequestStack.Items[r.IndexInStack] = null;
                    r.RequestStack = null;
                }

            } while ((r.Flags & RequestFlags.IsResolutionCall) == 0 && !r.DirectParent.IsEmpty);

            return this;
        }

        private void SetServiceInfo(IContainer container, Request parent, 
            int dependencyDepth, RequestStack stack, RequestFlags flags, IServiceInfo serviceInfo, Expression[] inputArgExprs)
        {
            DirectParent = parent;
            DependencyDepth = dependencyDepth;
            RequestStack = stack;

            _serviceInfo = serviceInfo;
            _actualServiceType = serviceInfo.GetActualServiceType();

            Flags = flags;

            // runtime state
            InputArgExprs = inputArgExprs;
            Container     = container;

            // reset factory info
            SetResolvedFactory(null, null, 0, FactoryType.Service, null, 0);
        }

        private void SetResolvedFactory(Type factoryImplType, 
            Factory factory, int factoryID, FactoryType factoryType, IReuse reuse, int decoratedFactoryID)
        {
            FactoryID = factoryID;
            FactoryType = factoryType;
            Reuse = reuse;
            DecoratedFactoryID = decoratedFactoryID;
            _hashCode = Hasher.Combine(DirectParent?._hashCode ?? 0, FactoryID);

            _factoryImplType = factoryImplType; // should be set from the runtime known `Factory` object
            Factory = factory; // runtime state
        }
    }

    /// <summary>Type of services supported by Container.</summary>
    public enum FactoryType
    {
        /// <summary>(default) Defines normal service factory</summary>
        Service,
        /// <summary>Defines decorator factory</summary>
        Decorator,
        /// <summary>Defines wrapper factory.</summary>
        Wrapper
    };

    /// <summary>Base class to store optional <see cref="Factory"/> settings.</summary>
    public abstract class Setup
    {
        /// <summary>Factory type is required to be specified by concrete setups as in
        /// <see cref="ServiceSetup"/>, <see cref="DecoratorSetup"/>, <see cref="WrapperSetup"/>.</summary>
        public abstract FactoryType FactoryType { get; }

        /// <summary>Predicate to check if factory could be used for resolved request.</summary>
        public Func<Request, bool> Condition { get; }

        /// <summary>Relative disposal order when defined. Greater number, later dispose.</summary>
        public int DisposalOrder { get; }

        /// <summary>Arbitrary metadata object associated with Factory/Implementation, may be a dictionary of key-values.</summary>
        public virtual object Metadata => null;

        /// <summary>Returns true if passed meta key and value match the setup metadata.</summary>
        public bool MatchesMetadata(string metadataKey, object metadata)
        {
            if (metadataKey == null)
                return Equals(metadata, Metadata);

            object metaValue;
            var metaDict = Metadata as IDictionary<string, object>;
            return metaDict != null
                && metaDict.TryGetValue(metadataKey, out metaValue)
                && Equals(metadata, metaValue);
        }

        /// <summary>Indicates that injected expression should be:
        /// <c><![CDATA[r.Resolver.Resolve<IDependency>(...)]]></c>
        /// instead of: <c><![CDATA[new Dependency(...)]]></c></summary>
        public bool AsResolutionCall => (_settings & Settings.AsResolutionCall) != 0;

        /// Setup with the only setting: `AsResolutionCall` 
        internal static readonly Setup AsResolutionCallSetup = 
            new ServiceSetup { _settings = Settings.AsResolutionCall };

        internal Setup WithAsResolutionCall()
        {
            if (AsResolutionCall)
                return this;

            if (this == Default)
                return AsResolutionCallSetup;

            var setupClone = (Setup)MemberwiseClone();
            setupClone._settings |= Settings.AsResolutionCall;
            return setupClone;
        }

        /// <summary>Works as `AsResolutionCall` but only with `Rules.UsedForExpressionGeneration`</summary>
        public bool AsResolutionCallForExpressionGeneration => (_settings & Settings.AsResolutionCallForExpressionGeneration) != 0;

        /// <summary>Specifies to use `asResolutionCall` but only in expression generation context, e.g. for compile-time generation</summary>
        internal static readonly Setup AsResolutionCallForGeneratedExpressionSetup =
            new ServiceSetup { _settings = Settings.AsResolutionCallForExpressionGeneration };

        internal Setup WithAsResolutionCallForGeneratedExpression()
        {
            if (AsResolutionCallForExpressionGeneration)
                return this;

            if (this == Default)
                return AsResolutionCallForGeneratedExpressionSetup;

            var setupClone = (Setup)MemberwiseClone();
            setupClone._settings |= Settings.AsResolutionCallForExpressionGeneration;
            return setupClone;
        }

        /// <summary>Marks service (not a wrapper or decorator) registration that is expected to be resolved via Resolve call.</summary>
        public bool AsResolutionRoot => (_settings & Settings.AsResolutionRoot) != 0;

        /// <summary>Opens scope, also implies <see cref="AsResolutionCall"/>.</summary>
        public bool OpenResolutionScope => (_settings & Settings.OpenResolutionScope) != 0;

        /// <summary>Stores reused instance as WeakReference.</summary>
        public bool WeaklyReferenced => (_settings & Settings.WeaklyReferenced) != 0;

        /// <summary>Allows registering transient disposable.</summary>
        public bool AllowDisposableTransient => (_settings & Settings.AllowDisposableTransient) != 0;

        /// <summary>Turns On tracking of disposable transient dependency in parent scope or in open scope if resolved directly.</summary>
        public bool TrackDisposableTransient => (_settings & Settings.TrackDisposableTransient) != 0;

        /// <summary>Instructs to use parent reuse. Applied only if <see cref="Factory.Reuse"/> is not specified.</summary>
        public bool UseParentReuse => (_settings & Settings.UseParentReuse) != 0;

        /// <summary>Prevents disposal of reused instance if it is disposable.</summary>
        public bool PreventDisposal => (_settings & Settings.PreventDisposal) != 0;

        /// <summary>When single service is resolved, but multiple candidates found, this options will be used to prefer this one.</summary>
        public bool PreferInSingleServiceResolve => (_settings & Settings.PreferInSingleServiceResolve) != 0;

        private Setup() { }

        private Setup(Func<Request, bool> condition,
            bool openResolutionScope, bool asResolutionCall, bool asResolutionRoot, bool preventDisposal, bool weaklyReferenced,
            bool allowDisposableTransient, bool trackDisposableTransient, bool useParentReuse, int disposalOrder, 
            bool preferOverMultipleResolved = false, bool asResolutionCallForExpressionGeneration = false)
        {
            Condition = condition;
            DisposalOrder = disposalOrder;

            if (asResolutionCall)
                _settings |= Settings.AsResolutionCall;
            if (openResolutionScope)
            {
                _settings |= Settings.OpenResolutionScope;
                _settings |= Settings.AsResolutionCall;
            }
            if (preventDisposal)
                _settings |= Settings.PreventDisposal;
            if (weaklyReferenced)
                _settings |= Settings.WeaklyReferenced;
            if (allowDisposableTransient)
                _settings |= Settings.AllowDisposableTransient;
            if (trackDisposableTransient)
            {
                _settings |= Settings.TrackDisposableTransient;
                _settings |= Settings.AllowDisposableTransient;
            }
            if (asResolutionRoot)
                _settings |= Settings.AsResolutionRoot;
            if (useParentReuse)
                _settings |= Settings.UseParentReuse;
            if (preferOverMultipleResolved)
                _settings |= Settings.PreferInSingleServiceResolve;
            if (asResolutionCallForExpressionGeneration)
                _settings |= Settings.AsResolutionCallForExpressionGeneration;
        }

        [Flags]
        private enum Settings
        {
            AsResolutionCall = 1 << 1,
            OpenResolutionScope = 1 << 2,
            PreventDisposal = 1 << 3,
            WeaklyReferenced = 1 << 4,
            AllowDisposableTransient = 1 << 5,
            TrackDisposableTransient = 1 << 6,
            AsResolutionRoot = 1 << 7,
            UseParentReuse = 1 << 8,
            PreferInSingleServiceResolve = 1 << 9,
            AsResolutionCallForExpressionGeneration = 1 << 10
        }

        private Settings _settings; // note: mutable because of setting the AsResolutionCall

        /// <summary>Default setup for service factories.</summary>
        public static readonly Setup Default = new ServiceSetup();

        /// <summary>Constructs setup object out of specified settings.
        /// If all settings are default then <see cref="Default"/> setup will be returned.
        /// <paramref name="metadataOrFuncOfMetadata"/> is metadata object or Func returning metadata object.</summary>
        public static Setup With(
            object metadataOrFuncOfMetadata = null, Func<Request, bool> condition = null,
            bool openResolutionScope = false, bool asResolutionCall = false, bool asResolutionRoot = false,
            bool preventDisposal = false, bool weaklyReferenced = false,
            bool allowDisposableTransient = false, bool trackDisposableTransient = false,
            bool useParentReuse = false, int disposalOrder = 0, bool preferInSingleServiceResolve = false)
        {
            if (metadataOrFuncOfMetadata == null && condition == null &&
                !openResolutionScope && !asResolutionRoot &&
                !preventDisposal && !weaklyReferenced && !allowDisposableTransient && !trackDisposableTransient &&
                !useParentReuse && disposalOrder == 0 && !preferInSingleServiceResolve)
                return !asResolutionCall ? Default : AsResolutionCallSetup;

            return new ServiceSetup(condition,
                metadataOrFuncOfMetadata, openResolutionScope, asResolutionCall, asResolutionRoot,
                preventDisposal, weaklyReferenced, allowDisposableTransient, trackDisposableTransient,
                useParentReuse, disposalOrder, preferInSingleServiceResolve);
        }

        /// <summary>Default setup which will look for wrapped service type as single generic parameter.</summary>
        public static readonly Setup Wrapper = new WrapperSetup();

        // todo: rename to WrapperOf
        /// <summary>Returns generic wrapper setup.
        /// Default for <paramref name="wrappedServiceTypeArgIndex" /> is -1 for generic wrapper with single type argument.
        /// Index need to be set for multiple type arguments. <paramref name="alwaysWrapsRequiredServiceType" /> need to be set 
        /// when generic wrapper type arguments should be ignored.</summary>
        public static Setup WrapperWith(int wrappedServiceTypeArgIndex = -1,
            bool alwaysWrapsRequiredServiceType = false, Func<Type, Type> unwrap = null,
            bool openResolutionScope = false, bool asResolutionCall = false,
            bool preventDisposal = false, bool weaklyReferenced = false,
            bool allowDisposableTransient = false, bool trackDisposableTransient = false,
            bool useParentReuse = false, Func<Request, bool> condition = null, int disposalOrder = 0) =>
                wrappedServiceTypeArgIndex == -1 && !alwaysWrapsRequiredServiceType && unwrap == null &&
                !openResolutionScope && !asResolutionCall && !preventDisposal && !weaklyReferenced &&
                !allowDisposableTransient && !trackDisposableTransient && condition == null && disposalOrder == 0
                    ? Wrapper
                    : new WrapperSetup(wrappedServiceTypeArgIndex, alwaysWrapsRequiredServiceType, unwrap,
                        condition, openResolutionScope, asResolutionCall, preventDisposal, weaklyReferenced,
                        allowDisposableTransient, trackDisposableTransient, useParentReuse, disposalOrder);

        /// <summary>Default decorator setup: decorator is applied to service type it registered with.</summary>
        public static readonly Setup Decorator = new DecoratorSetup();

        // todo: Make decorateeReuse a default?
        /// <summary>Creates setup with optional condition.
        /// The <paramref name="order" /> specifies relative decorator position in decorators chain.
        /// Greater number means further from decoratee - specify negative number to stay closer.
        /// Decorators without order (Order is 0) or with equal order are applied in registration order
        /// - first registered are closer decoratee.</summary>
        public static Setup DecoratorWith(
            Func<Request, bool> condition = null, int order = 0, bool useDecorateeReuse = false,
            bool openResolutionScope = false, bool asResolutionCall = false,
            bool preventDisposal = false, bool weaklyReferenced = false,
            bool allowDisposableTransient = false, bool trackDisposableTransient = false,
            int disposalOrder = 0) =>
            condition == null && order == 0 && !useDecorateeReuse &&
            !openResolutionScope && !asResolutionCall &&
            !preventDisposal && !weaklyReferenced && !allowDisposableTransient && !trackDisposableTransient &&
            disposalOrder == 0
                ? Decorator
                : new DecoratorSetup(condition, order, useDecorateeReuse, openResolutionScope, asResolutionCall,
                    preventDisposal, weaklyReferenced, allowDisposableTransient, trackDisposableTransient, disposalOrder);

        /// Creates a condition for both <paramref name="decorateeType"/>, <paramref name="decorateeServiceKey"/> and additional condition
        public static Func<Request, bool> GetDecorateeCondition(Type decorateeType,
            object decorateeServiceKey = null, Func<Request, bool> condition = null)
        {
            if (decorateeType == null && decorateeServiceKey == null)
                return condition;

            Func<Request, bool> decorateeCondition;
            if (decorateeServiceKey == null)
                decorateeCondition = r => r.GetKnownImplementationOrServiceType().IsAssignableTo(decorateeType);
            else if (decorateeType == null)
                decorateeCondition = r => decorateeServiceKey.Equals(r.ServiceKey);
            else
                decorateeCondition = r => decorateeServiceKey.Equals(r.ServiceKey) &&
                                     r.GetKnownImplementationOrServiceType().IsAssignableTo(decorateeType);
            return condition == null ? decorateeCondition : r => decorateeCondition(r) && condition(r);
        }

        /// <summary>Setup for decorator of type <paramref name="decorateeType"/>.</summary>
        public static Setup DecoratorOf(Type decorateeType = null,
            int order = 0, bool useDecorateeReuse = false, bool openResolutionScope = false, bool asResolutionCall = false,
            bool preventDisposal = false, bool weaklyReferenced = false, bool allowDisposableTransient = false,
            bool trackDisposableTransient = false, int disposalOrder = 0, object decorateeServiceKey = null) =>
            DecoratorWith(GetDecorateeCondition(decorateeType, decorateeServiceKey), order, useDecorateeReuse, openResolutionScope, asResolutionCall,
                preventDisposal, weaklyReferenced, allowDisposableTransient, trackDisposableTransient, disposalOrder);

        /// <summary>Setup for decorator of type <typeparamref name="TDecoratee"/>.</summary>
        public static Setup DecoratorOf<TDecoratee>(
            int order = 0, bool useDecorateeReuse = false, bool openResolutionScope = false, bool asResolutionCall = false,
            bool preventDisposal = false, bool weaklyReferenced = false, bool allowDisposableTransient = false,
            bool trackDisposableTransient = false, int disposalOrder = 0, object decorateeServiceKey = null) =>
            DecoratorOf(typeof(TDecoratee), order, useDecorateeReuse, openResolutionScope, asResolutionCall,
                preventDisposal, weaklyReferenced, allowDisposableTransient, trackDisposableTransient,
                disposalOrder, decorateeServiceKey);

        /// <summary>Service setup.</summary>
        internal sealed class ServiceSetup : Setup
        {
            /// <inheritdoc />
            public override FactoryType FactoryType => FactoryType.Service;

            /// <summary>Evaluates metadata if it specified as Func of object, and replaces Func with its result!.
            /// Otherwise just returns metadata object.</summary>
            /// <remarks>Invocation of Func metadata is Not thread-safe. Please take care of that inside the Func.</remarks>
            public override object Metadata =>
                _metadataOrFuncOfMetadata is Func<object> metaFactory
                    ? (_metadataOrFuncOfMetadata = metaFactory())
                    : _metadataOrFuncOfMetadata;

            /// All settings are set to defaults.
            public ServiceSetup() { }

            /// Specify all the individual settings.
            public ServiceSetup(Func<Request, bool> condition = null, object metadataOrFuncOfMetadata = null,
                bool openResolutionScope = false, bool asResolutionCall = false, bool asResolutionRoot = false,
                bool preventDisposal = false, bool weaklyReferenced = false, bool allowDisposableTransient = false, 
                bool trackDisposableTransient = false, bool useParentReuse = false, int disposalOrder = 0, 
                bool preferOverMultipleResolved = false, bool asResolutionCallForExpressionGeneration = false)
                : base(condition, openResolutionScope, asResolutionCall, asResolutionRoot,
                    preventDisposal, weaklyReferenced, allowDisposableTransient, trackDisposableTransient,
                    useParentReuse, disposalOrder, preferOverMultipleResolved, asResolutionCallForExpressionGeneration)
            {
                _metadataOrFuncOfMetadata = metadataOrFuncOfMetadata;
            }

            private object _metadataOrFuncOfMetadata;
        }

        /// <summary>Setup applied for wrappers.</summary>
        internal sealed class WrapperSetup : Setup
        {
            /// <summary>Returns <see cref="DryIoc.FactoryType.Wrapper"/> type.</summary>
            public override FactoryType FactoryType => FactoryType.Wrapper;

            /// <summary>Delegate to get wrapped type from provided wrapper type.
            /// If wrapper is generic, then wrapped type is usually a generic parameter.</summary>
            public readonly int WrappedServiceTypeArgIndex;

            /// <summary>Per name.</summary>
            public readonly bool AlwaysWrapsRequiredServiceType;

            /// <summary>Delegate returning wrapped type from wrapper type. Overwrites other options.</summary>
            public readonly Func<Type, Type> Unwrap;

            /// <summary>Default setup</summary>
            /// <param name="wrappedServiceTypeArgIndex">Default is -1 for generic wrapper with single type argument.
            /// Need to be set for multiple type arguments.</param>
            public WrapperSetup(int wrappedServiceTypeArgIndex = -1)
            {
                WrappedServiceTypeArgIndex = wrappedServiceTypeArgIndex;
            }

            /// <summary>Returns generic wrapper setup.
            /// Default for <paramref name="wrappedServiceTypeArgIndex" /> is -1 for generic wrapper with single type argument.
            /// Index need to be set for multiple type arguments. <paramref name="alwaysWrapsRequiredServiceType" /> need to be set 
            /// when generic wrapper type arguments should be ignored.</summary>
            public WrapperSetup(int wrappedServiceTypeArgIndex, bool alwaysWrapsRequiredServiceType, Func<Type, Type> unwrap,
                Func<Request, bool> condition,
                bool openResolutionScope, bool asResolutionCall,
                bool preventDisposal, bool weaklyReferenced, bool allowDisposableTransient, bool trackDisposableTransient,
                bool useParentReuse, int disposalOrder)
                : base(condition, openResolutionScope, asResolutionCall, false, preventDisposal, weaklyReferenced,
                    allowDisposableTransient, trackDisposableTransient, useParentReuse, disposalOrder)
            {
                WrappedServiceTypeArgIndex = wrappedServiceTypeArgIndex;
                AlwaysWrapsRequiredServiceType = alwaysWrapsRequiredServiceType;
                Unwrap = unwrap;
            }

            internal void ThrowIfInvalidRegistration(Type serviceType)
            {
                if (AlwaysWrapsRequiredServiceType || Unwrap != null || !serviceType.IsGeneric())
                    return;

                var typeArgCount = serviceType.GetGenericParamsAndArgs().Length;
                var typeArgIndex = WrappedServiceTypeArgIndex;
                Throw.If(typeArgCount > 1 && typeArgIndex == -1,
                    Error.GenericWrapperWithMultipleTypeArgsShouldSpecifyArgIndex, serviceType);

                var index = typeArgIndex != -1 ? typeArgIndex : 0;
                Throw.If(index > typeArgCount - 1,
                    Error.GenericWrapperTypeArgIndexOutOfBounds, serviceType, index);
            }

            /// <summary>Unwraps service type or returns the <paramref name="serviceType"/> as-is.</summary>
            public Type GetWrappedTypeOrNullIfWrapsRequired(Type serviceType)
            {
                if (Unwrap != null)
                    return Unwrap(serviceType);

                if (AlwaysWrapsRequiredServiceType || !serviceType.IsGeneric())
                    return null;

                var typeArgs = serviceType.GetGenericParamsAndArgs();
                var typeArgIndex = WrappedServiceTypeArgIndex;
                serviceType.ThrowIf(typeArgs.Length > 1 && typeArgIndex == -1,
                    Error.GenericWrapperWithMultipleTypeArgsShouldSpecifyArgIndex);

                typeArgIndex = typeArgIndex != -1 ? typeArgIndex : 0;
                serviceType.ThrowIf(typeArgIndex > typeArgs.Length - 1,
                    Error.GenericWrapperTypeArgIndexOutOfBounds, typeArgIndex);

                return typeArgs[typeArgIndex];
            }
        }

        /// <summary>Setup applied to decorators.</summary>
        internal sealed class DecoratorSetup : Setup
        {
            /// <summary>Returns Decorator factory type.</summary>
            public override FactoryType FactoryType => FactoryType.Decorator;

            /// <summary>If provided specifies relative decorator position in decorators chain.
            /// Greater number means further from decoratee - specify negative number to stay closer.
            /// Decorators without order (Order is 0) or with equal order are applied in registration order
            /// - first registered are closer decoratee.</summary>
            public readonly int Order;

            // todo: It does not consider the keys of decorated services,
            // therefore it will be shared between all services in collection
            /// <summary>Instructs to use decorated service reuse. Decorated service may be decorator itself.</summary>
            public readonly bool UseDecorateeReuse;

            /// <summary>Default setup.</summary>
            public DecoratorSetup() { }

            /// <summary>Creates decorator setup with optional condition. <paramref name="condition" /> applied to 
            /// decorated service to find that service is the decorator target. <paramref name="order" /> specifies 
            /// relative decorator position in decorators chain. Greater number means further from decoratee -
            /// specify negative number to stay closer. Decorators without order (Order is 0) or with equal order
            /// are applied in registration order - first registered are closer decoratee.</summary>
            public DecoratorSetup(Func<Request, bool> condition, int order, bool useDecorateeReuse,
                bool openResolutionScope = false, bool asResolutionCall = false,
                bool preventDisposal = false, bool weaklyReferenced = false,
                bool allowDisposableTransient = false, bool trackDisposableTransient = false,
                int disposalOrder = 0)
                : base(condition, openResolutionScope, asResolutionCall, false, preventDisposal, weaklyReferenced,
                    allowDisposableTransient, trackDisposableTransient, false, disposalOrder)
            {
                Order = order;
                UseDecorateeReuse = useDecorateeReuse;
            }
        }
    }

    /// <summary>Facility for creating concrete factories from some template/prototype. Example:
    /// creating closed-generic type reflection factory from registered open-generic prototype factory.</summary>
    public interface IConcreteFactoryGenerator
    {
        /// <summary>Generated factories so far, identified by the service type and key pair.</summary>
        ImHashMap<KV<Type, object>, ReflectionFactory> GeneratedFactories { get; }

        /// <summary>Returns factory per request. May track already generated factories and return one without regenerating.</summary>
        Factory GetGeneratedFactory(Request request, bool ifErrorReturnDefault = false);
    }

    /// Instructs how to deal with factory result expression: 
    public enum FactoryCaching
    {   /// Is up to DryIoc to decide,
        Default = 0,
        /// Prevents DryIoc to set `DoNotCache`.
        PleaseDontSetDoNotCache,
        /// If set, the expression won't be cached 
        DoNotCache
    }

    /// <summary>Base class for different ways to instantiate service:
    /// <list type="bullet">
    /// <item>Through reflection - <see cref="ReflectionFactory"/></item>
    /// <item>Using custom delegate - <see cref="DelegateFactory"/></item>
    /// <item>Using custom expression - <see cref="ExpressionFactory"/></item>
    /// <item>A placeholder for future actual implementation - <see cref="FactoryPlaceholder"/></item>
    /// </list>
    /// For all of the types Factory should provide result as <see cref="Expression"/> and <see cref="FactoryDelegate"/>.
    /// Factories are supposed to be immutable and stateless.
    /// Each created factory has an unique ID set in <see cref="FactoryID"/>.</summary>
    public abstract class Factory
    {
        /// <summary>Get next factory ID in a atomic way.</summary><returns>The ID.</returns>
        public static int GetNextID() => Interlocked.Increment(ref _lastFactoryID);

        /// <summary>Unique factory id generated from static seed.</summary>
        public int FactoryID { get; internal set; }

        /// <summary>Reuse policy for created services.</summary>
        public virtual IReuse Reuse => _reuse;

        /// <summary>Setup may contain different/non-default factory settings.</summary>
        public virtual Setup Setup
        {
            get => _setup;
            internal set { _setup = value ?? Setup.Default; }
        }

        /// <summary>Checks that condition is met for request or there is no condition setup.</summary>
        public bool CheckCondition(Request request) => Setup.Condition?.Invoke(request) != false;

        /// <summary>Shortcut for <see cref="DryIoc.Setup.FactoryType"/>.</summary>
        public FactoryType FactoryType => Setup.FactoryType;

        /// <summary>Non-abstract closed implementation type. May be null if not known beforehand, e.g. in <see cref="DelegateFactory"/>.</summary>
        public virtual Type ImplementationType => null;

        /// <summary>Allow inheritors to define lazy implementation type</summary>
        public virtual bool CanAccessImplementationType => true;

        /// <summary>Indicates that Factory is factory provider and
        /// consumer should call <see cref="IConcreteFactoryGenerator.GetGeneratedFactory"/> to get concrete factory.</summary>
        public virtual IConcreteFactoryGenerator FactoryGenerator => null;

        /// <summary>Registration order.</summary>
        public virtual int RegistrationOrder => FactoryID;

        /// <summary>Settings <b>(if any)</b> to select Constructor/FactoryMethod, Parameters, Properties and Fields.</summary>
        public virtual Made Made => Made.Default;

        /// <summary>The factory inserts the runtime-state into result expression, e.g. delegate or pre-created instance.</summary>
        public virtual bool HasRuntimeState => false;

        /// Indicates how to deal with the result expression
        public FactoryCaching Caching { get; set; }

        /// Instructs to skip caching the factory unless it really wants to do so via `PleaseDontSetDoNotCache`
        public Factory DoNotCache()
        {
            if (Caching != FactoryCaching.PleaseDontSetDoNotCache)
                Caching = FactoryCaching.DoNotCache;
            return this;
        }

        /// <summary>Initializes reuse and setup. Sets the <see cref="FactoryID"/></summary>
        /// <param name="reuse">(optional)</param> <param name="setup">(optional)</param>
        protected Factory(IReuse reuse = null, Setup setup = null)
        {
            FactoryID = GetNextID();
            _reuse = reuse;
            _setup = setup ?? Setup.Default;
        }

        /// <summary>The main factory method to create service expression, e.g. "new Client(new Service())".
        /// If <paramref name="request"/> has <see cref="Request.InputArgExprs"/> specified, they could be used in expression.</summary>
        /// <param name="request">Service request.</param>
        /// <returns>Created expression.</returns>
        public abstract Expression CreateExpressionOrDefault(Request request);

        /// <summary>Returns service expression: either by creating it with <see cref="CreateExpressionOrDefault"/> or taking expression from cache.
        /// Before returning method may transform the expression  by applying <see cref="Reuse"/>, or/and decorators if found any.</summary>
        public virtual Expression GetExpressionOrDefault(Request request)
        {
            request = request.WithResolvedFactory(this);

            // First look for decorators if it is not already a decorator
            var container = request.Container;
            if (FactoryType != FactoryType.Decorator)
            {
                var decoratorExpr = container.GetDecoratorExpressionOrDefault(request);
                if (decoratorExpr != null)
                    return decoratorExpr;
            }

            // Then optimize for already resolved singleton object, otherwise goes normal ApplyReuse route
            var setup = Setup;
            var rules = container.Rules;
            if (rules.EagerCachingSingletonForFasterAccess &&
                request.Reuse is SingletonReuse && !setup.PreventDisposal && !setup.WeaklyReferenced)
            {
                var itemRef = ((Scope)container.SingletonScope)._maps[FactoryID & Scope.MAP_COUNT_SUFFIX_MASK].GetEntryOrDefault(FactoryID);
                if (itemRef != null && itemRef.Value != Scope.NoItem)
                    return Constant(itemRef.Value);
            }

            if ((request.Flags & RequestFlags.IsGeneratedResolutionDependencyExpression) == 0 &&
                !request.OpensResolutionScope && (
                    setup.OpenResolutionScope || 
                    !request.IsResolutionCall && (
                      setup.AsResolutionCall || 
                      setup.AsResolutionCallForExpressionGeneration && rules.UsedForExpressionGeneration ||
                      setup.UseParentReuse ||
                      request.FactoryType == FactoryType.Service && request.DependencyDepth > rules.DependencyDepthToSplitObjectGraph
                    ) &&
                    request.GetActualServiceType() != typeof(void))
                )
                return Resolver.CreateResolutionExpression(request, setup.OpenResolutionScope);

            var mayCache = Caching != FactoryCaching.DoNotCache && 
                FactoryType == FactoryType.Service &&
                !request.IsResolutionRoot &&
                (!request.IsSingletonOrDependencyOfSingleton || rules.UsedForValidation) &&
                !request.IsDirectlyWrappedInFunc() && 
                !request.IsWrappedInFuncWithArgs() &&
                !setup.UseParentReuse &&
                !Made.IsConditional;

            ImMapEntry<Container.Registry.ExpressionCacheSlot> cacheEntry = null;
            if (mayCache)
            {
                var cachedExpr = ((Container)container).GetCachedFactoryExpression(FactoryID, request, out cacheEntry);
                if (cachedExpr != null)
                    return cachedExpr;
            }

            // Creates an object graph expression with all of the dependencies created
            var serviceExpr = CreateExpressionOrDefault(request);
            if (serviceExpr != null)
            {
                if (request.Reuse != DryIoc.Reuse.Transient &&
                    request.GetActualServiceType() != typeof(void) &&
                    // we don't need the reuse expression when we are validating the object graph
                    !rules.UsedForValidation) 
                {
                    var originalServiceExprType = serviceExpr.Type;

                    serviceExpr = ApplyReuse(serviceExpr, request);

                    if (serviceExpr.Type != originalServiceExprType)
                        serviceExpr = Convert(serviceExpr, originalServiceExprType);
                }

                if (mayCache)
                    ((Container)container).CacheFactoryExpression(FactoryID, request, serviceExpr, cacheEntry);
            }
            else Container.TryThrowUnableToResolve(request);
            return serviceExpr;
        }

        /// Applies reuse to created expression, by wrapping passed expression into scoped access
        /// and producing the result expression.
        protected virtual Expression ApplyReuse(Expression serviceExpr, Request request)
        {
            // Optimization: eagerly creates a singleton during the construction of object graph
            if (request.Reuse is SingletonReuse && 
                request.Rules.EagerCachingSingletonForFasterAccess &&
                !request.TracksTransientDisposable &&
                !request.IsWrappedInFunc())
            {
                var container = request.Container;
                var scope = (Scope)container.SingletonScope;
                if (scope.IsDisposed)
                    Throw.It(Error.ScopeIsDisposed, scope.ToString());

                var factoryId = FactoryID;
                ref var map = ref scope._maps[factoryId & Scope.MAP_COUNT_SUFFIX_MASK];

                var m = map;
                if (Interlocked.CompareExchange(ref map, m.AddOrKeep(factoryId, Scope.NoItem), m) != m)
                    Ref.Swap(ref map, factoryId, (x, i) => x.AddOrKeep(i, Scope.NoItem));

                var itemRef = map.GetEntryOrDefault(factoryId);
                if (itemRef.Value == Scope.NoItem)
                {
                    object singleton = null;
                    lock (itemRef)
                    {
                        if (itemRef.Value == Scope.NoItem)
                        {
                            var useFec = container.Rules.UseFastExpressionCompiler;
                            if (!Interpreter.TryInterpretAndUnwrapContainerException(container, serviceExpr, useFec, out singleton))
                                singleton = serviceExpr.CompileToFactoryDelegate(useFec, container.Rules.UseInterpretation)(container);

                            if (Setup.WeaklyReferenced)
                                singleton = new WeakReference(singleton);
                            else if (Setup.PreventDisposal) // todo: we don't need it here because because we just don't need to AddDisposable
                                singleton = new HiddenDisposable(singleton);

                            itemRef.Value = singleton;
                        }
                    }

                    if (singleton is IDisposable disp && disp != this)
                    {
                        if (Setup.DisposalOrder == 0)
                            scope.AddUnorderedDisposable(disp);
                        else
                            scope.AddDisposable(disp, Setup.DisposalOrder);
                    }
                }

                serviceExpr = Constant(itemRef.Value);
            }
            else
            {
                // Wrap service expression in WeakReference or HiddenDisposable
                if (Setup.WeaklyReferenced)
                    serviceExpr = New(typeof(WeakReference).Constructor(typeof(object)), serviceExpr);
                else if (Setup.PreventDisposal) 
                    serviceExpr = New(HiddenDisposable.Ctor, serviceExpr);

                serviceExpr = request.Reuse.Apply(request, serviceExpr);
            }

            // Unwrap WeakReference or HiddenDisposable
            if (Setup.WeaklyReferenced)
            {
                serviceExpr = Call(
                    typeof(ThrowInGeneratedCode).GetTypeInfo().GetDeclaredMethod(nameof(ThrowInGeneratedCode.WeakRefReuseWrapperGCed)),
                    Property(Convert(serviceExpr, typeof(WeakReference)),
                        typeof(WeakReference).Property(nameof(WeakReference.Target))));
            }
            else if (Setup.PreventDisposal)
            {
                serviceExpr = Field(Convert(serviceExpr, typeof(HiddenDisposable)), HiddenDisposable.ValueField);
            }

            return serviceExpr;
        }

        // todo: remove this
        /// [Obsolete("Not need to control on the factory level, the remaining UseInstanceFactory will be removed")] 
        public virtual bool UseInterpretation(Request request) => request.Rules.UseInterpretationForTheFirstResolution;

        /// Creates factory delegate from service expression and returns it.
        public virtual FactoryDelegate GetDelegateOrDefault(Request request) =>
            GetExpressionOrDefault(request)
                ?.CompileToFactoryDelegate(request.Rules.UseFastExpressionCompiler, request.Rules.UseInterpretation);

        internal virtual bool ValidateAndNormalizeRegistration(Type serviceType, object serviceKey, bool isStaticallyChecked, Rules rules)
        {
            if (!isStaticallyChecked)
                serviceType.ThrowIfNull();

            var setup = Setup;
            if (setup.FactoryType == FactoryType.Service)
            {
                // Warn about registering disposable transient
                var reuse = Reuse ?? rules.DefaultReuse;
                if (reuse != DryIoc.Reuse.Transient)
                    return true;

                if (setup.AllowDisposableTransient ||
                    !rules.ThrowOnRegisteringDisposableTransient)
                    return true;

                if (setup.UseParentReuse ||
                    setup.FactoryType == FactoryType.Decorator && ((Setup.DecoratorSetup)setup).UseDecorateeReuse)
                    return true;

                var knownImplOrServiceType = CanAccessImplementationType ? ImplementationType : serviceType;
                if (knownImplOrServiceType.IsAssignableTo<IDisposable>())
                    Throw.It(Error.RegisteredDisposableTransientWontBeDisposedByContainer,
                        serviceType, serviceKey ?? "{no key}", this);
            }
            else if (setup.FactoryType == FactoryType.Wrapper)
            {
                ((Setup.WrapperSetup)setup).ThrowIfInvalidRegistration(serviceType);
            }
            else if (setup.FactoryType == FactoryType.Decorator)
            {
                if (serviceKey != null)
                    Throw.It(Error.DecoratorShouldNotBeRegisteredWithServiceKey, serviceKey);
            }

            return true;
        }

        /// <summary>Returns nice string representation of factory.</summary>
        public override string ToString()
        {
            var s = new StringBuilder().Append("{FactoryID=").Append(FactoryID);
            if (ImplementationType != null)
                s.Append(", ImplType=").Print(ImplementationType);
            if (Reuse != null)
                s.Append(", Reuse=").Print(Reuse);
            if (Setup.FactoryType != Setup.Default.FactoryType)
                s.Append(", FactoryType=").Append(Setup.FactoryType);
            if (Setup.Metadata != null)
                s.Append(", Metadata=").Print(Setup.Metadata);
            if (Setup.Condition != null)
                s.Append(", HasCondition");

            if (Setup.OpenResolutionScope)
                s.Append(", OpensResolutionScope");
            else if (Setup.AsResolutionCall)
                s.Append(", AsResolutionCall");

            return s.Append("}").ToString();
        }

#region Implementation

        internal static int _lastFactoryID;
        private IReuse _reuse;
        private Setup _setup;

#endregion
    }

    /// <summary>Declares delegate to get single factory method or constructor for resolved request.</summary>
    public delegate FactoryMethod FactoryMethodSelector(Request request);

    /// <summary>Specifies how to get parameter info for injected parameter and resolved request</summary>
    public delegate Func<ParameterInfo, ParameterServiceInfo> ParameterSelector(Request request);

    /// <summary>Specifies what properties or fields to inject and how.</summary>
    public delegate IEnumerable<PropertyOrFieldServiceInfo> PropertiesAndFieldsSelector(Request request);

    /// <summary>DSL for specifying <see cref="ParameterSelector"/> injection rules.</summary>
    public static class Parameters
    {
        /// <summary>Returns default service info wrapper for each parameter info.</summary>
        public static ParameterSelector Of = request => ParameterServiceInfo.Of;

        /// <summary>Returns service info which considers each parameter as optional.</summary>
        public static ParameterSelector IfUnresolvedReturnDefault =
            request => pi => ParameterServiceInfo.Of(pi).WithDetails(ServiceDetails.IfUnresolvedReturnDefault);

        /// <summary>Combines source selector with other. Other is used as fallback when source returns null.</summary>
        public static ParameterSelector OverrideWith(this ParameterSelector source, ParameterSelector other) =>
            source == null || source == Of ? other ?? Of
            : other == null || other == Of ? source
            : req => paramInfo => other(req)?.Invoke(paramInfo) ?? source(req)?.Invoke(paramInfo);

        /// <summary>Obsolete: please use <see cref="OverrideWith"/></summary>
        [Obsolete("Replaced with OverrideWith", false)]
        public static ParameterSelector And(this ParameterSelector source, ParameterSelector other) =>
            source.OverrideWith(other);

        /// <summary>Overrides source parameter rules with specific parameter details. 
        /// If it is not your parameter just return null.</summary>
        /// <param name="source">Original parameters rules</param>
        /// <param name="getDetailsOrNull">Should return specific details or null.</param>
        /// <returns>New parameters rules.</returns>
        public static ParameterSelector Details(this ParameterSelector source, Func<Request, ParameterInfo, ServiceDetails> getDetailsOrNull)
        {
            getDetailsOrNull.ThrowIfNull();
            return source.OverrideWith(request => p => getDetailsOrNull(request, p)?.To(ParameterServiceInfo.Of(p).WithDetails));
        }

        /// <summary>Adds to <paramref name="source"/> selector service info for parameter identified by <paramref name="name"/>.</summary>
        /// <param name="source">Original parameters rules.</param> <param name="name">Name to identify parameter.</param>
        /// <param name="requiredServiceType">(optional)</param> <param name="serviceKey">(optional)</param>
        /// <param name="ifUnresolved">(optional) By default throws exception if unresolved.</param>
        /// <param name="defaultValue">(optional) Specifies default value to use when unresolved.</param>
        /// <param name="metadataKey">(optional) Required metadata key</param> <param name="metadata">Required metadata or value.</param>
        /// <returns>New parameters rules.</returns>
        public static ParameterSelector Name(this ParameterSelector source, string name,
            Type requiredServiceType = null, object serviceKey = null,
            IfUnresolved ifUnresolved = IfUnresolved.Throw, object defaultValue = null,
            string metadataKey = null, object metadata = null) =>
            source.Details((r, p) => !p.Name.Equals(name) ? null
                : ServiceDetails.Of(requiredServiceType, serviceKey, ifUnresolved, defaultValue, metadataKey, metadata));

        /// <summary>Specify parameter by name and set custom value to it.</summary>
        public static ParameterSelector Name(this ParameterSelector source,
            string name, Func<Request, ParameterInfo, ServiceDetails> getServiceDetails) =>
            source.Details((r, p) => p.Name.Equals(name) ? getServiceDetails(r, p) : null);

        /// <summary>Specify parameter by name and set custom value to it.</summary>
        public static ParameterSelector Name(this ParameterSelector source,
            string name, Func<Request, object> getCustomValue) =>
             source.Name(name, (r, p) => ServiceDetails.Of(getCustomValue(r)));

        /// <summary>Adds to <paramref name="source"/> selector service info for parameter identified by type <paramref name="parameterType"/>.</summary>
        /// <param name="source">Source selector.</param> <param name="parameterType">The type of the parameter.</param>
        /// <param name="requiredServiceType">(optional)</param> <param name="serviceKey">(optional)</param>
        /// <param name="ifUnresolved">(optional) By default throws exception if unresolved.</param>
        /// <param name="defaultValue">(optional) Specifies default value to use when unresolved.</param>
        /// <param name="metadataKey">(optional) Required metadata key</param> <param name="metadata">Required metadata or value.</param>
        /// <returns>Combined selector.</returns>
        public static ParameterSelector Type(this ParameterSelector source, Type parameterType,
            Type requiredServiceType = null, object serviceKey = null,
            IfUnresolved ifUnresolved = IfUnresolved.Throw, object defaultValue = null,
            string metadataKey = null, object metadata = null) =>
            source.Details((r, p) => !parameterType.IsAssignableTo(p.ParameterType) ? null
                : ServiceDetails.Of(requiredServiceType, serviceKey, ifUnresolved, defaultValue, metadataKey, metadata));

        /// <summary>Adds to <paramref name="source"/> selector service info for parameter identified by type <typeparamref name="T"/>.</summary>
        /// <typeparam name="T">Type of parameter.</typeparam> <param name="source">Source selector.</param>
        /// <param name="requiredServiceType">(optional)</param> <param name="serviceKey">(optional)</param>
        /// <param name="ifUnresolved">(optional) By default throws exception if unresolved.</param>
        /// <param name="defaultValue">(optional) Specifies default value to use when unresolved.</param>
        /// <param name="metadataKey">(optional) Required metadata key</param> <param name="metadata">Required metadata or value.</param>
        /// <returns>Combined selector.</returns>
        public static ParameterSelector Type<T>(this ParameterSelector source,
            Type requiredServiceType = null, object serviceKey = null,
            IfUnresolved ifUnresolved = IfUnresolved.Throw, object defaultValue = null,
            string metadataKey = null, object metadata = null) =>
            source.Type(typeof(T), requiredServiceType, serviceKey, ifUnresolved, defaultValue, metadataKey, metadata);

        /// <summary>Specify parameter by type and set its details.</summary>
        public static ParameterSelector Type<T>(this ParameterSelector source,
            Func<Request, ParameterInfo, ServiceDetails> getServiceDetails) =>
            source.Details((r, p) => p.ParameterType == typeof(T) ? getServiceDetails(r, p) : null);

        /// <summary>Specify parameter by type and set custom value to it.</summary>
        public static ParameterSelector Type<T>(this ParameterSelector source, Func<Request, T> getCustomValue) =>
            source.Type<T>((r, p) => ServiceDetails.Of(getCustomValue(r)));

        /// <summary>Specify parameter by type and set custom value to it.</summary>
        /// <param name="source">Original parameters rules.</param>
        /// <param name="parameterType">The type of the parameter.</param>
        /// <param name="getCustomValue">Custom value provider.</param>
        /// <returns>New parameters rules.</returns>
        public static ParameterSelector Type(this ParameterSelector source,
            Type parameterType, Func<Request, object> getCustomValue) =>
            source.Details((r, p) => p.ParameterType == parameterType ? ServiceDetails.Of(getCustomValue(r)) : null);
    }

    /// <summary>DSL for specifying <see cref="PropertiesAndFieldsSelector"/> injection rules.</summary>
    public static partial class PropertiesAndFields
    {
        /// <summary>Say to not resolve any properties or fields.</summary>
        public static PropertiesAndFieldsSelector Of = request => null;

        /// <summary>Public assignable instance members of any type except object, string, primitives types, and arrays of those.</summary>
        public static PropertiesAndFieldsSelector Auto = All(withNonPublic: false, withPrimitive: false);

        /// <summary>Public, declared, assignable, non-primitive properties.</summary>
        public static PropertiesAndFieldsSelector Properties(
            bool withNonPublic = false, bool withBase = false,
            IfUnresolved ifUnresolved = IfUnresolved.ReturnDefaultIfNotRegistered) =>
            All(withNonPublic: withNonPublic, withPrimitive: false, withFields: false, withBase: withBase, ifUnresolved: ifUnresolved);

        /// <summary>Should return service info for input member (property or field).</summary>
        public delegate PropertyOrFieldServiceInfo GetServiceInfo(MemberInfo member, Request request);

        /// <summary>Generates selector property and field selector with settings specified by parameters.
        /// If all parameters are omitted the return all public not primitive members.</summary>
        public static PropertiesAndFieldsSelector All(
            bool withNonPublic = true,
            bool withPrimitive = true,
            bool withFields = true,
            bool withBase = true,
            IfUnresolved ifUnresolved = IfUnresolved.ReturnDefaultIfNotRegistered,
            GetServiceInfo serviceInfo = null)
        {
            GetServiceInfo info = (m, r) =>
                serviceInfo != null ? serviceInfo(m, r) :
                PropertyOrFieldServiceInfo.Of(m).WithDetails(ServiceDetails.Of(ifUnresolved: ifUnresolved));

            return req =>
            {
                var properties = req.ImplementationType.GetMembers(x => x.DeclaredProperties, includeBase: withBase)
                    .Match(p => p.IsInjectable(withNonPublic, withPrimitive), p => info(p, req));

                if (!withFields)
                    return properties;

                var fields = req.ImplementationType
                    .GetMembers(x => x.DeclaredFields, includeBase: withBase)
                    .Match(f => f.IsInjectable(withNonPublic, withPrimitive), f => info(f, req));

                return properties.Append(fields);
            };
        }

        /// <summary>Combines source properties and fields with other. Other will override the source condition.</summary>
        /// <param name="source">Source selector.</param> <param name="other">Specific other selector to add.</param>
        /// <returns>Combined result selector.</returns>
        public static PropertiesAndFieldsSelector OverrideWith(
            this PropertiesAndFieldsSelector source, PropertiesAndFieldsSelector other)
        {
            return source == null || source == Of ? (other ?? Of)
                : other == null || other == Of ? source
                : r =>
                {
                    var sourceMembers = source(r).ToArrayOrSelf();
                    var otherMembers = other(r).ToArrayOrSelf();
                    return sourceMembers == null || sourceMembers.Length == 0 ? otherMembers
                        : otherMembers == null || otherMembers.Length == 0 ? sourceMembers
                        : otherMembers.Append(
                            sourceMembers.Match(otherMembers, (om, s) => s != null && om.All(o => o == null || !s.Member.Name.Equals(o.Member.Name))));
                };
        }

        /// <summary>Obsolete: please use <see cref="OverrideWith"/></summary>
        [Obsolete("Replaced with OverrideWith", false)]
        public static PropertiesAndFieldsSelector And(
            this PropertiesAndFieldsSelector source, PropertiesAndFieldsSelector other) =>
            source.OverrideWith(other);

        /// <summary>Specifies service details (key, if-unresolved policy, required type) for property/field with the name.</summary>
        /// <param name="source">Original member selector.</param> <param name="name">Member name.</param> <param name="getDetails">Details.</param>
        /// <returns>New selector.</returns>
        public static PropertiesAndFieldsSelector Details(this PropertiesAndFieldsSelector source,
            string name, Func<Request, ServiceDetails> getDetails)
        {
            name.ThrowIfNull();
            getDetails.ThrowIfNull();
            return source.OverrideWith(req =>
            {
                var implType = req.GetKnownImplementationOrServiceType();

                var property = implType
                    .GetMembers(x => x.DeclaredProperties, includeBase: true)
                    .FindFirst(x => x.Name == name);
                if (property != null && property.IsInjectable(true, true))
                    return getDetails(req)?.To(PropertyOrFieldServiceInfo.Of(property).WithDetails).One();

                var field = implType
                    .GetMembers(x => x.DeclaredFields, includeBase: true)
                    .FindFirst(x => x.Name == name);
                if (field != null && field.IsInjectable(true, true))
                    return getDetails(req)?.To(PropertyOrFieldServiceInfo.Of(field).WithDetails).One();

                return Throw.For<IEnumerable<PropertyOrFieldServiceInfo>>(
                    Error.NotFoundSpecifiedWritablePropertyOrField, name, req);
            });
        }

        /// <summary>Adds to <paramref name="source"/> selector service info for property/field identified by <paramref name="name"/>.</summary>
        /// <param name="source">Source selector.</param> <param name="name">Name to identify member.</param>
        /// <param name="requiredServiceType">(optional)</param> <param name="serviceKey">(optional)</param>
        /// <param name="ifUnresolved">(optional) By default returns default value if unresolved.</param>
        /// <param name="defaultValue">(optional) Specifies default value to use when unresolved.</param>
        /// <param name="metadataKey">(optional) Required metadata key</param> <param name="metadata">Required metadata or value.</param>
        /// <returns>Combined selector.</returns>
        public static PropertiesAndFieldsSelector Name(this PropertiesAndFieldsSelector source, string name,
            Type requiredServiceType = null, object serviceKey = null,
            IfUnresolved ifUnresolved = IfUnresolved.ReturnDefault, object defaultValue = null,
            string metadataKey = null, object metadata = null) =>
            source.Details(name, r => ServiceDetails.Of(
                requiredServiceType, serviceKey, ifUnresolved, defaultValue, metadataKey, metadata));

        /// <summary>Specifies custom value for property/field with specific name.</summary>
        public static PropertiesAndFieldsSelector Name(this PropertiesAndFieldsSelector source,
            string name, Func<Request, object> getCustomValue) =>
            source.Details(name, r => ServiceDetails.Of(getCustomValue(r)));

        /// <summary>Returns true if property matches flags provided.</summary>
        /// <param name="property">Property to match</param>
        /// <param name="withNonPublic">Says to include non public properties.</param>
        /// <param name="withPrimitive">Says to include properties of primitive type.</param>
        /// <returns>True if property is matched and false otherwise.</returns>
        public static bool IsInjectable(this PropertyInfo property,
            bool withNonPublic = false, bool withPrimitive = false)
        {
            if (!property.CanWrite || property.IsExplicitlyImplemented())
                return false;

            if (property.IsStatic())
                return false;

            return !property.IsIndexer() && 
                   (withNonPublic || property.GetSetMethodOrNull() != null) && 
                   (withPrimitive || !property.PropertyType.IsPrimitive(orArrayOfPrimitives: true));
        }

        /// <summary>Returns true if field matches flags provided.</summary>
        /// <param name="field">Field to match.</param>
        /// <param name="withNonPublic">Says to include non public fields.</param>
        /// <param name="withPrimitive">Says to include fields of primitive type.</param>
        /// <returns>True if property is matched and false otherwise.</returns>
        public static bool IsInjectable(this FieldInfo field,
            bool withNonPublic = false, bool withPrimitive = false) =>
            !field.IsInitOnly && !field.IsBackingField()
                && (withNonPublic || field.IsPublic)
                && (withPrimitive || !field.FieldType.IsPrimitive(orArrayOfPrimitives: true));
    }

    /// <summary>Reflects on <see cref="ImplementationType"/> constructor parameters and members,
    /// creates expression for each reflected dependency, and composes result service expression.</summary>
    public sealed class ReflectionFactory : Factory
    {
        /// <summary>Non-abstract service implementation type. May be open generic.</summary>
        public override Type ImplementationType
        {
            get
            {
                if (_implementationType == null && _implementationTypeProvider != null)
                    SetKnownImplementationType(_implementationTypeProvider(), Made);
                return _implementationType;
            }
        }

        /// <summary>False for lazy implementation type, to prevent its early materialization.</summary>
        public override bool CanAccessImplementationType =>
            _implementationType != null || _implementationTypeProvider == null;

        /// <summary>Provides closed-generic factory for registered open-generic variant.</summary>
        public override IConcreteFactoryGenerator FactoryGenerator => _factoryGenerator;

        /// <summary>Injection rules set for Constructor/FactoryMethod, Parameters, Properties and Fields.</summary>
        public override Made Made => _made;

        /// <summary>FactoryID of generator (open-generic) factory.</summary>
        public int GeneratorFactoryID { get; private set; }

        /// <summary>Will contain factory ID of generator's factory for generated factory.</summary>
        public override int RegistrationOrder => GeneratorFactoryID != 0 ? GeneratorFactoryID : FactoryID;

        /// <summary>Creates factory providing implementation type, optional reuse and setup.</summary>
        /// <param name="implementationType">(optional) Optional if Made.FactoryMethod is present Non-abstract close or open generic type.</param>
        /// <param name="reuse">(optional)</param> <param name="made">(optional)</param> <param name="setup">(optional)</param>
        public ReflectionFactory(Type implementationType = null, IReuse reuse = null, Made made = null, Setup setup = null)
            : base(reuse, setup)
        {
            _made = made ?? Made.Default;
            SetKnownImplementationType(implementationType, _made);
        }

        /// <summary>Creates factory providing implementation type, optional reuse and setup.</summary>
        /// <param name="implementationTypeProvider">Provider of non-abstract closed or open-generic type.</param>
        /// <param name="reuse">(optional)</param> <param name="made">(optional)</param> <param name="setup">(optional)</param>
        public ReflectionFactory(Func<Type> implementationTypeProvider, IReuse reuse = null, Made made = null, Setup setup = null)
            : base(reuse, setup)
        {
            _made = made ?? Made.Default;
            _implementationTypeProvider = implementationTypeProvider.ThrowIfNull();
        }

        /// <summary>Creates service expression.</summary>
        public override Expression CreateExpressionOrDefault(Request request)
        {
            var container = request.Container;
            var rules = container.Rules;

            FactoryMethod factoryMethod;
            var factoryMethodSelector = Made.FactoryMethod ?? rules.FactoryMethod;
            if (factoryMethodSelector == null)
            {
                factoryMethod = new FactoryMethod(_knownSingleCtor ?? request.ImplementationType.SingleConstructor());
            }
            else if ((factoryMethod = factoryMethodSelector(request)) == null)
                return Throw.For<Expression>(request.IfUnresolved != IfUnresolved.ReturnDefault,
                    Error.UnableToSelectCtor, request.ImplementationType, request);

            // If factory method is the method of some registered service, then resolve factory service first.
            var factoryExpr = factoryMethod.FactoryExpression;
            if (factoryExpr == null && factoryMethod.FactoryServiceInfo != null)
            {
                var factoryRequest = request.Push(factoryMethod.FactoryServiceInfo);
                factoryExpr = container.ResolveFactory(factoryRequest)?.GetExpressionOrDefault(factoryRequest);
                if (factoryExpr == null)
                    return null;
            }

            var ctorOrMember = factoryMethod.ConstructorOrMethodOrMember;
            var ctorOrMethod = ctorOrMember as MethodBase;
            if (ctorOrMethod == null) // return early when factory is Property or Field
            {
                var memberExpr = ctorOrMember is PropertyInfo 
                    ? Property(factoryExpr, (PropertyInfo)ctorOrMember)
                    : (Expression)Field(factoryExpr, (FieldInfo)ctorOrMember);
                return ConvertExpressionIfNeeded(memberExpr, request, ctorOrMember);
            }

            Expression arg0 = null, arg1 = null, arg2 = null, arg3 = null, arg4 = null;
            Expression[] paramExprs = Empty<Expression>();
            var parameters = ctorOrMethod.GetParameters();
            if (parameters.Length != 0)
            {
                paramExprs = factoryMethod.ResolvedParameterExpressions;
                if (paramExprs == null)
                {
                    if (parameters.Length > 5)
                        paramExprs = new Expression[parameters.Length];
                 
                    var paramSelector = rules.OverrideRegistrationMade
                        ? Made.Parameters.OverrideWith(rules.Parameters)
                        : rules.Parameters.OverrideWith(Made.Parameters);
                    var paramServiceInfoSelector = paramSelector(request);

                    var inputArgs = request.InputArgExprs;
                    var argsUsedMask = 0;
                    for (var i = 0; i < parameters.Length; i++)
                    {
                        var param = parameters[i];
                        if (inputArgs != null)
                        {
                            var inputArgExpr = TryGetExpressionFromInputArgs(param.ParameterType, inputArgs, ref argsUsedMask);
                            if (inputArgExpr != null)
                            {
                                if (paramExprs != null)
                                    paramExprs[i] = inputArgExpr;
                                else if (i == 0)
                                    arg0 = inputArgExpr;
                                else if (i == 1)
                                    arg1 = inputArgExpr;
                                else if (i == 2)
                                    arg2 = inputArgExpr;
                                else if (i == 3)
                                    arg3 = inputArgExpr;
                                else
                                    arg4 = inputArgExpr;
                                continue;
                            }
                        }

                        var paramInfo = paramServiceInfoSelector(param) ?? ParameterServiceInfo.Of(param);
                        var paramRequest = request.Push(paramInfo);
                        var paramDetails = paramInfo.Details;
                        var usedOrCustomValExpr = TryGetUsedInstanceOrCustomValueExpression(request, paramRequest, paramDetails);
                        if (usedOrCustomValExpr != null)
                        {
                            if (paramExprs != null)
                                paramExprs[i] = usedOrCustomValExpr;
                            else if (i == 0)
                                arg0 = usedOrCustomValExpr;
                            else if (i == 1)
                                arg1 = usedOrCustomValExpr;
                            else if (i == 2)
                                arg2 = usedOrCustomValExpr;
                            else if (i == 3)
                                arg3 = usedOrCustomValExpr;
                            else
                                arg4 = usedOrCustomValExpr;
                            continue;
                        }

                        var injectedExpr = container.ResolveFactory(paramRequest)?.GetExpressionOrDefault(paramRequest);
                        if (injectedExpr == null ||
                            // When param is an empty array / collection, then we may use a default value instead (#581)
                            paramDetails.DefaultValue != null &&
                            injectedExpr.NodeType == System.Linq.Expressions.ExpressionType.NewArrayInit &&
                            ((NewArrayExpression) injectedExpr).Expressions.Count == 0)
                        {
                            // Check if parameter dependency itself (without propagated parent details)
                            // does not allow default, then stop checking the rest of parameters.
                            if (paramDetails.IfUnresolved == IfUnresolved.Throw)
                                return null;
                            injectedExpr = paramDetails.DefaultValue != null
                                ? container.GetConstantExpression(paramDetails.DefaultValue)
                                : paramRequest.ServiceType.GetDefaultValueExpression();
                        }

                        if (paramExprs != null)
                            paramExprs[i] = injectedExpr;
                        else if (i == 0)
                            arg0 = injectedExpr;
                        else if (i == 1)
                            arg1 = injectedExpr;
                        else if (i == 2)
                            arg2 = injectedExpr;
                        else if (i == 3)
                            arg3 = injectedExpr;
                        else
                            arg4 = injectedExpr;
                    }
                }
            }

            if (rules.UsedForValidation) 
                return request.GetActualServiceType().GetDefaultValueExpression();

            var ctor = ctorOrMethod as ConstructorInfo;
            Expression serviceExpr;
            if (arg0 == null)
                serviceExpr = ctor != null ? New(ctor, paramExprs) : (Expression)Call(factoryExpr, (MethodInfo)ctorOrMethod, paramExprs);
            else if (arg1 == null)
                serviceExpr = ctor != null ? New(ctor, arg0) : (Expression)Call(factoryExpr, (MethodInfo)ctorOrMethod, arg0);
            else if (arg2 == null)
                serviceExpr = ctor != null ? New(ctor, arg0, arg1) : (Expression)Call(factoryExpr, (MethodInfo)ctorOrMethod, arg0, arg1);
            else if (arg3 == null)
                serviceExpr = ctor != null ? New(ctor, arg0, arg1, arg2) : (Expression)Call(factoryExpr, (MethodInfo)ctorOrMethod, arg0, arg1, arg2);
            else if (arg4 == null)
                serviceExpr = ctor != null ? New(ctor, arg0, arg1, arg2, arg3) : (Expression)Call(factoryExpr, (MethodInfo)ctorOrMethod, arg0, arg1, arg2, arg3);
            else
                serviceExpr = ctor != null ? New(ctor, arg0, arg1, arg2, arg3, arg4) : (Expression)Call(factoryExpr, (MethodInfo)ctorOrMethod, arg0, arg1, arg2, arg3, arg4);

            if (ctor == null)
                return ConvertExpressionIfNeeded(serviceExpr, request, ctorOrMember);

            if (rules.PropertiesAndFields == null && Made.PropertiesAndFields == null)
                return serviceExpr;

            return CreateMemberInitExpression(serviceExpr, request);
        }

        private Expression CreateMemberInitExpression(Expression serviceExpr, Request request)
        {
            var container = request.Container;
            var rules = container.Rules;
            var propertiesAndFieldsSelector = rules.OverrideRegistrationMade
                ? Made.PropertiesAndFields.OverrideWith(rules.PropertiesAndFields)
                : rules.PropertiesAndFields.OverrideWith(Made.PropertiesAndFields);
            var propertiesAndFields = propertiesAndFieldsSelector.Invoke(request);
            if (propertiesAndFields == null)
                return serviceExpr;

            var assignments = Empty<MemberAssignment>();
            foreach (var member in propertiesAndFields)
                if (member != null)
                {
                    var memberRequest = request.Push(member);
                    var memberExpr =
                        TryGetUsedInstanceOrCustomValueExpression(request, memberRequest, member.Details)
                        ?? container.ResolveFactory(memberRequest)?.GetExpressionOrDefault(memberRequest);
                    if (memberExpr != null)
                        assignments = assignments.AppendOrUpdate(Bind(member.Member, memberExpr));
                    else if (request.IfUnresolved == IfUnresolved.ReturnDefault)
                        return null;
                }

            return assignments.Length == 0 ? serviceExpr : MemberInit((NewExpression) serviceExpr, assignments);
        }

        private static Expression ConvertExpressionIfNeeded(Expression serviceExpr, Request request, MemberInfo ctorOrMember)
        {
            var actualServiceType = request.GetActualServiceType();
            var serviceExprType = serviceExpr.Type;
            if (serviceExprType == typeof(object))
                return Convert(serviceExpr, actualServiceType);
            return serviceExprType == actualServiceType || serviceExprType.IsAssignableTo(actualServiceType) ? serviceExpr
                : serviceExprType.HasConversionOperatorTo(actualServiceType) ? Convert(serviceExpr, actualServiceType)
                : request.IfUnresolved != IfUnresolved.Throw ? null
                : Throw.For<Expression>(Error.ServiceIsNotAssignableFromFactoryMethod, actualServiceType, ctorOrMember,
                    request);
        }

        // Check not yet used arguments provided via `Func<Arg, TService>` or `Resolve(.., args: new[] { arg })`
        internal static Expression TryGetExpressionFromInputArgs(Type paramType, Expression[] inputArgs, ref int argsUsedMask)
        {
            for (var a = 0; a < inputArgs.Length; ++a)
                if ((argsUsedMask & 1 << a) == 0 && inputArgs[a].Type.IsAssignableTo(paramType))
                {
                    argsUsedMask |= 1 << a; // mark that argument was used
                    return inputArgs[a];
                }
            return null;
        }

        internal static Expression TryGetUsedInstanceOrCustomValueExpression(Request request, Request paramRequest, ServiceDetails paramDetails)
        {
            if (paramDetails == DryIoc.ServiceDetails.Default)
            {
                // Generate the fast resolve call for used instances
                if (request.Container.TryGetUsedInstance(paramRequest.ServiceType, out var instance))
                    return Call(ResolverContext.GetRootOrSelfExpr(paramRequest), Resolver.ResolveFastMethod,
                        Constant(paramRequest.ServiceType, typeof(Type)), Constant(paramRequest.IfUnresolved));
                return null;
            }

            if (paramDetails.HasCustomValue)
            {
                var serviceType = paramRequest.ServiceType;
                var hasConversionOperator = false;
                var customValue = paramDetails.CustomValue;
                if (customValue != null)
                {
                    var customTypeValue = customValue.GetType();
                    if (!customTypeValue.IsArray && 
                        !customTypeValue.IsAssignableTo(serviceType) && 
                        !(hasConversionOperator = customTypeValue.HasConversionOperatorTo(serviceType)))
                        return Throw.For<Expression>(paramRequest.IfUnresolved != IfUnresolved.ReturnDefault,
                            Error.InjectedCustomValueIsOfDifferentType, customValue, serviceType, paramRequest);
                }

                return hasConversionOperator
                    ? Convert(request.Container.GetConstantExpression(customValue), serviceType)
                    : request.Container.GetConstantExpression(customValue, serviceType);
            }

            return null;
        }

        internal override bool ValidateAndNormalizeRegistration(Type serviceType, object serviceKey, bool isStaticallyChecked, Rules rules)
        {
            base.ValidateAndNormalizeRegistration(serviceType, serviceKey, isStaticallyChecked, rules);

            if (!CanAccessImplementationType)
                return true;

            var implType = ImplementationType;
            if (Made.FactoryMethod == null && rules.FactoryMethod == null)
            {
                var ctors = implType.GetTypeInfo().DeclaredConstructors.ToArrayOrSelf();
                var ctorCount = 0;
                for (var i = 0; ctorCount != 2 && i < ctors.Length; i++)
                {
                    var ctor = ctors[i];
                    if (ctor.IsPublic && !ctor.IsStatic)
                    {
                        ++ctorCount;
                        _knownSingleCtor = ctor;
                    }
                }

                if (ctorCount == 0)
                    Throw.It(Error.UnableToSelectSinglePublicConstructorFromNone, implType);
                else if (ctorCount > 1)
                    Throw.It(Error.UnableToSelectSinglePublicConstructorFromMultiple, implType, ctors);
            }

            if (isStaticallyChecked || implType == null)
                return true;

            var implTypeInfo = implType.GetTypeInfo();
            if (!implTypeInfo.IsGenericTypeDefinition)
            {
                if (implTypeInfo.IsGenericType && !ReflectionTools.AreAllTypeArgumentsClosed(implTypeInfo.GetGenericParamsAndArgsUnsafe()))
                    Throw.It(Error.RegisteringNotAGenericTypedefImplType, implType, implType.GetGenericTypeDefinition());

                else if (implType != serviceType && serviceType != typeof(object))
                {
                    if (!serviceType.IsGenericDefinition())
                    {
                        if (!implType.IsImplementingServiceType(serviceType))
                            Throw.It(Error.RegisteringImplementationNotAssignableToServiceType, implType, serviceType);
                    }
                    else
                    {
                        if (implType.GetImplementedTypes().IndexOf(serviceType, (st, t) => t == st || t.GetGenericDefinitionOrNull() == st) == -1)
                            Throw.It(Error.RegisteringImplementationNotAssignableToServiceType, implType, serviceType);
                    }
                }
            }
            else if (implType != serviceType) // implTypeInfo.IsGenericTypeDefinition
            {
                var serviceTypeInfo = serviceType.GetTypeInfo();
                if (serviceTypeInfo.IsGenericTypeDefinition)
                {
                    var implTypeParams = implTypeInfo.GetGenericParamsAndArgsUnsafe();
                    var implementedTypes = implType.GetImplementedTypes();

                    var implementedTypeFound = false;
                    var containsAllTypeParams = false;
                    for (var i = 0; !containsAllTypeParams && i < implementedTypes.Length; ++i)
                    {
                        var implementedType = implementedTypes[i];
                        implementedTypeFound = implementedType.GetGenericDefinitionOrNull() == serviceType;
                        containsAllTypeParams = implementedTypeFound && implementedType.ContainsAllGenericTypeParameters(implTypeParams);
                    }

                    if (!implementedTypeFound)
                        Throw.It(Error.RegisteringImplementationNotAssignableToServiceType, implType, serviceType);

                    if (!containsAllTypeParams)
                        Throw.It(Error.RegisteringOpenGenericServiceWithMissingTypeArgs,
                            implType, serviceType, implementedTypes.Match(t => t.GetGenericDefinitionOrNull() == serviceType));
                }

                else if (serviceTypeInfo.IsGenericType && !ReflectionTools.AreAllTypeArgumentsClosed(serviceTypeInfo.GetGenericParamsAndArgsUnsafe()))
                    Throw.It(Error.RegisteringNotAGenericTypedefServiceType,
                        serviceType, serviceType.GetGenericTypeDefinition());

                else if (!serviceTypeInfo.IsGenericType)
                    Throw.It(Error.RegisteringOpenGenericImplWithNonGenericService, implType, serviceType);

                else if (!implType.IsImplementingServiceType(serviceType.GetGenericTypeDefinition()))
                    Throw.It(Error.RegisteringImplementationNotAssignableToServiceType, implType, serviceType);
            }

            return true;
        }

#region Implementation

        private Type _implementationType; // non-readonly to be set by lazy type provider
        private readonly Func<Type> _implementationTypeProvider;
        private readonly Made _made;
        private ClosedGenericFactoryGenerator _factoryGenerator;
        private ConstructorInfo _knownSingleCtor;

        private sealed class ClosedGenericFactoryGenerator : IConcreteFactoryGenerator
        {
            public ImHashMap<KV<Type, object>, ReflectionFactory> GeneratedFactories => _generatedFactories.Value;

            public ClosedGenericFactoryGenerator(ReflectionFactory openGenericFactory) => _openGenericFactory = openGenericFactory;

            public Factory GetGeneratedFactory(Request request, bool ifErrorReturnDefault = false)
            {
                var openFactory = _openGenericFactory;
                var implType = openFactory._implementationType;
                var serviceType = request.GetActualServiceType();
                var serviceTypeInfo = serviceType.GetTypeInfo();

                Type[] closedTypeArgs;
                if (implType == null || serviceTypeInfo.IsGenericType && serviceType.GetGenericTypeDefinition() == implType)
                    closedTypeArgs = serviceTypeInfo.GetGenericParamsAndArgsUnsafe();
                else if (implType.IsGenericParameter)
                    closedTypeArgs = serviceType.One();
                else
                {
                    var implTypeInfo = implType.GetTypeInfo();
                    var implTypeParams = implTypeInfo.GetGenericParamsAndArgsUnsafe();

                    closedTypeArgs = new Type[implTypeParams.Length];
                    var implementedTypes = implType.GetImplementedTypes();

                    var serviceTypeArgs = serviceTypeInfo.GetGenericParamsAndArgsUnsafe();

                    var matchFound = false;
                    for (var i = 0; !matchFound && i < implementedTypes.Length; ++i)
                    {
                        var implementedType = implementedTypes[i];
                        var implementedTypeInfo = implementedType.GetTypeInfo();
                        if (implementedTypeInfo.IsGenericType && 
                            implementedType.GetGenericTypeDefinition() == serviceType.GetGenericTypeDefinition())
                        {
                            var implementedTypeGenericParams = implementedTypeInfo.GetGenericParamsAndArgsUnsafe();
                            if (!ReflectionTools.AreAllTypeArgumentsClosed(implementedTypeGenericParams))
                                matchFound = MatchServiceWithImplementedTypeParams(
                                    closedTypeArgs, implTypeParams, implementedTypeGenericParams, serviceTypeArgs);
                        }
                    }

                    if (!matchFound)
                        return ifErrorReturnDefault || request.IfUnresolved != IfUnresolved.Throw ? null : 
                            Throw.For<Factory>(Error.NoMatchedImplementedTypesWithServiceType, implType, implementedTypes, request);

                    MatchOpenGenericConstraints(implTypeParams, closedTypeArgs);

                    for (var i = 0; i < closedTypeArgs.Length; i++)
                        if (closedTypeArgs[i] == null)
                            return ifErrorReturnDefault || request.IfUnresolved != IfUnresolved.Throw ? null : 
                                Throw.For<Factory>(Error.NotFoundOpenGenericImplTypeArgInService,
                                    implType, implTypeParams[i], request);
                }

                var made = openFactory.Made;
                if (made.FactoryMethod != null)
                {
                    // resolve request with factory to specify the implementation type may be required by FactoryMethod or GetClosed...
                    request = request.WithResolvedFactory(openFactory, ifErrorReturnDefault, ifErrorReturnDefault, copyRequest: true);
                    var factoryMethod = made.FactoryMethod(request);
                    if (factoryMethod == null)
                        return ifErrorReturnDefault ? null : // todo: should we check ifUnresolved here,
                            Throw.For<Factory>(Error.GotNullFactoryWhenResolvingService, request);

                    var checkMatchingType = implType != null && implType.IsGenericParameter;
                    var closedFactoryMethod = GetClosedFactoryMethodOrDefault(factoryMethod, closedTypeArgs, request, checkMatchingType);
                    if (closedFactoryMethod == null) // may be null only for `IfUnresolved.ReturnDefault` or if the check for matching type is failed
                        return null;

                    made = Made.Of(closedFactoryMethod, made.Parameters, made.PropertiesAndFields);
                }

                if (implType != null)
                {
                    implType = implType.IsGenericParameter ? closedTypeArgs[0] : 
                        Throw.IfThrows<ArgumentException, Type>(() => implType.MakeGenericType(closedTypeArgs), 
                            !ifErrorReturnDefault && request.IfUnresolved == IfUnresolved.Throw,
                            Error.NoMatchedGenericParamConstraints, implType, request);
                    if (implType == null)
                        return null;
                }

                var knownImplOrServiceType = implType ?? made.FactoryMethodKnownResultType ?? serviceType;
                var serviceKey = request.ServiceKey;
                serviceKey = (serviceKey as OpenGenericTypeKey)?.ServiceKey ?? serviceKey;
                var generatedFactoryKey = KV.Of(knownImplOrServiceType, serviceKey);

                var generatedFactories = _generatedFactories.Value;
                if (!generatedFactories.IsEmpty)
                {
                    var generatedFactory = generatedFactories.GetValueOrDefault(generatedFactoryKey);
                    if (generatedFactory != null)
                        return generatedFactory;
                }

                var closedGenericFactory = new ReflectionFactory(implType, openFactory.Reuse, made, openFactory.Setup)
                {
                    GeneratorFactoryID = openFactory.FactoryID,
                    Caching = openFactory.Caching
                };

                // we should use whatever the first factory is registered because it can be used already in decorators and recursive factories check
                _generatedFactories.Swap(generatedFactoryKey, closedGenericFactory,
                    (x, genFacKey, fac) => x.AddOrUpdate(genFacKey, fac, (oldFac, _) => closedGenericFactory = oldFac));

                return closedGenericFactory;
            }

            private readonly ReflectionFactory _openGenericFactory;
            private readonly Ref<ImHashMap<KV<Type, object>, ReflectionFactory>>
                _generatedFactories = Ref.Of(ImHashMap<KV<Type, object>, ReflectionFactory>.Empty);
        }

        private void SetKnownImplementationType(Type implType, Made made)
        {
            var knownImplType = implType;

            var factoryMethodResultType = Made.FactoryMethodKnownResultType;
            if (implType == null ||
                implType == typeof(object) || // required as currently object represents the open-generic type argument T registrations
                implType.IsAbstract())
            {
                if (made.FactoryMethod == null)
                {
                    if (implType == null)
                        Throw.It(Error.RegisteringNullImplementationTypeAndNoFactoryMethod);
                    if (implType == typeof(object))
                        Throw.It(Error.RegisteringObjectTypeAsImplementationIsNotSupported);
                    if (implType.IsAbstract())
                        Throw.It(Error.RegisteringAbstractImplementationTypeAndNoFactoryMethod, implType);
                }

                knownImplType = null; // Ensure that we do not have abstract implementation type

                // Using non-abstract factory method result type is safe for conditions and diagnostics
                if (factoryMethodResultType != null &&
                    factoryMethodResultType != typeof(object) &&
                    !factoryMethodResultType.IsAbstract())
                    knownImplType = factoryMethodResultType;
            }
            else if (factoryMethodResultType != null
                  && factoryMethodResultType != implType)
            {
                if (!factoryMethodResultType.IsAssignableTo(implType) &&
                    !factoryMethodResultType.HasConversionOperatorTo(implType))
                    Throw.It(Error.RegisteredFactoryMethodResultTypesIsNotAssignableToImplementationType,
                        implType, factoryMethodResultType);
            }

            var openGenericImplType = knownImplType ?? implType;
            if (openGenericImplType == typeof(object) || // for open-generic T implementation
                openGenericImplType != null &&           // for open-generic X<T> implementation
                (openGenericImplType.IsGenericDefinition() || openGenericImplType.IsGenericParameter) ||
                made.IsConditionalImplementation)
            {
                _factoryGenerator = new ClosedGenericFactoryGenerator(this);
            }

            _implementationType = knownImplType;
        }

        private static void MatchOpenGenericConstraints(Type[] implTypeParams, Type[] implTypeArgs)
        {
            for (var i = 0; i < implTypeParams.Length; i++)
            {
                var implTypeArg = implTypeArgs[i];
                if (implTypeArg == null)
                    continue; // skip yet unknown type arg

                var implTypeParamConstraints = implTypeParams[i].GetGenericParamConstraints();
                if (implTypeParamConstraints.IsNullOrEmpty())
                    continue; // skip case with no constraints

                // match type parameters inside constraint
                var constraintMatchFound = false;
                for (var j = 0; !constraintMatchFound && j < implTypeParamConstraints.Length; ++j)
                {
                    var implTypeParamConstraint = implTypeParamConstraints[j];
                    if (implTypeParamConstraint != implTypeArg && implTypeParamConstraint.IsOpenGeneric())
                    {
                        var implTypeArgArgs = implTypeArg.IsGeneric() ? implTypeArg.GetGenericParamsAndArgs() : implTypeArg.One();
                        var implTypeParamConstraintParams = implTypeParamConstraint.GetGenericParamsAndArgs();

                        constraintMatchFound = MatchServiceWithImplementedTypeParams(
                            implTypeArgs, implTypeParams, implTypeParamConstraintParams, implTypeArgArgs);
                    }
                }
            }
        }

        private static bool MatchServiceWithImplementedTypeParams(
            Type[] resultImplArgs, Type[] implParams, Type[] serviceParams, Type[] serviceArgs,
            int resultCount = 0)
        {
            if (serviceArgs.Length != serviceParams.Length)
                return false;

            for (var i = 0; i < serviceParams.Length; i++)
            {
                var serviceArg = serviceArgs[i];
                var implementedParam = serviceParams[i];
                if (implementedParam.IsGenericParameter)
                {
                    var paramIndex = implParams.Length - 1;
                    while (paramIndex != -1 && !ReferenceEquals(implParams[paramIndex], implementedParam))
                        --paramIndex;
                    if (paramIndex != -1)
                    {
                        if (resultImplArgs[paramIndex] == null)
                            resultImplArgs[paramIndex] = serviceArg;
                        else if (resultImplArgs[paramIndex] != serviceArg)
                            return false; // more than one service type arg is matching with single implementation type parameter
                    }
                }
                else if (implementedParam != serviceArg)
                {
                    if (!implementedParam.IsOpenGeneric() ||
                        implementedParam.GetGenericDefinitionOrNull() != serviceArg.GetGenericDefinitionOrNull())
                        return false; // type parameter and argument are of different types

                    if (!MatchServiceWithImplementedTypeParams(resultImplArgs, implParams,
                        implementedParam.GetGenericParamsAndArgs(), serviceArg.GetGenericParamsAndArgs()))
                        return false; // nested match failed due one of above reasons.
                }
            }

            return true;
        }

        private static FactoryMethod GetClosedFactoryMethodOrDefault(
            FactoryMethod factoryMethod, Type[] serviceTypeArgs, Request request, bool ifErrorReturnDefault = false)
        {
            var factoryMember = factoryMethod.ConstructorOrMethodOrMember;
            var factoryInfo = factoryMethod.FactoryServiceInfo;

            var resultType = factoryMember.GetReturnTypeOrDefault();
            var implTypeParams = resultType.IsGenericParameter ? resultType.One() : resultType.GetGenericParamsAndArgs();

            // Get method declaring type, and if its open-generic,
            // then close it first. It is required to get actual method.
            var factoryImplType = factoryMember.DeclaringType.ThrowIfNull();
            if (factoryImplType.IsOpenGeneric())
            {
                var factoryImplTypeParams = factoryImplType.GetGenericParamsAndArgs();
                var resultFactoryImplTypeArgs = new Type[factoryImplTypeParams.Length];

                var isFactoryImplTypeClosed = MatchServiceWithImplementedTypeParams(
                    resultFactoryImplTypeArgs, factoryImplTypeParams, implTypeParams, serviceTypeArgs);

                if (!isFactoryImplTypeClosed)
                    return ifErrorReturnDefault || request.IfUnresolved != IfUnresolved.Throw ? null
                        : Throw.For<FactoryMethod>(Error.NoMatchedFactoryMethodDeclaringTypeWithServiceTypeArgs,
                            factoryImplType, new StringBuilder().Print(serviceTypeArgs, itemSeparator: ", "), request);

                // For instance factory match its service type from the implementation factory type.
                if (factoryInfo != null)
                {
                    // Look for service type equivalent within factory implementation type base classes and interfaces,
                    // because we need identical type arguments to match.
                    var factoryServiceType = factoryInfo.ServiceType;
                    if (factoryServiceType != factoryImplType)
                        factoryServiceType = factoryImplType.GetImplementedTypes()
                            .FindFirst(factoryServiceType, (fServiceType, t) => t.IsGeneric() && t.GetGenericTypeDefinition() == fServiceType)
                            .ThrowIfNull();

                    var factoryServiceTypeParams = factoryServiceType.GetGenericParamsAndArgs();
                    var resultFactoryServiceTypeArgs = new Type[factoryServiceTypeParams.Length];

                    var isFactoryServiceTypeClosed = MatchServiceWithImplementedTypeParams(
                        resultFactoryServiceTypeArgs, factoryServiceTypeParams, factoryImplTypeParams, resultFactoryImplTypeArgs);

                    // Replace factory info with close factory service type
                    if (isFactoryServiceTypeClosed)
                    {
                        factoryServiceType = factoryServiceType.GetGenericTypeDefinition().ThrowIfNull();
                        var closedFactoryServiceType = Throw.IfThrows<ArgumentException, Type>(
                            () => factoryServiceType.MakeGenericType(resultFactoryServiceTypeArgs),
                            !ifErrorReturnDefault && request.IfUnresolved == IfUnresolved.Throw,
                            Error.NoMatchedGenericParamConstraints, factoryServiceType, request);

                        if (closedFactoryServiceType == null)
                            return null;

                        // Copy factory info with closed factory type
                        factoryInfo = ServiceInfo.Of(closedFactoryServiceType).WithDetails(factoryInfo.Details);
                    }
                }

                MatchOpenGenericConstraints(factoryImplTypeParams, resultFactoryImplTypeArgs);

                // Close the factory type implementation
                // and get factory member to use from it.
                var closedFactoryImplType = Throw.IfThrows<ArgumentException, Type>(
                    () => factoryImplType.MakeGenericType(resultFactoryImplTypeArgs),
                    !ifErrorReturnDefault && request.IfUnresolved == IfUnresolved.Throw,
                    Error.NoMatchedGenericParamConstraints, factoryImplType, request);

                if (closedFactoryImplType == null)
                    return null;

                // Find corresponding member again, now from closed type
                var factoryMethodBase = factoryMember as MethodBase;
                if (factoryMethodBase != null)
                {
                    var factoryMethodParameters = factoryMethodBase.GetParameters();
                    var targetMethods = closedFactoryImplType.GetMembers(t => t.DeclaredMethods, includeBase: true)
                        .Match(m => m.Name == factoryMember.Name && m.GetParameters().Length == factoryMethodParameters.Length)
                        .ToArrayOrSelf();

                    if (targetMethods.Length == 1)
                        factoryMember = targetMethods[0];
                    else // Fallback to MethodHandle only if methods have similar signatures
                    {
                        var methodHandleProperty = typeof(MethodBase).GetTypeInfo()
                            .DeclaredProperties
                            .FindFirst(it => it.Name == "MethodHandle")
                            .ThrowIfNull(Error.OpenGenericFactoryMethodDeclaringTypeIsNotSupportedOnThisPlatform,
                                factoryImplType, closedFactoryImplType, factoryMethodBase.Name);
                        factoryMember = MethodBase.GetMethodFromHandle(
                            (RuntimeMethodHandle)methodHandleProperty.GetValue(factoryMethodBase, Empty<object>()),
                            closedFactoryImplType.TypeHandle);
                    }
                }
                else if (factoryMember is FieldInfo)
                {
                    factoryMember = closedFactoryImplType.GetMembers(t => t.DeclaredFields, includeBase: true)
                        .Single(f => f.Name == factoryMember.Name);
                }
                else if (factoryMember is PropertyInfo)
                {
                    factoryMember = closedFactoryImplType.GetMembers(t => t.DeclaredProperties, includeBase: true)
                        .Single(f => f.Name == factoryMember.Name);
                }
            }

            // If factory method is actual method and still open-generic after closing its declaring type,
            // then match remaining method type parameters and make closed method
            var openFactoryMethod = factoryMember as MethodInfo;
            if (openFactoryMethod != null && openFactoryMethod.ContainsGenericParameters)
            {
                var methodTypeParams = openFactoryMethod.GetGenericArguments();
                var resultMethodTypeArgs = new Type[methodTypeParams.Length];

                var isMethodClosed = MatchServiceWithImplementedTypeParams(
                    resultMethodTypeArgs, methodTypeParams, implTypeParams, serviceTypeArgs);

                if (!isMethodClosed)
                    return ifErrorReturnDefault || request.IfUnresolved != IfUnresolved.Throw ? null
                        : Throw.For<FactoryMethod>(Error.NoMatchedFactoryMethodWithServiceTypeArgs,
                            openFactoryMethod, new StringBuilder().Print(serviceTypeArgs, itemSeparator: ", "),
                            request);

                MatchOpenGenericConstraints(methodTypeParams, resultMethodTypeArgs);

                factoryMember = Throw.IfThrows<ArgumentException, MethodInfo>(
                    () => openFactoryMethod.MakeGenericMethod(resultMethodTypeArgs),
                    !ifErrorReturnDefault && request.IfUnresolved == IfUnresolved.Throw,
                    Error.NoMatchedGenericParamConstraints, factoryImplType, request);

                if (factoryMember == null)
                    return null;
            }

            return FactoryMethod.Of(factoryMember, factoryInfo);
        }

#endregion
    }

    /// <summary>Creates service expression using client provided expression factory delegate.</summary>
    public sealed class ExpressionFactory : Factory
    {
        /// <summary>Wraps provided delegate into factory.</summary>
        /// <param name="getServiceExpression">Delegate that will be used internally to create service expression.</param>
        /// <param name="reuse">(optional) Reuse.</param> <param name="setup">(optional) Setup.</param>
        public ExpressionFactory(Func<Request, Expression> getServiceExpression, IReuse reuse = null, Setup setup = null)
            : base(reuse, setup)
        {
            _getServiceExpression = getServiceExpression.ThrowIfNull();
        }

        /// <summary>Creates service expression using wrapped delegate.</summary>
        /// <param name="request">Request to resolve.</param> <returns>Expression returned by stored delegate.</returns>
        public override Expression CreateExpressionOrDefault(Request request) =>
            _getServiceExpression(request);

        private readonly Func<Request, Expression> _getServiceExpression;
    }

    /// Wraps the instance in registry
    public sealed class RegisteredInstanceFactory : Factory
    {
        /// The registered pre-created object instance
        public readonly object Instance;

        /// <summary>Non-abstract closed implementation type.</summary>
        public override Type ImplementationType { get; }

        /// <inheritdoc />
        public override bool HasRuntimeState => true;

        /// Simplified specially for register instance 
        internal override bool ValidateAndNormalizeRegistration(Type serviceType, object serviceKey, bool isStaticallyChecked, Rules rules)
        {
            if (!isStaticallyChecked && (ImplementationType != null && !ImplementationType.IsAssignableTo(serviceType.ThrowIfNull())))
                Throw.It(Error.RegisteringInstanceNotAssignableToServiceType, ImplementationType, serviceType);
            return true;
        }

        /// <summary>Creates factory.</summary>
        public RegisteredInstanceFactory(object instance, IReuse reuse = null, Setup setup = null)
           : base(reuse ?? DryIoc.Reuse.Singleton,
               (setup ?? DryIoc.Setup.Default).WithAsResolutionCallForGeneratedExpression())
        {
            if (instance != null) // it may be `null` as well
            {
                ImplementationType = instance.GetType();
                if (Setup.WeaklyReferenced)
                    Instance = new WeakReference(instance);
                else
                    Instance = instance;
            }
        }

        /// Wraps the instance in expression constant
        public override Expression CreateExpressionOrDefault(Request request)
        {
            // unpacks the weak-reference
            if (Setup.WeaklyReferenced)
                return Call(
                    typeof(ThrowInGeneratedCode).GetTypeInfo()
                        .GetDeclaredMethod(nameof(ThrowInGeneratedCode.WeakRefReuseWrapperGCed)),
                    Property(
                        Constant(Instance, typeof(WeakReference)),
                        typeof(WeakReference).Property(nameof(WeakReference.Target))));

            // otherwise just return a constant
            var instanceExpr = request.Container.GetConstantExpression(Instance);
            var serviceType = request.GetActualServiceType();
            if (ImplementationType.IsAssignableTo(serviceType))
                return instanceExpr;
            return Convert(instanceExpr, serviceType);
        }

        /// Simplified path for the registered instance
        public override Expression GetExpressionOrDefault(Request request)
        {
            if (// preventing recursion
                (request.Flags & RequestFlags.IsGeneratedResolutionDependencyExpression) == 0 && !request.IsResolutionCall && 
                 (Setup.AsResolutionCall || Setup.AsResolutionCallForExpressionGeneration && request.Rules.UsedForExpressionGeneration))
                return Resolver.CreateResolutionExpression(request.WithResolvedFactory(this), Setup.OpenResolutionScope);

            // First look for decorators if it is not already a decorator
            var serviceType = request.ServiceType;
            var elementType = serviceType.GetArrayElementTypeOrNull();
            if (elementType != null)
                serviceType = typeof(IEnumerable<>).MakeGenericType(elementType);
            if (!request.Container.GetDecoratorFactoriesOrDefault(serviceType).IsNullOrEmpty())
            {
                var decoratorExpr = request.Container.GetDecoratorExpressionOrDefault(request.WithResolvedFactory(this));
                if (decoratorExpr != null)
                    return decoratorExpr;
            }

            return CreateExpressionOrDefault(request);
        }

        /// Used at resolution root too simplify getting the actual instance
        public override FactoryDelegate GetDelegateOrDefault(Request request)
        {
            request = request.WithResolvedFactory(this);

            if (request.Container.GetDecoratorExpressionOrDefault(request) != null)
                return base.GetDelegateOrDefault(request);

            return Setup.WeaklyReferenced 
                ? (FactoryDelegate)UnpackWeakRefFactory 
                : InstanceFactory;
        }

        private object InstanceFactory(IResolverContext _) => Instance;
        private object UnpackWeakRefFactory(IResolverContext _) => (Instance as WeakReference)?.Target.WeakRefReuseWrapperGCed();
    }

    /// <summary>This factory is the thin wrapper for user provided delegate
    /// and where possible it uses delegate directly: without converting it to expression.</summary>
    public sealed class DelegateFactory : Factory
    {
        /// <summary>Non-abstract closed implementation type.</summary>
        public override Type ImplementationType { get; }

        /// <inheritdoc />
        public override bool HasRuntimeState => true;

        /// <summary>Creates factory.</summary>
        public DelegateFactory(FactoryDelegate factoryDelegate,
           IReuse reuse = null, Setup setup = null, Type knownImplementationType = null)
           : base(reuse, (setup ?? Setup.Default).WithAsResolutionCallForGeneratedExpression())
        {
            _factoryDelegate = factoryDelegate.ThrowIfNull();
            ImplementationType = knownImplementationType;
        }

        /// <summary>Create expression by wrapping call to stored delegate with provided request.</summary>
        public override Expression CreateExpressionOrDefault(Request request)
        {
            // GetConstant here is needed to check the state
            var delegateExpr = request.Container.GetConstantExpression(_factoryDelegate);
            var resolverExpr = ResolverContext.GetRootOrSelfExpr(request);
            return Convert(Invoke(delegateExpr, resolverExpr), request.GetActualServiceType());
        }

        /// <summary>If possible returns delegate directly, without creating expression trees, just wrapped in <see cref="FactoryDelegate"/>.
        /// If decorator found for request then factory fall-backs to expression creation.</summary>
        /// <param name="request">Request to resolve.</param>
        /// <returns>Factory delegate directly calling wrapped delegate, or invoking expression if decorated.</returns>
        public override FactoryDelegate GetDelegateOrDefault(Request request)
        {
            request = request.WithResolvedFactory(this);

            // Wrap the delegate in respective expression for non-simple use
            if (request.Reuse != DryIoc.Reuse.Transient ||
                FactoryType == FactoryType.Service &&
                request.Container.GetDecoratorExpressionOrDefault(request) != null)
                return base.GetDelegateOrDefault(request);

            // Otherwise just use delegate as-is
            return _factoryDelegate;
        }

        private readonly FactoryDelegate _factoryDelegate;
    }

    internal sealed class FactoryPlaceholder : Factory
    {
        public static readonly Factory Default = new FactoryPlaceholder();

        // Always resolved asResolutionCall, to create a hole in object graph to be filled in later
        public override Setup Setup => _setup;
        private static readonly Setup _setup = Setup.With(asResolutionCall: true);

        public override Expression CreateExpressionOrDefault(Request request) =>
            Throw.For<Expression>(Error.NoImplementationForPlaceholder, request);
    }

    /// Should return value stored in scope
    public delegate object CreateScopedValue();

    /// <summary>Lazy object storage that will create object with provided factory on first access,
    /// then will be returning the same object for subsequent access.</summary>
    public interface IScope : IEnumerable<IScope>, IDisposable
    {
        /// <summary>Parent scope in scope stack. Null for root scope.</summary>
        IScope Parent { get; }

        /// <summary>Optional name object associated with scope.</summary>
        object Name { get; }

        /// <summary>True if scope is disposed.</summary>
        bool IsDisposed { get; }

        /// <summary>Looks up for stored item by id.</summary>
        bool TryGet(out object item, int id);

        /// Creates, stores, and returns created item
        object GetOrAdd(int id, CreateScopedValue createValue, int disposalOrder = 0);

        /// Create the value via `FactoryDelegate` passing the `IResolverContext`
        object GetOrAddViaFactoryDelegate(int id, FactoryDelegate createValue, IResolverContext r, int disposalOrder = 0);

        /// Creates, stores, and returns created item
        object TryGetOrAddWithoutClosure(int id,
            IResolverContext resolveContext, Expression expr, bool useFec,
            Func<IResolverContext, Expression, bool, object> createValue, int disposalOrder = 0);

        /// <summary>Tracked item will be disposed with the scope. 
        /// Smaller <paramref name="disposalOrder"/> will be disposed first.</summary>
        object TrackDisposable(object item, int disposalOrder = 0);

        ///[Obsolete("Removing because it is used only by obsolete `UseInstance` feature")]
        void SetOrAdd(int id, object item);

        ///[Obsolete("Removing because it is not used")]
        object GetOrTryAdd(int id, object item, int disposalOrder);

        /// Sets (replaces) the factory for specified type.
        void SetUsedInstance(Type type, FactoryDelegate factory);

        /// Looks up for stored item by type.
        bool TryGetUsedInstance(IResolverContext r, Type type, out object instance);

        /// Clones the scope.
        IScope Clone();
    }

    /// Scope is container to hold the shared per scope items and dispose <see cref="IDisposable"/> items.
    /// Scope uses Locking to ensure that the object factory called only once.
    public sealed class Scope : IScope
    {
        /// <summary>Parent scope in scope stack. Null for the root scope.</summary>
        public IScope Parent { get; }

        /// <summary>Optional name associated with scope.</summary>
        public object Name { get; }

        /// <summary>True if scope is disposed.</summary>
        public bool IsDisposed => _disposed == 1;
        private int _disposed;

        private ImHashMap<Type, FactoryDelegate> _factories;
        private ImList<IDisposable> _unorderedDisposables;
        private ImMap<IDisposable> _disposables;

        internal const int MAP_COUNT = 16;
        internal const int MAP_COUNT_SUFFIX_MASK = MAP_COUNT - 1;
        internal ImMap<object>[] _maps;

        internal static readonly object NoItem = new object();

        private static StackPool<ImMap<object>[]> _mapsPool = new StackPool<ImMap<object>[]>();

        private static ImMap<object>[] _emptySlots = CreateEmptyMaps();

        private static ImMap<object>[] CreateEmptyMaps()
        {
            var empty = ImMap<object>.Empty;
            var slots = new ImMap<object>[MAP_COUNT];
            for (var i = 0; i < MAP_COUNT; ++i) 
                slots[i] = empty;
            return slots;
        }

        /// <summary>Creates scope with optional parent and name.</summary>
        public Scope(IScope parent = null, object name = null)
            : this(parent, name, /*_mapsPool.RentOrDefault() ??*/ CreateEmptyMaps(), ImHashMap<Type, FactoryDelegate>.Empty, 
                ImList<IDisposable>.Empty, ImMap<IDisposable>.Empty)
        { }

        private Scope(IScope parent, object name, ImMap<object>[] maps, ImHashMap<Type, FactoryDelegate> instances, 
            ImList<IDisposable> unorderedDisposables, ImMap<IDisposable> disposables)
        {
            Parent = parent;
            Name = name;
            _unorderedDisposables = unorderedDisposables;
            _disposables = disposables;
            _factories = ImHashMap<Type, FactoryDelegate>.Empty;
            _maps = maps;
        }

        /// <inheritdoc />
        public IScope Clone()
        {
            var slotsCopy = new ImMap<object>[MAP_COUNT];

            // todo: we need a way to copy all Refs in the slot - do we?
            for (var i = 0; i < MAP_COUNT; i++) 
                slotsCopy[i] = _maps[i];

            return new Scope(Parent, Name, slotsCopy, _factories, _unorderedDisposables, _disposables);
        }

        /// <inheritdoc />
        [MethodImpl((MethodImplOptions)256)]
        public object GetOrAdd(int id, CreateScopedValue createValue, int disposalOrder = 0)
        {
            ref var map = ref _maps[id & MAP_COUNT_SUFFIX_MASK];
            var itemRef = map.GetEntryOrDefault(id);
            if (itemRef != null && itemRef.Value != NoItem)
                return itemRef.Value;
            return TryGetOrAdd(ref map, id, createValue, disposalOrder);
        }

        private object TryGetOrAdd(ref ImMap<object> map, int id, CreateScopedValue createValue, int disposalOrder = 0)
        {
            if (_disposed == 1)
                Throw.It(Error.ScopeIsDisposed, ToString());

            var m = map;
            if (Interlocked.CompareExchange(ref map, m.AddOrKeep(id, NoItem), m) != m)
                Ref.Swap(ref map, id, (x, i) => x.AddOrKeep(i, NoItem));

            var itemRef = map.GetEntryOrDefault(id);
            if (itemRef.Value != NoItem)
                return itemRef.Value;

            // lock on the ref itself to set its `Item` field
            lock (itemRef)
            {
                // double-check if the item was changed in between (double check locking)
                if (itemRef.Value != NoItem)
                    return itemRef.Value;
                itemRef.Value = createValue();
            }

            if (itemRef.Value is IDisposable disp && disp != this)
                if (disposalOrder == 0)
                    AddUnorderedDisposable(disp);
                else
                    AddDisposable(disp, disposalOrder);

            return itemRef.Value;
        }

        /// <inheritdoc />
        [MethodImpl((MethodImplOptions)256)]
        public object GetOrAddViaFactoryDelegate(int id, FactoryDelegate createValue, IResolverContext r, int disposalOrder = 0)
        {
            var itemRef = _maps[id & MAP_COUNT_SUFFIX_MASK].GetEntryOrDefault(id);
            return itemRef != null && itemRef.Value != NoItem
                ? itemRef.Value
                : TryGetOrAddViaFactoryDelegate(id, createValue, r, disposalOrder);
        }

        internal static readonly MethodInfo GetOrAddViaFactoryDelegateMethod =
            typeof(IScope).GetTypeInfo().GetDeclaredMethod(nameof(IScope.GetOrAddViaFactoryDelegate));

        internal ImMapEntry<object> TryAddViaFactoryDelegate(int id, FactoryDelegate createValue, IResolverContext r, int disposalOrder)
        {
            if (_disposed == 1)
                Throw.It(Error.ScopeIsDisposed, ToString());

            ref var map = ref _maps[id & MAP_COUNT_SUFFIX_MASK];
            var m = map;
            if (Interlocked.CompareExchange(ref map, m.AddOrKeep(id, NoItem), m) != m)
                Ref.Swap(ref map, id, (x, i) => x.AddOrKeep(i, NoItem));

            var itemRef = map.GetEntryOrDefault(id);
            if (itemRef.Value != NoItem)
                return itemRef;

            lock (itemRef)
            {
                if (itemRef.Value != NoItem)
                    return itemRef;
                itemRef.Value = createValue(r);
            }

            if (itemRef.Value is IDisposable disp && disp != this)
                AddDisposable(disp, disposalOrder);

            return itemRef;
        }

        internal object TryGetOrAddViaFactoryDelegate(int id, FactoryDelegate createValue, IResolverContext r, int disposalOrder = 0)
        {
            if (_disposed == 1)
                Throw.It(Error.ScopeIsDisposed, ToString());

            ref var map = ref _maps[id & MAP_COUNT_SUFFIX_MASK];
            var m = map;
            if (Interlocked.CompareExchange(ref map, m.AddOrKeep(id, NoItem), m) != m)
                Ref.Swap(ref map, id, (x, i) => x.AddOrKeep(i, NoItem));

            var itemRef = map.GetEntryOrDefault(id);
            if (itemRef.Value != NoItem)
                return itemRef.Value;

            lock (itemRef)
            {
                if (itemRef.Value != NoItem)
                    return itemRef.Value;
                itemRef.Value = createValue(r);
            }

            if (itemRef.Value is IDisposable disp && disp != this)
            {
                if (disposalOrder == 0)
                    AddUnorderedDisposable(disp);
                else
                    AddDisposable(disp, disposalOrder);
            }

            return itemRef.Value;
        }

        /// <inheritdoc />
        public object TryGetOrAddWithoutClosure(int id,
            IResolverContext resolveContext, Expression expr, bool useFec,
            Func<IResolverContext, Expression, bool, object> createValue, int disposalOrder = 0)
        {
            if (_disposed == 1)
                Throw.It(Error.ScopeIsDisposed, ToString());

            ref var map = ref _maps[id & MAP_COUNT_SUFFIX_MASK];
            var m = map;
            if (Interlocked.CompareExchange(ref map, m.AddOrKeep(id, NoItem), m) != m)
                Ref.Swap(ref map, id, (x, i) => x.AddOrKeep(i, NoItem));

            var itemRef = map.GetEntryOrDefault(id);
            if (itemRef.Value != NoItem)
                return itemRef.Value;

            lock (itemRef)
            {
                if (itemRef.Value != NoItem)
                    return itemRef.Value;
                itemRef.Value = createValue(resolveContext, expr, useFec);
            }

            if (itemRef.Value is IDisposable disposable && disposable != this)
                if (disposalOrder == 0)
                    AddUnorderedDisposable(disposable);
                else
                    AddDisposable(disposable, disposalOrder);

            return itemRef.Value;
        }

        ///[Obsolete("Removing because it is used only by obsolete `UseInstance` feature")]
        public void SetOrAdd(int id, object item)
        {
            if (_disposed == 1)
                Throw.It(Error.ScopeIsDisposed, ToString());

            ref var map = ref _maps[id & MAP_COUNT_SUFFIX_MASK];
            var m = map;
            if (Interlocked.CompareExchange(ref map, m.AddOrKeep(id, NoItem), m) != m)
                Ref.Swap(ref map, x => x.AddOrKeep(id, NoItem));

            var itemRef = map.GetEntryOrDefault(id);
            if (itemRef.Value != NoItem)
                return;

            // lock on the ref itself to set its `Item` field
            lock (itemRef)
            {
                // double-check if the item was changed in between (double check locking)
                if (itemRef.Value != NoItem)
                    return;
                // we can simple assign because we are under the lock 
                itemRef.Value = item;
            }

            if (item is IDisposable disp && disp != this)
                AddUnorderedDisposable(disp);
        }

        ///[Obsolete("Removing because it is not used")]
        public object GetOrTryAdd(int id, object newItem, int disposalOrder)
        {
            if (_disposed == 1)
                Throw.It(Error.ScopeIsDisposed, ToString());

            ref var map = ref _maps[id & MAP_COUNT_SUFFIX_MASK];

            var itemRef = map.GetEntryOrDefault(id);
            if (itemRef != null && itemRef.Value != NoItem)
                return itemRef.Value;

            var m = map;
            if (Interlocked.CompareExchange(ref map, m.AddOrKeep(id, NoItem), m) != m)
                Ref.Swap(ref map, x => x.AddOrUpdate(id, NoItem));

            itemRef = map.GetEntryOrDefault(id);

            // lock on the ref itself to set its `Item` field
            lock (itemRef)
            {
                // double-check if the item was changed in between (double check locking)
                if (itemRef.Value != NoItem)
                    return itemRef.Value;

                // we can simple assign because we are under the lock 
                itemRef.Value = newItem;
            }

            if (itemRef.Value is IDisposable disp && disp != this)
                if (disposalOrder == 0)
                    AddUnorderedDisposable(disp);
                else
                    AddDisposable(disp, disposalOrder);

            return itemRef.Value;
        }

        internal void AddDisposable(IDisposable disposable, int disposalOrder)
        {
            var d = _disposables;
            if (Interlocked.CompareExchange(ref _disposables, d.AddOrUpdate(disposalOrder, disposable), d) != d)
                Ref.Swap(ref _disposables, disposalOrder, disposable, (x, dispOrder, disp) => x.AddOrUpdate(dispOrder, disp));
        }

        [MethodImpl((MethodImplOptions)256)]
        internal void AddUnorderedDisposable(IDisposable disposable)
        {
            var copy = _unorderedDisposables;
            if (Interlocked.CompareExchange(ref _unorderedDisposables, copy.Push(disposable), copy) != copy)
                Ref.Swap(ref _unorderedDisposables, disposable, (x, d) => x.Push(d));
        }

        /// <inheritdoc />
        [MethodImpl((MethodImplOptions)256)]
        public bool TryGet(out object item, int id)
        {
            var itemRef = _maps[id & MAP_COUNT_SUFFIX_MASK].GetEntryOrDefault(id); 
            if (itemRef != null && itemRef.Value != NoItem)
            {
                item = itemRef.Value;
                return true;
            }

            item = null;
            return false;
        }

        // todo: consider adding the overload without `disposalOrder`
        /// <summary>Can be used to manually add service for disposal</summary>
        public object TrackDisposable(object item, int disposalOrder = 0)
        {
            if (item is IDisposable disposable && disposable != this)
                if (disposalOrder == 0)
                    AddUnorderedDisposable(disposable);
                else
                    AddDisposable(disposable, disposalOrder);
            return item;
        }

        internal static readonly MethodInfo TrackDisposableMethod =
            typeof(IScope).GetTypeInfo().GetDeclaredMethod(nameof(IScope.TrackDisposable));

        /// Add instance to the small registry via factory
        public void SetUsedInstance(Type type, FactoryDelegate factory)
        {
            if (_disposed == 1)
                Throw.It(Error.ScopeIsDisposed, ToString());
            var f = _factories;
            var hash = RuntimeHelpers.GetHashCode(type);
            if (Interlocked.CompareExchange(ref _factories, f.AddOrUpdate(hash, type, factory), f) != f)
                Ref.Swap(ref _factories, hash, type, factory, (x, h, t, fac) => x.AddOrUpdate(h, t, fac));
        }

        /// <summary>Try retrieve instance from the small registry.</summary>
        public bool TryGetUsedInstance(IResolverContext r, Type type, out object instance)
        {
            instance = null;
            if (_disposed == 1)
                return false;

            if (!_factories.IsEmpty)
            {
                var factory = _factories.GetValueOrDefault(RuntimeHelpers.GetHashCode(type), type);
                if (factory != null)
                {
                    instance = factory(r);
                    return true;
                }
            }

            return Parent?.TryGetUsedInstance(r, type, out instance) ?? false;
        }

        /// <summary>Enumerates all the parent scopes upwards starting from this one.</summary>
        public IEnumerator<IScope> GetEnumerator()
        {
            for (IScope scope = this; scope != null; scope = scope.Parent)
                yield return scope;
        }

        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

        /// <summary>Disposes all stored <see cref="IDisposable"/> objects and empties item storage.
        /// The disposal happens in REVERSE resolution / injection order, consumer first, dependency next.
        /// It will allow consumer to do something with its dependency before it is disposed.</summary>
        /// <remarks>All disposal exceptions are swallowed except the ContainerException,
        /// which may indicate container misconfiguration.</remarks>
        public void Dispose()
        {
            if (Interlocked.CompareExchange(ref _disposed, 1, 0) == 1)
                return;

            if (!_disposables.IsEmpty)
                SafelyDisposeOrderedDisposables(_disposables);

            for (var unordDisp = _unorderedDisposables; !unordDisp.IsEmpty; unordDisp = unordDisp.Tail) 
                unordDisp.Head.Dispose();

            _unorderedDisposables = ImList<IDisposable>.Empty;
            _disposables = ImMap<IDisposable>.Empty;
            _factories = ImHashMap<Type, FactoryDelegate>.Empty;
            
            var maps = Interlocked.Exchange(ref _maps, _emptySlots);
            var empty = ImMap<object>.Empty;
            //for (int i = 0; i < MAP_COUNT; i++) maps[i] = empty;
            //_mapsPool.Return(maps);
        }

        private static void SafelyDisposeOrderedDisposables(ImMap<IDisposable> disposables)
        {
            disposables.Visit(d => {
                try
                {
                    // Ignoring disposing exception, as it is not important to proceed the disposal of other items
                    d.Value.Dispose();
                }
                catch (ContainerException)
                {
                    throw;
                }
                catch (Exception)
                {
                }
            });
        }

        /// <summary>Prints scope info (name and parent) to string for debug purposes.</summary>
        public override string ToString() =>
            "{" + (IsDisposed ? "IsDisposed=true, " : "")
                + (Name != null ? "Name=" + Name : "Name=null")
                + (Parent != null ? ", Parent=" + Parent : "")
                + "}";
    }

    /// <summary>Delegate to get new scope from old/existing current scope.</summary>
    /// <param name="oldScope">Old/existing scope to change.</param>
    /// <returns>New scope or old if do not want to change current scope.</returns>
    public delegate IScope SetCurrentScopeHandler(IScope oldScope);

    /// <summary>Provides ambient current scope and optionally scope storage for container,
    /// examples are HttpContext storage, Execution context, Thread local.</summary>
    public interface IScopeContext : IDisposable
    {
        /// <summary>Returns current scope or null if no ambient scope available at the moment.</summary>
        /// <returns>Current scope or null.</returns>
        IScope GetCurrentOrDefault();

        /// <summary>Changes current scope using provided delegate. Delegate receives current scope as input and
        /// should return new current scope.</summary>
        /// <param name="setCurrentScope">Delegate to change the scope.</param>
        /// <remarks>Important: <paramref name="setCurrentScope"/> may be called multiple times in concurrent environment.
        /// Make it predictable by removing any side effects.</remarks>
        /// <returns>New current scope. So it is convenient to use method in "using (var newScope = ctx.SetCurrent(...))".</returns>
        IScope SetCurrent(SetCurrentScopeHandler setCurrentScope);
    }

#if NET35 || NET40 || NET403 || PCL || PCL328 || PCL259

    /// <summary>Tracks one current scope per thread, so the current scope in different tread would be different or null,
    /// if not yet tracked. Context actually stores scope references internally, so it should be disposed to free them.</summary>
    public sealed class ThreadScopeContext : IScopeContext
    {
        /// <summary>Provides static name for context. It is OK because its constant.</summary>
        public static readonly string ScopeContextName = "ThreadScopeContext";

        /// <summary>Returns current scope in calling Thread or null, if no scope tracked.</summary>
        public IScope GetCurrentOrDefault() =>
            _scopes.GetValueOrDefault(Portable.GetCurrentManagedThreadID()) as IScope;

        /// <summary>Change current scope for the calling Thread.</summary>
        public IScope SetCurrent(SetCurrentScopeHandler setCurrentScope)
        {
            var threadId = Portable.GetCurrentManagedThreadID();
            IScope newScope = null;
            Ref.Swap(ref _scopes, s => s.AddOrUpdate(threadId, 
                newScope = setCurrentScope(s.GetValueOrDefault(threadId) as IScope)));
            return newScope;
        }

        /// <summary>Disposes the scopes and empties internal scope storage.</summary>
        public void Dispose()
        {
            if (!_scopes.IsEmpty) 
                _scopes.Visit(d => d.Value?.Dispose());
            _scopes = ImMap<IScope>.Empty;
        }

        /// Collection of scoped by their managed thread id
        private ImMap<IScope> _scopes = ImMap<IScope>.Empty;
    }

#else
    /// <summary>Tracks one current scope per thread, so the current scope in different tread would be different or null,
    /// if not yet tracked. Context actually stores scope references internally, so it should be disposed to free them.</summary>
    public sealed class ThreadScopeContext : IScopeContext
    {
        /// <summary>Provides static name for context. It is OK because its constant.</summary>
        public static readonly string ScopeContextName = "ThreadScopeContext";

        private ThreadLocal<IScope> _scope = new ThreadLocal<IScope>(true);

        /// <summary>Returns current scope in calling Thread or null, if no scope tracked.</summary>
        public IScope GetCurrentOrDefault() => 
            _scope.Value;

        /// <summary>Change current scope for the calling Thread.</summary>
        public IScope SetCurrent(SetCurrentScopeHandler setCurrentScope) => 
            _scope.Value = setCurrentScope(GetCurrentOrDefault());

        /// <summary>Disposes the scopes and empties internal scope storage.</summary>
        public void Dispose()
        {
            var scopes = _scope.Values;
            foreach (var scope in scopes)
            {
                var s = scope;
                while (s != null)
                {
                    var x = s;
                    s = s.Parent;
                    x.Dispose();
                }
            }
        }
    }

#endif

    /// <summary>Simplified scope agnostic reuse abstraction. More easy to implement,
    ///  and more powerful as can be based on other storage beside reuse.</summary>
    public interface IReuse : IConvertibleToExpression
    {
        /// <summary>Relative to other reuses lifespan value.</summary>
        int Lifespan { get; }

        /// <summary>Optional name. Use to find matching scope by the name.
        /// It also may be interpreted as object[] Names for matching with multiple scopes </summary>
        object Name { get; }

        /// <summary>Returns true if reuse can be applied: may check if scope or other reused item storage is present.</summary>
        bool CanApply(Request request);

        /// <summary>Returns composed expression.</summary>
        Expression Apply(Request request, Expression serviceFactoryExpr);
    }

    /// <summary>Returns container bound scope for storing singleton objects.</summary>
    public sealed class SingletonReuse : IReuse
    {
        /// <summary>Big lifespan.</summary>
        public const int DefaultLifespan = 1000;

        /// <summary>Relative to other reuses lifespan value.</summary>
        public int Lifespan => DefaultLifespan;

        /// <inheritdoc />
        public object Name => null;

        /// <summary>Returns true because singleton is always available.</summary>
        public bool CanApply(Request request) => true;

        /// <summary>Returns expression call to GetOrAddItem.</summary>
        public Expression Apply(Request request, Expression serviceFactoryExpr)
        {
            // this is required because we cannot use ValueType for the object
            if (serviceFactoryExpr.Type.IsValueType())
                serviceFactoryExpr = Convert(serviceFactoryExpr, typeof(object));

            if (request.TracksTransientDisposable)
                return Call(ResolverContext.SingletonScopeExpr, Scope.TrackDisposableMethod,
                    serviceFactoryExpr, Constant(request.Factory.Setup.DisposalOrder));

            return Call(ResolverContext.SingletonScopeExpr, Scope.GetOrAddViaFactoryDelegateMethod,
                Constant(request.FactoryID), Lambda<FactoryDelegate>(serviceFactoryExpr,
                    FactoryDelegateCompiler.FactoryDelegateParamExprs
#if SUPPORTS_FAST_EXPRESSION_COMPILER
                    , typeof(object)
#endif
                ),
                FactoryDelegateCompiler.ResolverContextParamExpr,
                Constant(request.Factory.Setup.DisposalOrder));
        }

        private static readonly Lazy<Expression> _singletonReuseExpr = Lazy.Of<Expression>(() =>
            Field(null, typeof(Reuse).Field(nameof(Reuse.Singleton))));

        /// <inheritdoc />
        public Expression ToExpression(Func<object, Expression> fallbackConverter) => _singletonReuseExpr.Value;

        /// <summary>Pretty prints reuse name and lifespan</summary>
        public override string ToString() => "Singleton {Lifespan=" + Lifespan + "}";
    }

    /// <summary>Specifies that instances are created, stored and disposed together with some scope.</summary>
    public sealed class CurrentScopeReuse : IReuse
    {
        /// <summary>Less than Singleton's</summary>
        public const int DefaultLifespan = 100;

        /// <summary>Relative to other reuses lifespan value.</summary>
        public int Lifespan { get; }

        /// <inheritdoc />
        public object Name { get; }

        /// <summary>Returns true if scope is open and the name is matching with reuse <see cref="Name"/>.</summary>
        public bool CanApply(Request request) =>
            ScopedOrSingleton || 
            (Name == null ? request.Container.CurrentScope != null : request.Container.GetNamedScope(Name, false) != null);

        /// <summary>Creates scoped item creation and access expression.</summary>
        public Expression Apply(Request request, Expression serviceFactoryExpr)
        {
            // strip the conversion as we are operating with object anyway
            if (serviceFactoryExpr.NodeType == ExprType.Convert)
                serviceFactoryExpr = ((UnaryExpression)serviceFactoryExpr).Operand;
            
            // this is required because we cannot use ValueType for the object
            if (serviceFactoryExpr.Type.IsValueType())
                serviceFactoryExpr = Convert(serviceFactoryExpr, typeof(object));

            var resolverContextParamExpr = FactoryDelegateCompiler.ResolverContextParamExpr;

            if (request.TracksTransientDisposable)
            {
                if (ScopedOrSingleton)
                    return Call(TrackScopedOrSingletonMethod, new[] { resolverContextParamExpr, serviceFactoryExpr });

                var ifNoScopeThrowExpr = Constant(request.IfUnresolved == IfUnresolved.Throw);
                if (Name == null)
                    return Call(TrackScopedMethod, new[] { resolverContextParamExpr, ifNoScopeThrowExpr, serviceFactoryExpr });

                var nameExpr = request.Container.GetConstantExpression(Name, typeof(object));
                return Call(TrackNameScopedMethod, new[] { resolverContextParamExpr, nameExpr, ifNoScopeThrowExpr, serviceFactoryExpr });
            }
            else
            {
                var idExpr = Constant(request.FactoryID);

                Expression factoryDelegateExpr;
                if (serviceFactoryExpr is InvocationExpression ie &&
                    ie.Expression is ConstantExpression registeredDelegateExpr &&
                    registeredDelegateExpr.Type == typeof(FactoryDelegate))
                {
                    // optimization for the registered delegate
                    factoryDelegateExpr = registeredDelegateExpr;
                }
                else
                {
                    factoryDelegateExpr = Lambda<FactoryDelegate>(serviceFactoryExpr,
                        FactoryDelegateCompiler.FactoryDelegateParamExprs
#if SUPPORTS_FAST_EXPRESSION_COMPILER
                        , typeof(object)
#endif
                    );
                }

                var disposalIndex = request.Factory.Setup.DisposalOrder;

                if (ScopedOrSingleton)
                    return Call(GetScopedOrSingletonViaFactoryDelegateMethod,
                        resolverContextParamExpr, idExpr, factoryDelegateExpr, Constant(disposalIndex));

                var ifNoScopeThrowExpr = Constant(request.IfUnresolved == IfUnresolved.Throw);

                if (Name == null)
                {
                    if (disposalIndex == 0)
                        return Call(GetScopedViaFactoryDelegateNoDisposalIndexMethod,
                            resolverContextParamExpr, ifNoScopeThrowExpr, idExpr, factoryDelegateExpr);

                    return Call(GetScopedViaFactoryDelegateMethod, 
                        resolverContextParamExpr, ifNoScopeThrowExpr, idExpr, factoryDelegateExpr, Constant(disposalIndex));
                }

                return Call(GetNameScopedViaFactoryDelegateMethod, resolverContextParamExpr,
                    request.Container.GetConstantExpression(Name, typeof(object)),
                    ifNoScopeThrowExpr, idExpr, factoryDelegateExpr, Constant(disposalIndex));
            }
        }

        /// <inheritdoc />
        public Expression ToExpression(Func<object, Expression> fallbackConverter) =>
            Name == null && !ScopedOrSingleton 
                ? Field(null, typeof(Reuse).GetTypeInfo().GetDeclaredField(nameof(Reuse.Scoped)))
                : ScopedOrSingleton 
                    ? (Expression)Field(null, typeof(Reuse).GetTypeInfo().GetDeclaredField(nameof(Reuse.ScopedOrSingleton)))
                    : Call(typeof(Reuse).Method(nameof(Reuse.ScopedTo), typeof(object)), fallbackConverter(Name));

        /// <summary>Pretty prints reuse to string.</summary> <returns>Reuse string.</returns>
        public override string ToString()
        {
            var s = new StringBuilder(ScopedOrSingleton ? "ScopedOrSingleton {" : "Scoped {");
            if (Name != null)
                s.Append("Name=").Print(Name).Append(", ");
            if (Lifespan != DefaultLifespan)
                s.Append("NON DEFAULT LIFESPAN=").Append(Lifespan);
            else
                s.Append("Lifespan=").Append(Lifespan);

            return s.Append("}").ToString();
        }

        /// <summary>Creates the reuse.</summary>
        public CurrentScopeReuse(object name, bool scopedOrSingleton, int lifespan)
        {
            Name = name;
            ScopedOrSingleton = scopedOrSingleton;
            Lifespan = lifespan;
        }

        /// <summary>Creates the reuse optionally specifying its name.</summary>
        public CurrentScopeReuse(object name = null, bool scopedOrSingleton = false) 
            : this(name, scopedOrSingleton, DefaultLifespan)
        {
        }

        /// <summary>Flag indicating that it is a scope or singleton.</summary>
        public readonly bool ScopedOrSingleton;

        /// [Obsolete("Replaced by `GetScopedOrSingletonViaFactoryDelegate`")]
        public static object GetScopedOrSingleton(IResolverContext r,
            int id, CreateScopedValue createValue, int disposalIndex) =>
            (r.CurrentScope ?? r.SingletonScope).GetOrAdd(id, createValue, disposalIndex);

        /// Subject
        public static object GetScopedOrSingletonViaFactoryDelegate(IResolverContext r,
            int id, FactoryDelegate createValue, int disposalIndex) =>
            (r.CurrentScope ?? r.SingletonScope).GetOrAddViaFactoryDelegate(id, createValue, r, disposalIndex);

        internal static readonly MethodInfo GetScopedOrSingletonViaFactoryDelegateMethod =
            typeof(CurrentScopeReuse).GetTypeInfo().GetDeclaredMethod(nameof(GetScopedOrSingletonViaFactoryDelegate));

        /// Subject
        public static object TrackScopedOrSingleton(IResolverContext r, object item) =>
            (r.CurrentScope ?? r.SingletonScope).TrackDisposable(item);

        internal static readonly MethodInfo TrackScopedOrSingletonMethod =
            typeof(CurrentScopeReuse).GetTypeInfo().GetDeclaredMethod(nameof(TrackScopedOrSingleton));

        /// [Obsolete("Replaced by `GetScopedViaFactoryDelegate`")]
        public static object GetScoped(IResolverContext r,
            bool throwIfNoScope, int id, CreateScopedValue createValue, int disposalIndex) =>
            r.GetCurrentScope(throwIfNoScope)?.GetOrAdd(id, createValue, disposalIndex);

        /// Subject
        public static object GetScopedViaFactoryDelegateNoDisposalIndex(IResolverContext r,
            bool throwIfNoScope, int id, FactoryDelegate createValue) =>
            r.GetCurrentScope(throwIfNoScope)?.GetOrAddViaFactoryDelegate(id, createValue, r);

        internal static readonly MethodInfo GetScopedViaFactoryDelegateNoDisposalIndexMethod =
            typeof(CurrentScopeReuse).GetTypeInfo().GetDeclaredMethod(nameof(GetScopedViaFactoryDelegateNoDisposalIndex));

        /// Subject
        public static object GetScopedViaFactoryDelegate(IResolverContext r,
            bool throwIfNoScope, int id, FactoryDelegate createValue, int disposalIndex) =>
            r.GetCurrentScope(throwIfNoScope)?.GetOrAddViaFactoryDelegate(id, createValue, r, disposalIndex);

        internal static readonly MethodInfo GetScopedViaFactoryDelegateMethod =
            typeof(CurrentScopeReuse).GetTypeInfo().GetDeclaredMethod(nameof(GetScopedViaFactoryDelegate));

        /// [Obsolete("Replaced by `GetNameScopedViaFactoryDelegate`")]
        public static object GetNameScoped(IResolverContext r,
            object scopeName, bool throwIfNoScope, int id, CreateScopedValue createValue, int disposalIndex) =>
            r.GetNamedScope(scopeName, throwIfNoScope)?.GetOrAdd(id, createValue, disposalIndex);

        /// Subject
        public static object GetNameScopedViaFactoryDelegate(IResolverContext r,
            object scopeName, bool throwIfNoScope, int id, FactoryDelegate createValue, int disposalIndex) =>
            r.GetNamedScope(scopeName, throwIfNoScope)?.GetOrAddViaFactoryDelegate(id, createValue, r, disposalIndex);

        internal static readonly MethodInfo GetNameScopedViaFactoryDelegateMethod =
            typeof(CurrentScopeReuse).GetTypeInfo().GetDeclaredMethod(nameof(GetNameScopedViaFactoryDelegate));

        /// Subject
        public static object TrackScoped(IResolverContext r, bool throwIfNoScope, object item) =>
            r.GetCurrentScope(throwIfNoScope)?.TrackDisposable(item);

        internal static readonly MethodInfo TrackScopedMethod =
            typeof(CurrentScopeReuse).GetTypeInfo().GetDeclaredMethod(nameof(TrackScoped));

        /// Subject
        public static object TrackNameScoped(IResolverContext r, object scopeName, bool throwIfNoScope, object item) =>
            r.GetNamedScope(scopeName, throwIfNoScope)?.TrackDisposable(item);

        internal static readonly MethodInfo TrackNameScopedMethod =
            typeof(CurrentScopeReuse).GetTypeInfo().GetDeclaredMethod(nameof(TrackNameScoped));
    }

    /// <summary>Abstracts way to match reuse and scope names</summary>
    public interface IScopeName
    {
        /// <summary>Does the job.</summary>
        bool Match(object scopeName);
    }

    /// <summary>Represents multiple names</summary>
    public sealed class CompositeScopeName : IScopeName
    {
        /// <summary>Wraps multiple names</summary>
        public static CompositeScopeName Of(object[] names) => new CompositeScopeName(names);

        /// <summary>Matches all the name in a loop until first match is found, otherwise returns false.</summary>
        public bool Match(object scopeName)
        {
            var names = _names;
            for (var i = 0; i < names.Length; i++)
            {
                var name = names[i];
                if (name == scopeName)
                    return true;
                var aScopeName = name as IScopeName;
                if (aScopeName != null && aScopeName.Match(scopeName))
                    return true;
                if (scopeName != null && scopeName.Equals(name))
                    return true;
            }

            return false;
        }

        private CompositeScopeName(object[] names) { _names = names; }
        private readonly object[] _names;
    }

    /// <summary>Holds the name for the resolution scope.</summary>
    public sealed class ResolutionScopeName : IScopeName
    {
        /// <summary>Creates scope with specified service type and key</summary>
        public static ResolutionScopeName Of(Type serviceType = null, object serviceKey = null) =>
            new ResolutionScopeName(serviceType, serviceKey);

        /// <summary>Creates scope with specified service type and key.</summary>
        public static ResolutionScopeName Of<TService>(object serviceKey = null) =>
            new ResolutionScopeName(typeof(TService), serviceKey);

        /// <summary>Type of service opening the scope.</summary>
        public readonly Type ServiceType;

        /// <summary>Optional service key of service opening the scope.</summary>
        public readonly object ServiceKey;

        private ResolutionScopeName(Type serviceType, object serviceKey)
        {
            ServiceType = serviceType;
            ServiceKey = serviceKey;
        }

        /// <inheritdoc />
        public bool Match(object scopeName)
        {
            var name = scopeName as ResolutionScopeName;
            return name != null &&
                   (ServiceType == null ||
                   name.ServiceType.IsAssignableTo(ServiceType) ||
                   ServiceType.IsOpenGeneric() &&
                   name.ServiceType.GetGenericDefinitionOrNull().IsAssignableTo(ServiceType)) &&
                   (ServiceKey == null || ServiceKey.Equals(name.ServiceKey));
        }

        /// <summary>String representation for easy debugging and understood error messages.</summary>
        public override string ToString()
        {
            var s = new StringBuilder().Append("{ServiceType=").Print(ServiceType);
            if (ServiceKey != null)
                s.Append(", ServiceKey=").Print(ServiceKey);
            return s.Append('}').ToString();
        }
    }

    /// <summary>Specifies pre-defined reuse behaviors supported by container:
    /// used when registering services into container with <see cref="Registrator"/> methods.</summary>
    public static class Reuse
    {
        /// <summary>Synonym for absence of reuse.</summary>
        public static readonly IReuse Transient = new TransientReuse();

        /// <summary>Specifies to store single service instance per <see cref="Container"/>.</summary>
        public static readonly IReuse Singleton = new SingletonReuse();

        /// <summary>Same as InCurrentScope. From now on will be the default name.</summary>
        public static readonly IReuse Scoped = new CurrentScopeReuse();

        /// <summary>Same as InCurrentNamedScope. From now on will be the default name.</summary>
        public static IReuse ScopedTo(object name) => new CurrentScopeReuse(name);

        /// <summary>Specifies all the scope details</summary>
        public static IReuse ScopedTo(object name, bool scopedOrSingleton, int lifespan) =>
            new CurrentScopeReuse(name, scopedOrSingleton, lifespan);

        // todo: Should be renamed to `ScopedToMany` to prevent overload ambiguity
        /// <summary>Scoped to multiple names.</summary>
        public static IReuse ScopedTo(params object[] names) =>
            names.IsNullOrEmpty() ? Scoped
            : names.Length == 1 ? ScopedTo(names[0]) 
            : new CurrentScopeReuse(CompositeScopeName.Of(names));

        // todo: Consider changing the name to remove the ambiguity
        /// <summary>Same as InResolutionScopeOf. From now on will be the default name.</summary>
        public static IReuse ScopedTo(Type serviceType = null, object serviceKey = null) =>
            serviceType == null && serviceKey == null ? Scoped
            : new CurrentScopeReuse(ResolutionScopeName.Of(serviceType, serviceKey));

        /// <summary>Same as InResolutionScopeOf. From now on will be the default name.</summary>
        public static IReuse ScopedTo<TService>(object serviceKey = null) =>
            ScopedTo(typeof(TService), serviceKey);

        /// <summary>The same as <see cref="InCurrentScope"/> but if no open scope available will fallback to <see cref="Singleton"/></summary>
        /// <remarks>The <see cref="Error.DependencyHasShorterReuseLifespan"/> is applied the same way as for <see cref="InCurrentScope"/> reuse.</remarks>
        public static readonly IReuse ScopedOrSingleton = new CurrentScopeReuse(scopedOrSingleton: true);

        /// <summary>Obsolete: same as <see cref="Scoped"/>.</summary>
        [Obsolete("The same as Reuse.Scoped, please prefer to use Reuse.Scoped or the Reuse.ScopedTo to specify a bound service.")]
        public static readonly IReuse InResolutionScope = Scoped;

        /// <summary>Obsolete: same as <see cref="Scoped"/>.</summary>
        public static readonly IReuse InCurrentScope = Scoped;

        /// <summary>Returns current scope reuse with specific name to match with scope.
        /// If name is not specified then function returns <see cref="InCurrentScope"/>.</summary>
        /// <param name="name">(optional) Name to match with scope.</param>
        /// <returns>Created current scope reuse.</returns>
        public static IReuse InCurrentNamedScope(object name = null) => ScopedTo(name);

        /// <summary>Obsolete: please use ScopedTo instead.</summary>
        public static IReuse InResolutionScopeOf(Type assignableFromServiceType = null, object serviceKey = null) =>
            ScopedTo(assignableFromServiceType, serviceKey);

        /// <summary>Obsolete: please use ScopedTo instead.</summary>
        public static IReuse InResolutionScopeOf<TAssignableFromServiceType>(object serviceKey = null) =>
            ScopedTo<TAssignableFromServiceType>(serviceKey);

        /// <summary>Same as Scoped but requires <see cref="ThreadScopeContext"/>.</summary>
        public static readonly IReuse InThread = Scoped;

        // todo: Minimize usage of name for scopes, it will be more performant. e.g. ASP.NET Core does not use one.
        /// <summary>Special name that by convention recognized by <see cref="InWebRequest"/>.</summary>
        public static string WebRequestScopeName = "~WebRequestScopeName";

        /// <summary>Obsolete: please prefer using <see cref="Reuse.Scoped"/> instead.
        /// The named scope has performance drawback comparing to just a scope.
        /// If you need to distinguish nested scope, give names to them instead of naming the top web request scope.</summary>
        public static readonly IReuse InWebRequest = ScopedTo(WebRequestScopeName);

#region Implementation

        private sealed class TransientReuse : IReuse
        {
            public int Lifespan => 0;

            public object Name => null;

            public Expression Apply(Request _, Expression serviceFactoryExpr) => serviceFactoryExpr;

            public bool CanApply(Request request) => true;

            private readonly Lazy<Expression> _transientReuseExpr = Lazy.Of<Expression>(() =>
                Field(null, typeof(Reuse).Field(nameof(Transient))));

            public Expression ToExpression(Func<object, Expression> fallbackConverter) =>
                _transientReuseExpr.Value;

            public override string ToString() => "TransientReuse";
        }

#endregion
    }

    /// <summary>Policy to handle unresolved service.</summary>
    public enum IfUnresolved
    {
        /// <summary>If service is unresolved for whatever means, it will throw the respective exception.</summary>
        Throw,
        /// <summary>If service is unresolved for whatever means, it will return default(serviceType) value.</summary>
        ReturnDefault,
        /// <summary>If service is not registered, then it will return default, for other errors it will throw.</summary>
        ReturnDefaultIfNotRegistered,
    }

    /// <summary>Declares minimal API for service resolution. 
    /// Resolve default and keyed is separated because of optimization for faster resolution of the former.</summary>
    public interface IResolver
#if SUPPORTS_ISERVICE_PROVIDER
    : IServiceProvider
#endif

    {
        /// <summary>Resolves default (non-keyed) service from container and returns created service object.</summary>
        /// <param name="serviceType">Service type to search and to return.</param>
        /// <param name="ifUnresolved">Says what to do if service is unresolved.</param>
        /// <returns>Created service object or default based on <paramref name="ifUnresolved"/> provided.</returns>
        object Resolve(Type serviceType, IfUnresolved ifUnresolved);

        /// <summary>Resolves service instance from container.</summary>
        /// <param name="serviceType">Service type to search and to return.</param>
        /// <param name="serviceKey">(optional) service key used for registering service.</param>
        /// <param name="ifUnresolved">(optional) Says what to do if service is unresolved.</param>
        /// <param name="requiredServiceType">(optional) Registered or wrapped service type to use instead of <paramref name="serviceType"/>,
        ///     or wrapped type for generic wrappers.  The type should be assignable to return <paramref name="serviceType"/>.</param>
        /// <param name="preResolveParent">(optional) Dependency chain info.</param>
        /// <param name="args">(optional) To specify the dependency objects to use instead of resolving them from container.</param>
        /// <returns>Created service object or default based on <paramref name="ifUnresolved"/> parameter.</returns>
        object Resolve(Type serviceType, object serviceKey,
            IfUnresolved ifUnresolved, Type requiredServiceType, Request preResolveParent, object[] args);

        /// <summary>Resolves all services registered for specified <paramref name="serviceType"/>, or if not found returns
        /// empty enumerable. If <paramref name="serviceType"/> specified then returns only (single) service registered with this type.</summary>
        /// <param name="serviceType">Return type of an service item.</param>
        /// <param name="serviceKey">(optional) Resolve only single service registered with the key.</param>
        /// <param name="requiredServiceType">(optional) Actual registered service to search for.</param>
        /// <param name="preResolveParent">Dependency resolution path info.</param>
        /// <param name="args">(optional) To specify the dependency objects to use instead of resolving them from container.</param>
        /// <returns>Enumerable of found services or empty. Does Not throw if no service found.</returns>
        IEnumerable<object> ResolveMany(Type serviceType, object serviceKey,
            Type requiredServiceType, Request preResolveParent, object[] args);
    }

    /// <summary>Specifies options to handle situation when registered service is already present in the registry.</summary>
    public enum IfAlreadyRegistered
    {
        /// <summary>Appends new default registration or throws registration with the same key.</summary>
        AppendNotKeyed,
        /// <summary>Throws if default or registration with the same key is already exist.</summary>
        Throw,
        /// <summary>Keeps old default or keyed registration ignoring new registration: ensures Register-Once semantics.</summary>
        Keep,
        /// <summary>Replaces old registration with new one.</summary>
        Replace,
        /// <summary>Adds the new implementation or null (Made.Of),
        /// otherwise keeps the previous registration of the same implementation type.</summary>
        AppendNewImplementation
    }

    /// <summary>Existing registration info.</summary>
    public struct ServiceRegistrationInfo : IComparable<ServiceRegistrationInfo>
    {
        /// <summary>Registered factory.</summary>
        public Factory Factory;

        /// <summary>Required service type.</summary>
        public Type ServiceType;

        /// <summary>May be <c>null</c> for single default service, or <see cref="DefaultKey"/> for multiple default services.</summary>
        public object OptionalServiceKey;

        /// <summary>Provides registration order across all factory registrations in container.</summary>
        /// <remarks>May be the same for factory registered with multiple services
        /// OR for closed-generic factories produced from the single open-generic registration.</remarks>
        public int FactoryRegistrationOrder => Factory.RegistrationOrder;

        /// <summary>Implementation type if available.</summary>
        public Type ImplementationType => Factory.CanAccessImplementationType ? Factory.ImplementationType : null;

        /// <summary>Shortcut to <see cref="Setup.AsResolutionRoot"/> property, useful to find all roots</summary>
        public bool AsResolutionRoot => Factory.Setup.AsResolutionRoot;

        /// <summary>Shortcut to service info.</summary>
        public ServiceInfo ToServiceInfo() => ServiceInfo.Of(ServiceType, serviceKey: OptionalServiceKey);

        /// <summary>Overrides the service type and pushes the original service type to required service type</summary>
        public ServiceInfo ToServiceInfo(Type serviceType) =>
            ServiceInfo.Of(serviceType, ServiceType, IfUnresolved.Throw, OptionalServiceKey);

        /// <summary>Overrides the service type and pushes the original service type to required service type</summary>
        public ServiceInfo ToServiceInfo<TService>() => ToServiceInfo(typeof(TService));

        /// <summary>Creates info. Registration order is figured out automatically based on Factory.</summary>
        public ServiceRegistrationInfo(Factory factory, Type serviceType, object optionalServiceKey)
        {
            Factory = factory;
            ServiceType = serviceType;
            OptionalServiceKey = optionalServiceKey;
        }

        /// <summary>Orders by registration</summary>
        public int CompareTo(ServiceRegistrationInfo other) => Factory.FactoryID - other.Factory.FactoryID;

        /// <summary>Pretty-prints info to string.</summary>
        public override string ToString()
        {
            var s = new StringBuilder().Print(ServiceType);
            if (OptionalServiceKey != null)
                s.Append(" with ServiceKey=").Print(OptionalServiceKey);
            return s.Append(" with factory ").Append(Factory).ToString();
        }
    }

    /// <summary>Defines operations that for changing registry, and checking if something exist in registry.</summary>
    public interface IRegistrator
    {
        /// <summary>Registers factory in registry with specified service type and key for lookup.
        /// Returns true if factory was added to registry, false otherwise. False may be in case of <see cref="IfAlreadyRegistered.Keep"/>
        /// setting and already existing factory</summary>
        /// <param name="factory">To register.</param>
        /// <param name="serviceType">Service type as unique key in registry for lookup.</param>
        /// <param name="serviceKey">Service key as complementary lookup for the same service type.</param>
        /// <param name="ifAlreadyRegistered">Policy how to deal with already registered factory with same service type and key.</param>
        /// <param name="isStaticallyChecked">[performance] Confirms that service and implementation types are statically checked by compiler.</param>
        /// <returns>True if factory was added to registry, false otherwise.
        /// False may be in case of <see cref="IfAlreadyRegistered.Keep"/> setting and already existing factory.</returns>
        void Register(Factory factory, Type serviceType, object serviceKey, IfAlreadyRegistered? ifAlreadyRegistered, bool isStaticallyChecked);

        /// <summary>Returns true if expected factory is registered with specified service key and type.
        /// Not provided or <c>null</c> <paramref name="serviceKey"/> means to check the <paramref name="serviceType"/> 
        /// alone with any service key.</summary>
        bool IsRegistered(Type serviceType, object serviceKey, FactoryType factoryType, Func<Factory, bool> condition);

        /// <summary>Removes factory with specified service type and key from registry and cache.
        /// BUT consuming services may still hold on the resolved service instance.
        /// The cache of consuming services may also hold on the unregistered service. Use `IContainer.ClearCache` to clear all cache.</summary>
        void Unregister(Type serviceType, object serviceKey, FactoryType factoryType, Func<Factory, bool> condition);

        /// <summary>Returns all registered service factories with their Type and optional Key.
        /// Decorator and Wrapper types are not included.</summary>
        IEnumerable<ServiceRegistrationInfo> GetServiceRegistrations();

        /// <summary>Searches for registered factories by type, and key (if specified),
        /// and by factory type (by default uses <see cref="FactoryType.Service"/>).
        /// May return empty, 1 or multiple factories.</summary>
        Factory[] GetRegisteredFactories(Type serviceType, object serviceKey, FactoryType factoryType);

        /// Puts 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);
    }

    /// <summary>What to do with registrations when creating the new container from the existent one.</summary>
    public enum RegistrySharing
    {
        /// <summary>Shares both registrations and resolution cache if any</summary>
        Share = 0,
        /// <summary>Clones the registrations but preserves the resolution cache</summary>
        CloneButKeepCache,
        /// <summary>Clones the registrations and drops the cache -- full reset!</summary>
        CloneAndDropCache
    }

    /// <summary>Combines registrator and resolver roles, plus rules and scope management.</summary>
    public interface IContainer : IRegistrator, IResolverContext
    {
        /// <summary>Rules for defining resolution/registration behavior throughout container.</summary>
        Rules Rules { get; }

        /// <summary>Represents scope bound to container itself, and not an ambient (context) thingy.</summary>
        IScope OwnCurrentScope { get; }

        // todo: replace with the below overload with more parameters
        /// <summary>Creates new container from the current one by specifying the listed parameters.
        /// If the null or default values are provided then the default or new values will be applied.
        /// Nothing will be inherited from the current container.
        /// If you want to inherit something you need to provide it as parameter.</summary>
        IContainer With(Rules rules, IScopeContext scopeContext, RegistrySharing registrySharing, IScope singletonScope);

        /// <summary>Creates new container from the current one by specifying the listed parameters.
        /// If the null or default values are provided then the default or new values will be applied.
        /// Nothing will be inherited from the current container.
        /// If you want to inherit something you need to provide it as parameter.</summary>
        IContainer With(IResolverContext parent, Rules rules, IScopeContext scopeContext,
            RegistrySharing registrySharing, IScope singletonScope, IScope currentScope);

        /// <summary>Produces new container which prevents any further registrations.</summary>
        /// <param name="ignoreInsteadOfThrow">(optional)Controls what to do with registrations: ignore or throw exception.
        /// Throws exception by default.</param>
        /// <returns>New container preserving all current container state but disallowing registrations.</returns>
        IContainer WithNoMoreRegistrationAllowed(bool ignoreInsteadOfThrow = false);

        /// <summary>Searches for requested factory in registry, and then using <see cref="DryIoc.Rules.UnknownServiceResolvers"/>.</summary>
        /// <param name="request">Factory request.</param>
        /// <returns>Found factory, otherwise null if <see cref="Request.IfUnresolved"/> is set to <see cref="IfUnresolved.ReturnDefault"/>.</returns>
        Factory ResolveFactory(Request request);

        /// <summary>Searches for registered service factory and returns it, or null if not found.
        /// Will use <see cref="DryIoc.Rules.FactorySelector"/> if specified.</summary>
        /// <param name="request">Factory request.</param>
        /// <returns>Found factory or null.</returns>
        Factory GetServiceFactoryOrDefault(Request request);

        /// <summary>Finds all registered default and keyed service factories and returns them.
        /// It skips decorators and wrappers.</summary>
        /// <param name="serviceType">Service type to look for, may be open-generic type too.</param>
        /// <param name="bothClosedAndOpenGenerics">(optional) For generic serviceType instructs to look for
        /// both closed and open-generic registrations.</param>
        /// <returns>Enumerable of found pairs.</returns>
        /// <remarks>Returned Key item should not be null - it should be <see cref="DefaultKey.Value"/>.</remarks>
        IEnumerable<KV<object, Factory>> GetAllServiceFactories(Type serviceType, bool bothClosedAndOpenGenerics = false);

        /// <summary>Searches for registered wrapper factory and returns it, or null if not found.</summary>
        /// <param name="serviceType">Service type to look for.</param> <returns>Found wrapper factory or null.</returns>
        Factory GetWrapperFactoryOrDefault(Type serviceType);

        /// <summary>Returns all decorators registered for the service type.</summary> <returns>Decorator factories.</returns>
        Factory[] GetDecoratorFactoriesOrDefault(Type serviceType);

        /// <summary>Creates decorator expression: it could be either Func{TService,TService},
        /// or service expression for replacing decorators.</summary>
        /// <param name="request">Decorated service request.</param>
        /// <returns>Decorator expression.</returns>
        Expression GetDecoratorExpressionOrDefault(Request request);

        /// <summary>If <paramref name="serviceType"/> is generic type then this method checks if the type registered as generic wrapper,
        /// and recursively unwraps and returns its type argument. This type argument is the actual service type we want to find.
        /// Otherwise, method returns the input <paramref name="serviceType"/>.</summary>
        /// <param name="serviceType">Type to unwrap. Method will return early if type is not generic.</param>
        /// <param name="requiredServiceType">Required service type or null if don't care.</param>
        /// <returns>Unwrapped service type in case it corresponds to registered generic wrapper, or input type in all other cases.</returns>
        Type GetWrappedType(Type serviceType, Type requiredServiceType);

        /// <summary>Converts known items into custom expression or wraps in a constant expression.</summary>
        /// <param name="item">Item to convert.</param>
        /// <param name="itemType">(optional) Type of item, otherwise item <see cref="object.GetType()"/>.</param>
        /// <param name="throwIfStateRequired">(optional) Throws for non-primitive and not-recognized items,
        /// identifying that result expression require run-time state. For compiled expression it means closure in lambda delegate.</param>
        /// <returns>Returns constant or state access expression for added items.</returns>
        Expression GetConstantExpression(object item, Type itemType = null, bool throwIfStateRequired = false);

        /// <summary>Clears cache for specified service(s). But does not clear instances of already resolved/created singletons and scoped services!</summary>
        /// <param name="serviceType">Target service type.</param>
        /// <param name="factoryType">(optional) If not specified, clears cache for all <see cref="FactoryType"/>.</param>
        /// <param name="serviceKey">(optional) If omitted, the cache will be cleared for all registrations of <paramref name="serviceType"/>.</param>
        /// <returns>True if target service was found, false - otherwise.</returns>
        bool ClearCache(Type serviceType, FactoryType? factoryType, object serviceKey);

        /// Puts instance created via the passed factory on demand into the current or singleton scope
        new void Use(Type serviceType, FactoryDelegate factory);

        /// [Obsolete("Replaced by `Use` to put runtime data into container scopes and with `RegisterInstance` as a sugar for `RegisterDelegate(_ => instance)`")]
        new void UseInstance(Type serviceType, object instance, IfAlreadyRegistered IfAlreadyRegistered,
            bool preventDisposal, bool weaklyReferenced, object serviceKey);
    }

    /// <summary>Resolves all registered services of <typeparamref name="TService"/> type on demand,
    /// when enumerator <see cref="IEnumerator.MoveNext"/> called. If service type is not found, empty returned.</summary>
    /// <typeparam name="TService">Service type to resolve.</typeparam>
    public sealed class LazyEnumerable<TService> : IEnumerable<TService>
    {
        /// <summary>Exposes internal items enumerable.</summary>
        public readonly IEnumerable<TService> Items;

        /// <summary>Wraps lazy resolved items.</summary> <param name="items">Lazy resolved items.</param>
        public LazyEnumerable(IEnumerable<TService> items)
        {
            Items = items.ThrowIfNull();
        }

        /// <summary>Return items enumerator.</summary> 
        public IEnumerator<TService> GetEnumerator() => Items.GetEnumerator();

        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }

    /// <summary>Wrapper type to box service with associated arbitrary metadata object.</summary>
    /// <typeparam name="T">Service type.</typeparam>
    /// <typeparam name="TMetadata">Arbitrary metadata object type.</typeparam>
    public sealed class Meta<T, TMetadata>
    {
        /// <summary>Value or object with associated metadata.</summary>
        public readonly T Value;

        /// <summary>Associated metadata object. Could be anything.</summary>
        public readonly TMetadata Metadata;

        /// <summary>Boxes value and its associated metadata together.</summary>
        public Meta(T value, TMetadata metadata)
        {
            Value = value;
            Metadata = metadata;
        }
    }

    /// Exception that container throws in case of error. Dedicated exception type simplifies
    /// filtering or catching container relevant exceptions from client code.
#if SUPPORTS_SERIALIZABLE
    [Serializable]
#endif
    [SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable", Justification = "Not available in PCL.")]
    public class ContainerException : InvalidOperationException
    {
        /// <summary>Error code of exception, possible values are listed in <see cref="Error"/> class.</summary>
        public readonly int Error;

        /// <summary>Simplifies the access to the error name.</summary>
        public string ErrorName => DryIoc.Error.NameOf(Error);

        /// <summary>Creates exception by wrapping <paramref name="errorCode"/> and its message,
        /// optionally with <paramref name="innerException"/> exception.</summary>
        public static ContainerException Of(ErrorCheck errorCheck, int errorCode,
            object arg0, object arg1 = null, object arg2 = null, object arg3 = null, Exception innerException = null) =>
            new ContainerException(errorCode,
                string.Format(GetMessage(errorCheck, errorCode), Print(arg0), Print(arg1), Print(arg2), Print(arg3)),
                innerException);

        /// <summary>Gets error message based on provided args.</summary> <param name="errorCheck"></param> <param name="errorCode"></param>
        protected static string GetMessage(ErrorCheck errorCheck, int errorCode) =>
            errorCode == -1 ? Throw.GetDefaultMessage(errorCheck) : DryIoc.Error.Messages[errorCode];

        /// <summary>Prints argument for formatted message.</summary> <param name="arg">To print.</param> <returns>Printed string.</returns>
        protected static string Print(object arg) =>
            arg == null ? string.Empty : new StringBuilder().Print(arg).ToString();

        /// <summary>Creates exception with message describing cause and context of error,
        /// and leading/system exception causing it.</summary>
        public ContainerException(int errorCode, string message, Exception innerException)
            : base($"code: {DryIoc.Error.NameOf(errorCode)}; message: {message}", innerException)
        {
            Error = errorCode;
        }

        /// <summary>Creates exception with message describing cause and context of error.</summary>
        public ContainerException(int error, string message)
            : this(error, message, null) { }

#if SUPPORTS_SERIALIZABLE
        /// <inheritdoc />
        protected ContainerException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
            : base(info, context) {}
#endif
    }

    /// <summary>Defines error codes and error messages for all DryIoc exceptions (DryIoc extensions may define their own.)</summary>
    public static class Error
    {
        private static int _errorIndex = -1;

        /// <summary>List of error messages indexed with code.</summary>
        public static readonly string[] Messages = new string[100];

#pragma warning disable 1591 // "Missing XML-comment"
        public static readonly int
            UnableToResolveUnknownService = Of(
                "Unable to resolve {0}" + NewLine +
                "Where no service registrations found" + NewLine +
                "  and no dynamic registrations found in {1} of Rules.DynamicServiceProviders" + NewLine +
                "  and nothing found in {2} of Rules.UnknownServiceResolvers"),

            UnableToResolveFromRegisteredServices = Of(
                "Unable to resolve {0}" + NewLine +
                "  with normal and dynamic registrations:" + NewLine + "{1}"),

            ExpectedSingleDefaultFactory = Of(
                "Expecting single default registration but found many:" + NewLine + "{0}" + NewLine +
                "when resolving {1}." + NewLine +
                "Please identify service with key, or metadata, or use Rules.WithFactorySelector to specify single registered factory."),

            RegisteringImplementationNotAssignableToServiceType = Of(
                "Registering implementation type {0} is not assignable to service type {1}."),
            RegisteredFactoryMethodResultTypesIsNotAssignableToImplementationType = Of(
                "Registered factory method return type {1} should be assignable Or castable to implementation type {0} but it is not."),
            RegisteringOpenGenericRequiresFactoryProvider = Of(
                "Unable to register delegate factory for open-generic service {0}." + NewLine +
                "You need to specify concrete (closed) service type returned by delegate."),
            RegisteringOpenGenericImplWithNonGenericService = Of(
                "Unable to register open-generic implementation {0} with non-generic service {1}."),
            RegisteringOpenGenericServiceWithMissingTypeArgs = Of(
                "Unable to register open-generic implementation {0} because service {1} should specify all type arguments, but specifies only {2}."),
            RegisteringNotAGenericTypedefImplType = Of(
                "Unsupported registration of implementation {0} which is not a generic type definition but contains generic parameters." + NewLine +
                "Consider to register generic type definition {1} instead."),
            RegisteringNotAGenericTypedefServiceType = Of(
                "Unsupported registration of service {0} which is not a generic type definition but contains generic parameters." + NewLine +
                "Consider to register generic type definition {1} instead."),
            RegisteringNullImplementationTypeAndNoFactoryMethod = Of(
                "Registering without implementation type and without FactoryMethod to use instead."),
            RegisteringObjectTypeAsImplementationIsNotSupported = Of(
                "Registering `System.Object` type as implementation without a factory method is not supported."),
            RegisteringAbstractImplementationTypeAndNoFactoryMethod = Of(
                "Registering abstract implementation type {0} when it is should be concrete. Also there is not FactoryMethod to use instead."),
            UnableToSelectSinglePublicConstructorFromMultiple = Of(
                "Unable to select single public constructor from implementation type {0}:" + NewLine +
                "{1}"),
            UnableToSelectSinglePublicConstructorFromNone = Of(
                "Unable to select single public constructor from implementation type {0} because it does not have one."),
            NoMatchedImplementedTypesWithServiceType = Of(
                "Unable to match service with open-generic {0} implementing {1} when resolving {2}."),
            NoMatchedFactoryMethodDeclaringTypeWithServiceTypeArgs = Of(
                "Unable to match open-generic factory method Declaring type {0} with requested service type arguments <{1}> when resolving {2}."),
            NoMatchedFactoryMethodWithServiceTypeArgs = Of(
                "Unable to match open-generic factory method {0} with requested service type arguments <{1}> when resolving {2}."),
            OpenGenericFactoryMethodDeclaringTypeIsNotSupportedOnThisPlatform = Of(
                "[Specific to this .NET version] Unable to match method or constructor {0} from open-generic declaring type {1} to closed-generic type {2}, " +
                NewLine +
                "Please give the method an unique name to distinguish it from other overloads."),
            ResolvingOpenGenericServiceTypeIsNotPossible = Of(
                "Resolving open-generic service type is not possible for type: {0}."),
            RecursiveDependencyDetected = Of(
                "Recursive dependency is detected when resolving" + NewLine + "{0}."),
            ScopeIsDisposed = Of(
                "Scope {0} is disposed and scoped instances are disposed and no longer available."),
            NotFoundOpenGenericImplTypeArgInService = Of(
                "Unable to find for open-generic implementation {0} the type argument {1} when resolving {2}."),
            UnableToSelectCtor = Of(
                "Unable to get constructor of {0} using provided constructor selector when resolving {1}."),
            UnableToFindCtorWithAllResolvableArgs = Of(
                "Unable to find most resolvable constructor also including passed input arguments `{0}` " +
                NewLine + " when resolving: {1}."),
            RegisteredDelegateResultIsNotOfServiceType = Of(
                "Registered factory delegate returns service {0} is not assignable to desired service {1}."),
            NotFoundSpecifiedWritablePropertyOrField = Of(
                "Unable to find writable property or field {0} when resolving: {1}."),
            PushingToRequestWithoutFactory = Of(
                "Pushing the next request {0} into parent request not yet resolved to factory: {1}"),
            NoMatchedGenericParamConstraints = Of(
                "Open-generic service does not match with registered open-generic implementation constraints {0} when resolving: {1}."),
            GenericWrapperWithMultipleTypeArgsShouldSpecifyArgIndex = Of(
                "Generic wrapper type {0} should specify what type argument is wrapped, but it does not."),
            GenericWrapperTypeArgIndexOutOfBounds = Of(
                "Registered generic wrapper {0} specified type argument index {1} is out of type argument list."),
            DependencyHasShorterReuseLifespan = Of(
                "Dependency {0} with reuse {1} has shorter lifespan than its parent's {2}" + NewLine +
                "If you know what you're doing you may disable this error with the rule `new Container(rules => rules.WithoutThrowIfDependencyHasShorterReuseLifespan())`."),
            WeakRefReuseWrapperGCed = Of(
                "Reused service wrapped in WeakReference is Garbage Collected and no longer available."),
            ServiceIsNotAssignableFromFactoryMethod = Of(
                "Service of {0} is not assignable from factory method {1} when resolving: {2}."),
            GotNullConstructorFromFactoryMethod = Of(
                "Got null constructor when resolving {0}"),
            UnableToRegisterDuplicateDefault = Of(
                "The default service {0} without key {1} is already registered as {2}."),
            UnableToRegisterDuplicateKey = Of(
                "Unable to register service with duplicate key '{0}': {1}" + NewLine +
                " There is already registered service with the same key {2}."),
            NoCurrentScope = Of(
                "No current scope is available: probably you are registering to, or resolving from outside of the scope. " + NewLine +
                "Current resolver context is: {0}."),
            ContainerIsDisposed = Of(
                "Container is disposed and should not be used: {0}"),
            NoMatchedScopeFound = Of(
                "Unable to find matching scope with name {0} starting from the current scope {1}."),
            NotSupportedMadeOfExpression = Of(
                "Expected expression of method call, property getter, or new statement (with optional property initializer), " +
                "but found this Made.Of expression: {0}"),
            UnexpectedFactoryMemberExpressionInMadeOf = Of(
                "Expected property getter, but found not supported `{0}` " + NewLine +
                "in Made.Of expression: `{1}`"),
            UnexpectedExpressionInsteadOfArgMethodInMadeOf = Of(
                "Expected `Arg.Of` method call to specify parameter, property or field, but found `{0}` " + NewLine +
                "in Made.Of expression: `{1}`"),
            UnexpectedExpressionInsteadOfConstantInMadeOf = Of(
                "Expected `ConstantExpression` for value of parameter, property, or field, but found `{0}` " + NewLine +
                "in Made.Of expression: `{1}`"),
            InjectedCustomValueIsOfDifferentType = Of(
                "Injected value {0} is not assignable to {1} when resolving: {2}"),
            NoConversionOperatorFoundWhenInterpretingTheConvertExpression = Of(
                "There is no explicit or implicit conversion operator found when interpreting {0} to {1} in expression: {2}"),
            StateIsRequiredToUseItem = Of(
                "Runtime state is required to inject (or use) the: {0}. " + NewLine +
                "The reason is using RegisterDelegate, Use (or UseInstance), RegisterInitializer/Disposer, or registering with non-primitive service key, or metadata." + NewLine +
                "You can convert run-time value to expression via container.With(rules => rules.WithItemToExpressionConverter(YOUR_ITEM_TO_EXPRESSION_CONVERTER))."),
            ArgValueIndexIsProvidedButNoArgValues = Of(
                "`Arg.Index` is provided but no values are passed in Made.Of expression: " + NewLine +
                "{0}"),
            ArgValueIndexIsOutOfProvidedArgValues = Of(
                "`Arg.Index` {0} is outside of provided values [{1}] in Made.Of expression: " + NewLine +
                "{2}"),
            ResolutionNeedsRequiredServiceType = Of(
                "Expecting required service type but it is not specified when resolving: {0}"),
            RegisterMappingNotFoundRegisteredService = Of(
                "When registering mapping, Container is unable to find factory of registered service type {0} and key {1}."),
            RegisterMappingUnableToSelectFromMultipleFactories = Of(
                "RegisterMapping selected more than 1 factory with provided type {0} and key {1}: {2}"),
            RegisteringInstanceNotAssignableToServiceType = Of(
                "Registered instance of type {0} is not assignable to serviceType {1}."),
            NoMoreRegistrationsAllowed = Of(
                "Container does not allow further registrations." + NewLine +
                "Attempting to register {0}{1} with implementation factory {2}."),
            NoMoreUnregistrationsAllowed = Of(
                "Container does not allow further registry modification." + NewLine +
                "Attempting to Unregister {0}{1} with factory type {2}."),
            GotNullFactoryWhenResolvingService = Of(
                "Got null factory method when resolving {0}"),
            RegisteredDisposableTransientWontBeDisposedByContainer = Of(
                "Registered Disposable Transient service {0} with key {1} registered as {2} won't be disposed by container." +
                " DryIoc does not hold reference to resolved transients, and therefore does not control their dispose." +
                " To silence this exception Register<YourService>(setup: Setup.With(allowDisposableTransient: true)) " +
                " or set the rule Container(rules => rules.WithoutThrowOnRegisteringDisposableTransient())." +
                " To enable tracking use Register<YourService>(setup: Setup.With(trackDisposableTransient: true)) " +
                " or set the rule Container(rules => rules.WithTrackingDisposableTransients())"),
            NotFoundMetaCtorWithTwoArgs = Of(
                "Expecting Meta wrapper public constructor with two arguments {0} but not found when resolving: {1}"),
            UnableToSelectFromManyRegistrationsWithMatchingMetadata = Of(
                "Unable to select from multiple registrations matching the Metadata type {0}:" + NewLine +
                "{1}" + NewLine +
                "When resolving: {2}"),
            ImplTypeIsNotSpecifiedForAutoCtorSelection = Of(
                "Implementation type is not specified when using automatic constructor selection: {0}"),
            NoImplementationForPlaceholder = Of(
                "There is no real implementation, only a placeholder for the service {0}." + NewLine +
                "Please Register the implementation with the ifAlreadyRegistered.Replace parameter to fill the placeholder."),
            UnableToFindSingletonInstance = Of(
                "Expecting the instance to be stored in singleton scope, but unable to find anything here." + NewLine +
                "Likely, you've called UseInstance from the scoped container, but resolving from another container or injecting into a singleton."),
            DecoratorShouldNotBeRegisteredWithServiceKey = Of(
                "Registering Decorator {0} with service key {1} is not supported," + NewLine +
                "because instead of decorator with the key you actually want a decorator for service registered with the key." + NewLine +
                "To apply decorator for service with the key, please use `Setup.DecoratorOf(decorateeServiceKey: \"a service key\")`"),
            PassedCtorOrMemberIsNull = Of(
                "The constructor of member info passed to `Made.Of` or `FactoryMethod.Of` is null"),
            PassedMemberIsNotStaticButInstanceFactoryIsNull = Of(
                "The member info {0} passed to `Made.Of` or `FactoryMethod.Of` is NOT static, but instance factory is not provided or null"),
            PassedMemberIsStaticButInstanceFactoryIsNotNull = Of(
                "You are passing constructor or STATIC member info {0} to `Made.Of` or `FactoryMethod.Of`, but then why are you passing factory INSTANCE: {1}"),
            UndefinedMethodWhenGettingTheSingleMethod = Of(
                "Undefined Method '{0}' in Type {1} (including non-public={2})"),
            UndefinedMethodWhenGettingMethodWithSpecifiedParameters = Of(
                "Undefined Method '{0}' in Type {1} with parameters {2}."),
            UndefinedPropertyWhenGettingProperty = Of("Undefined property {0} in type {1}"),
            UndefinedFieldWhenGettingField = Of("Undefined field {0} in type {1}"),
            UnableToFindConstructorWithArgs = Of("Unable to find a constructor in Type {0} with args: {1}"),
            UnableToFindSingleConstructor = Of(
                "Unable to find a single constructor in Type {0} (including non-public={1})"),
            DisposerTrackForDisposeError = Of("Something is {0} already."),
            NoServicesWereRegisteredByRegisterMany = Of(
                "No service types were discovered in `RegisterMany` (or in `RegisterInstanceMany`) for the specified implementation types: " + NewLine +
                "[{0}]" + NewLine +
                "Maybe you missed the implementation or service type(s), " +
                "e.g. provided only abstract or compiler-generated implementation types, " +
                "or specified a wrong `serviceTypeCondition`," +
                "or did not specify to use `nonPublicServiceTypes`, etc."),
            FoundNoRootsToValidate = Of(
                "No roots to Validate found. Check the `condition` passed to Validate method for container: {0}" + NewLine +
                "You may also examine all container registrations via `container.container.GetServiceRegistrations()` method."),
            UnableToInterpretTheNestedLambda = Of(
                "Unable to interpret the nested lambda with Body:" + NewLine +
                "{0}")
            ;

#pragma warning restore 1591 // "Missing XML-comment"

        private static int Of(string message)
        {
            var errorIndex = Interlocked.Increment(ref _errorIndex);
            Messages[errorIndex] = message;
            return errorIndex;
        }

        /// <summary>Returns the name of error with the provided error code.</summary>
        public static string NameOf(int error) =>
            typeof(Error).GetTypeInfo().DeclaredFields
                .Where(f => f.FieldType == typeof(int)).Where((_, i) => i == error + 1)
                .FirstOrDefault()?.Name;

        static Error()
        {
            Throw.GetMatchedException = ContainerException.Of;
        }
    }

    /// <summary>Checked error condition, possible error sources.</summary>
    public enum ErrorCheck
    {
        /// <summary>Unspecified, just throw.</summary>
        Unspecified,
        /// <summary>Predicate evaluated to false.</summary>
        InvalidCondition,
        /// <summary>Checked object is null.</summary>
        IsNull,
        /// <summary>Checked object is of unexpected type.</summary>
        IsNotOfType,
        /// <summary>Checked type is not assignable to expected type</summary>
        TypeIsNotOfType,
        /// <summary>Invoked operation throws, it is source of inner exception.</summary>
        OperationThrows,
    }

    /// <summary>Enables more clean error message formatting and a bit of code contracts.</summary>
    public static class Throw
    {
        private static string[] CreateDefaultMessages()
        {
            var messages = new string[(int)ErrorCheck.OperationThrows + 1];
            messages[(int)ErrorCheck.Unspecified]      = "The error reason is unspecified, which is bad thing.";
            messages[(int)ErrorCheck.InvalidCondition] = "Argument {0} of type {1} has invalid condition.";
            messages[(int)ErrorCheck.IsNull]           = "Argument of type {0} is null.";
            messages[(int)ErrorCheck.IsNotOfType]      = "Argument {0} is not of type {1}.";
            messages[(int)ErrorCheck.TypeIsNotOfType]  = "Type argument {0} is not assignable from type {1}.";
            messages[(int)ErrorCheck.OperationThrows]  = "Invoked operation throws the inner exception {0}.";
            return messages;
        }

        private static readonly string[] _defaultMessages = CreateDefaultMessages();

        /// <summary>Returns the default message specified for <see cref="ErrorCheck"/> code.</summary>
        public static string GetDefaultMessage(ErrorCheck error) =>
            _defaultMessages[(int)error];

        /// <summary>Declares mapping between <see cref="ErrorCheck"/> type and <paramref name="error"/> code to specific <see cref="Exception"/>.</summary>
        public delegate Exception GetMatchedExceptionHandler(ErrorCheck errorCheck, int error, object arg0, object arg1, object arg2, object arg3, Exception inner);

        /// <summary>Returns matched exception for error check and error code.</summary>
        public static GetMatchedExceptionHandler GetMatchedException = ContainerException.Of;

        /// <summary>Throws matched exception with provided error code if throw condition is true.</summary>
        public static void If(bool throwCondition, int error = -1, object arg0 = null, object arg1 = null, object arg2 = null, object arg3 = null)
        {
            if (throwCondition)
                throw GetMatchedException(ErrorCheck.InvalidCondition, error, arg0, arg1, arg2, arg3, null);
        }

        /// <summary>Throws matched exception with provided error code if throw condition is true.
        /// Otherwise returns source <paramref name="arg0"/>.</summary>
        public static T ThrowIf<T>(this T arg0, bool throwCondition, int error = -1, object arg1 = null, object arg2 = null, object arg3 = null)
        {
            if (!throwCondition) return arg0;
            throw GetMatchedException(ErrorCheck.InvalidCondition, error, arg0, arg1, arg2, arg3, null);
        }

        /// <summary>Throws exception if <paramref name="arg"/> is null, otherwise returns <paramref name="arg"/>.</summary>
        public static T ThrowIfNull<T>(this T arg, int error = -1, object arg0 = null, object arg1 = null, object arg2 = null, object arg3 = null)
            where T : class
        {
            if (arg != null) return arg;
            throw GetMatchedException(ErrorCheck.IsNull, error, arg0 ?? typeof(T), arg1, arg2, arg3, null);
        }

        /// <summary>Throws exception if <paramref name="arg0"/> is not assignable to type specified by <paramref name="arg1"/>,
        /// otherwise just returns <paramref name="arg0"/>.</summary>
        public static T ThrowIfNotInstanceOf<T>(this T arg0, Type arg1, int error = -1, object arg2 = null, object arg3 = null)
            where T : class
        {
            if (arg1.IsTypeOf(arg0)) return arg0;
            throw GetMatchedException(ErrorCheck.IsNotOfType, error, arg0, arg1, arg2, arg3, null);
        }

        /// <summary>Throws if <paramref name="arg0"/> is not assignable from <paramref name="arg1"/>.</summary>
        public static Type ThrowIfNotImplementedBy(this Type arg0, Type arg1, int error = -1, object arg2 = null, object arg3 = null)
        {
            if (arg1.IsAssignableTo(arg0)) return arg0;
            throw GetMatchedException(ErrorCheck.TypeIsNotOfType, error, arg0, arg1, arg2, arg3, null);
        }

        /// <summary>Invokes <paramref name="operation"/> and in case of <typeparamref name="TEx"/> re-throws it as inner-exception.</summary>
        public static T IfThrows<TEx, T>(Func<T> operation, bool throwCondition, int error, object arg0 = null, object arg1 = null, object arg2 = null, object arg3 = null) where TEx : Exception
        {
            try
            {
                return operation();
            }
            catch (TEx ex)
            {
                if (throwCondition)
                    throw GetMatchedException(ErrorCheck.OperationThrows, error, arg0, arg1, arg2, arg3, ex);
                return default(T);
            }
        }

        /// <summary>Just throws the exception with the <paramref name="error"/> code.</summary>
        public static object It(int error, object arg0 = null, object arg1 = null, object arg2 = null, object arg3 = null)
        {
            throw GetMatchedException(ErrorCheck.Unspecified, error, arg0, arg1, arg2, arg3, null);
        }

        /// <summary>Throws <paramref name="error"/> instead of returning value of <typeparamref name="T"/>.
        /// Supposed to be used in expression that require some return value.</summary>
        public static T For<T>(int error, object arg0 = null, object arg1 = null, object arg2 = null, object arg3 = null)
        {
            throw GetMatchedException(ErrorCheck.Unspecified, error, arg0, arg1, arg2, arg3, null);
        }

        /// Throws if contidion is true, otherwise returns the `default(T)` value
        public static T For<T>(bool throwCondition, int error, 
            object arg0 = null, object arg1 = null, object arg2 = null, object arg3 = null)
        {
            if (!throwCondition) return default(T);
            throw GetMatchedException(ErrorCheck.Unspecified, error, arg0, arg1, arg2, arg3, null);
        }
    }

    /// <summary>Called from generated code.</summary>
    public static class ThrowInGeneratedCode
    {
        /// <summary>Throws if object is null.</summary>
        public static object WeakRefReuseWrapperGCed(this object obj)
        {
            if (obj == null) Throw.It(Error.WeakRefReuseWrapperGCed);
            return obj;
        }
    }

    /// <summary>Contains helper methods to work with Type: for instance to find Type implemented base types and interfaces, etc.</summary>
    public static class ReflectionTools
    {
#if SUPPORTS_DELEGATE_METHOD
        private static Lazy<Action<Exception>> _preserveExceptionStackTraceAction = new Lazy<Action<Exception>>(() =>
            typeof(Exception).GetSingleMethodOrNull("InternalPreserveStackTrace", true)
            ?.To(x => x.CreateDelegate(typeof(Action<Exception>)).To<Action<Exception>>()));

        /// <summary>Preserves the stack trace becfore re-throwing.</summary>
        public static void TryRethrowWithPreservedStackTrace(this Exception ex)
        {
            _preserveExceptionStackTraceAction.Value?.Invoke(ex);
            throw ex;
        }
#else
        /// <summary>Preserves the stack trace becfore re-throwing.</summary>
        public static void TryRethrowWithPreservedStackTrace(this Exception ex)
        {
            throw ex;
        }
#endif

        /// <summary>Flags for <see cref="GetImplementedTypes"/> method.</summary>
        [Flags]
        public enum AsImplementedType
        {
            /// <summary>Include nor object not source type.</summary>
            None = 0,
            /// <summary>Include source type to list of implemented types.</summary>
            SourceType = 1,
            /// <summary>Include <see cref="System.Object"/> type to list of implemented types.</summary>
            ObjectType = 2
        }

        /// <summary>Returns all interfaces and all base types (in that order) implemented by <paramref name="sourceType"/>.
        /// Specify <paramref name="asImplementedType"/> to include <paramref name="sourceType"/> itself as first item and
        /// <see cref="object"/> type as the last item.</summary>
        public static Type[] GetImplementedTypes(this Type sourceType, AsImplementedType asImplementedType = AsImplementedType.None)
        {
            Type[] results;

            var sourceTypeInfo = sourceType.GetTypeInfo();
            var interfaces = sourceTypeInfo.ImplementedInterfaces.ToArrayOrSelf();
            var interfaceStartIndex = (asImplementedType & AsImplementedType.SourceType) == 0 ? 0 : 1;
            var includingObjectType = (asImplementedType & AsImplementedType.ObjectType) == 0 ? 0 : 1;
            var sourcePlusInterfaceCount = interfaceStartIndex + interfaces.Length;

            var baseType = sourceTypeInfo.BaseType;
            if (baseType == null || baseType == typeof(object))
                results = new Type[sourcePlusInterfaceCount + includingObjectType];
            else
            {
                List<Type> baseBaseTypes = null;
                for (var bb = baseType.GetTypeInfo().BaseType; bb != null && bb != typeof(object); bb = bb.GetTypeInfo().BaseType)
                    (baseBaseTypes ?? (baseBaseTypes = new List<Type>(2))).Add(bb);

                if (baseBaseTypes == null)
                    results = new Type[sourcePlusInterfaceCount + includingObjectType + 1];
                else
                {
                    results = new Type[sourcePlusInterfaceCount + baseBaseTypes.Count + includingObjectType + 1];
                    baseBaseTypes.CopyTo(results, sourcePlusInterfaceCount + 1);
                }

                results[sourcePlusInterfaceCount] = baseType;
            }

            if (interfaces.Length == 1)
                results[interfaceStartIndex] = interfaces[0];
            else if (interfaces.Length > 1)
                Array.Copy(interfaces, 0, results, interfaceStartIndex, interfaces.Length);

            if (interfaceStartIndex == 1)
                results[0] = sourceType;
            if (includingObjectType == 1)
                results[results.Length - 1] = typeof(object);

            return results;
        }

        /// <summary>Gets a collection of the interfaces implemented by the current type and its base types.</summary>
        public static Type[] GetImplementedInterfaces(this Type type) =>
            type.GetTypeInfo().ImplementedInterfaces.ToArrayOrSelf();

        /// <summary>Gets all declared and if specified, the base members too.</summary>
        public static IEnumerable<MemberInfo> GetAllMembers(this Type type, bool includeBase = false) =>
            type.GetMembers(t =>
                t.DeclaredMethods.Cast<MemberInfo>().Concat(
                t.DeclaredProperties.Cast<MemberInfo>().Concat(
                t.DeclaredFields.Cast<MemberInfo>())),
                includeBase);

        /// <summary>Returns true if the <paramref name="openGenericType"/> contains all generic parameters
        /// from <paramref name="genericParameters"/>.</summary>
        public static bool ContainsAllGenericTypeParameters(this Type openGenericType, Type[] genericParameters)
        {
            if (!openGenericType.IsOpenGeneric())
                return false;

            var matchedParams = new Type[genericParameters.Length];
            Array.Copy(genericParameters, 0, matchedParams, 0, genericParameters.Length);

            ClearGenericParametersReferencedInConstraints(matchedParams);
            ClearMatchesFoundInGenericParameters(matchedParams, openGenericType.GetGenericParamsAndArgs());

            for (var i = 0; i < matchedParams.Length; i++)
                if (matchedParams[i] != null)
                    return false;
            return true;
        }

        /// <summary>Returns true if class is compiler generated. Checking for CompilerGeneratedAttribute
        /// is not enough, because this attribute is not applied for classes generated from "async/await".</summary>
        public static bool IsCompilerGenerated(this Type type) =>
            type.FullName != null && type.FullName.Contains("<>c__DisplayClass");

        /// <summary>Returns true if type is generic.</summary>
        public static bool IsGeneric(this Type type) =>
            type.GetTypeInfo().IsGenericType;

        /// <summary>Returns true if type is generic type definition (open type).</summary>
        public static bool IsGenericDefinition(this Type type) =>
            type.GetTypeInfo().IsGenericTypeDefinition;

        /// <summary>Returns true if type is closed generic: does not have open generic parameters, only closed/concrete ones.</summary>
        public static bool IsClosedGeneric(this Type type)
        {
            var typeInfo = type.GetTypeInfo();
            return typeInfo.IsGenericType && 
                   !typeInfo.IsGenericTypeDefinition && 
                   AreAllTypeArgumentsClosed(typeInfo.GetGenericParamsAndArgsUnsafe());
        }

        /// <summary>Returns true if type if open generic: contains at list one open generic parameter. Could be
        /// generic type definition as well.</summary>
        public static bool IsOpenGeneric(this Type type)
        {
            var typeInfo = type.GetTypeInfo();
            return typeInfo.IsGenericTypeDefinition || 
                   typeInfo.IsGenericType && 
                    !AreAllTypeArgumentsClosed(typeInfo.GetGenericParamsAndArgsUnsafe());
        }

        /// <summary>Checks that all type args are closed.</summary>
        internal static bool AreAllTypeArgumentsClosed(this Type[] typeArgs)
        {
            foreach (var typeArg in typeArgs)
            {
                if (typeArg.IsGenericParameter)
                    return false;
                var typeArgInfo = typeArg.GetTypeInfo();
                if (!typeArgInfo.IsGenericType)
                    continue;
                if (!AreAllTypeArgumentsClosed(typeArgInfo.GetGenericParamsAndArgsUnsafe()))
                    return false;
            }

            return true;
        }

        /// <summary>Returns generic type definition if type is generic and null otherwise.</summary>
        public static Type GetGenericDefinitionOrNull(this Type type) =>
            type != null && type.GetTypeInfo().IsGenericType ? type.GetGenericTypeDefinition() : null;

        /// <summary>Returns generic type parameters and arguments in order they specified. If type is not generic, returns empty array.</summary>
        public static Type[] GetGenericParamsAndArgs(this Type type)
        {
            var typeInfo = type.GetTypeInfo();
            return typeInfo.IsGenericTypeDefinition || typeInfo.IsGenericType 
                ? typeInfo.GetGenericParamsAndArgsUnsafe()
                : Empty<Type>();
        }

        [MethodImpl((MethodImplOptions)256)]
        internal static Type[] GetGenericParamsAndArgsUnsafe(this TypeInfo typeInfo)
        {
#if PCL || PCL259 || PCL328 || NET35 || NET40 || NET403 || NETSTANDARD1_0 || NETSTANDARD1_1 || NETSTANDARD1_2 || NETSTANDARD1_3 || NETSTANDARD1_4
            return typeInfo.IsGenericTypeDefinition ? typeInfo.GenericTypeParameters : typeInfo.GenericTypeArguments;
#else
            return typeInfo.GetGenericArguments();
#endif
        }

        /// <summary>Returns array of interface and base class constraints for provider generic parameter type.</summary>
        public static Type[] GetGenericParamConstraints(this Type type) =>
            type.GetTypeInfo().GetGenericParameterConstraints();

        /// <summary>If type is array returns is element type, otherwise returns null.</summary>
        /// <param name="type">Source type.</param> <returns>Array element type or null.</returns>
        public static Type GetArrayElementTypeOrNull(this Type type)
        {
            var typeInfo = type.GetTypeInfo();
            return typeInfo.IsArray ? typeInfo.GetElementType() : null;
        }

        /// <summary>Return base type or null, if not exist (the case for only for object type).</summary>
        public static Type GetBaseType(this Type type) =>
            type.GetTypeInfo().BaseType;

        /// <summary>Checks if type is public or nested public in public type.</summary>
        public static bool IsPublicOrNestedPublic(this Type type)
        {
            var ti = type.GetTypeInfo();
            return ti.IsPublic || ti.IsNestedPublic && ti.DeclaringType.IsPublicOrNestedPublic();
        }

        /// <summary>Returns true if type is class.</summary>
        public static bool IsClass(this Type type) =>
            type.GetTypeInfo().IsClass;

        /// <summary>Returns true if type is value type.</summary>
        public static bool IsValueType(this Type type) =>
            type.GetTypeInfo().IsValueType;

        /// <summary>Returns true if type is interface.</summary>
        public static bool IsInterface(this Type type) =>
            type.GetTypeInfo().IsInterface;

        /// <summary>Returns true if type if abstract or interface.</summary>
        public static bool IsAbstract(this Type type) =>
            type.GetTypeInfo().IsAbstract;

        /// <summary>Returns true if type is static.</summary>
        public static bool IsStatic(this Type type)
        {
            var typeInfo = type.GetTypeInfo();
            return typeInfo.IsAbstract && typeInfo.IsSealed;
        }

        /// <summary>Returns true if type is enum type.</summary>
        public static bool IsEnum(this Type type) =>
            type.GetTypeInfo().IsEnum;

        /// <summary>Returns true if type can be casted with conversion operators.</summary>
        public static bool HasConversionOperatorTo(this Type sourceType, Type targetType) =>
            (sourceType.FindConvertOperator(sourceType, targetType) ?? 
             targetType.FindConvertOperator(sourceType, targetType)) != null;

        /// Returns `target source.op_(Explicit|Implicit)(source)` or null if not found
        public static MethodInfo GetSourceConversionOperatorToTarget(this Type sourceType, Type targetType) => 
            sourceType.FindConvertOperator(sourceType, targetType);

        /// Returns `target target.op_(Explicit|Implicit)(source)` or null if not found
        public static MethodInfo GetTargetConversionOperatorFromSource(this Type sourceType, Type targetType) =>
            targetType.FindConvertOperator(sourceType, targetType);

        internal static MethodInfo FindConvertOperator(this Type type, Type sourceType, Type targetType)
        {
            var methods = type.GetTypeInfo().DeclaredMethods.ToArrayOrSelf();
            for (var i = 0; i < methods.Length; i++)
            {
                var m = methods[i];
                if (m.IsStatic && m.IsSpecialName && m.ReturnType == targetType)
                {
                    var n = m.Name;
                    // n == "op_Implicit" || n == "op_Explicit"
                    if (n.Length == 11 &&
                        n[2] == '_' && n[5] == 'p' && n[6] == 'l' && n[7] == 'i' && n[8] == 'c' && n[9] == 'i' && n[10] == 't' &&
                        m.GetParameters()[0].ParameterType == sourceType)
                        return m;
                }
            }

            return null;
        }

        /// <summary>Returns true if type is assignable to <paramref name="other"/> type.</summary>
        public static bool IsAssignableTo(this Type type, Type other) =>
            type != null && other != null && other.GetTypeInfo().IsAssignableFrom(type.GetTypeInfo());

        /// <summary>Returns true if type is assignable to <typeparamref name="T"/> type.</summary>
        public static bool IsAssignableTo<T>(this Type type) =>
            type != null && typeof(T).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo());

        /// <summary>Returns true if type of <paramref name="obj"/> is assignable to source <paramref name="type"/>.</summary>
        public static bool IsTypeOf(this Type type, object obj) =>
            obj != null && obj.GetType().IsAssignableTo(type);

        /// <summary>Returns true if provided type IsPrimitive in .Net terms, or enum, or string,
        /// or array of primitives if <paramref name="orArrayOfPrimitives"/> is true.</summary>
        public static bool IsPrimitive(this Type type, bool orArrayOfPrimitives = false)
        {
            var typeInfo = type.GetTypeInfo();
            return typeInfo.IsPrimitive || typeInfo.IsEnum || type == typeof(string)
                || orArrayOfPrimitives && typeInfo.IsArray && typeInfo.GetElementType().IsPrimitive(true);
        }

        /// <summary>Returns all attributes defined on <paramref name="type"/>.</summary>
        public static Attribute[] GetAttributes(this Type type, Type attributeType = null, bool inherit = false) =>
            type.GetTypeInfo().GetCustomAttributes(attributeType ?? typeof(Attribute), inherit)
                // ReSharper disable once RedundantEnumerableCastCall
                .Cast<Attribute>() // required in .NET 4.5
                .ToArrayOrSelf();

        /// <summary>Recursive method to enumerate all input type and its base types for specific details.
        /// Details are returned by <paramref name="getMembers"/> delegate.</summary>
        public static IEnumerable<TMember> GetMembers<TMember>(
            this Type type, Func<TypeInfo, IEnumerable<TMember>> getMembers, bool includeBase = false)
        {
            var typeInfo = type.GetTypeInfo();
            var members = getMembers(typeInfo);
            if (!includeBase || typeInfo.BaseType == null || typeInfo.BaseType == typeof(object))
                return members;
            return members.Append(typeInfo.BaseType.GetMembers(getMembers, true));
        }

        /// <summary>Returns all public instance constructors for the type</summary>
        public static IEnumerable<ConstructorInfo> PublicConstructors(this Type type)
        {
            foreach (var x in type.GetTypeInfo().DeclaredConstructors)
                if (x.IsPublic && !x.IsStatic)
                    yield return x;
        }

        /// <summary>Returns all public instance constructors for the type</summary>
        public static IEnumerable<ConstructorInfo> PublicAndInternalConstructors(this Type type)
        {
            foreach (var x in type.GetTypeInfo().DeclaredConstructors)
                if (!x.IsPrivate && !x.IsStatic)
                    yield return x;
        }

        //todo: [Obsolete("Prefer to use `GetInstanceConstructors` and `GetPublicInstanceConstructors`")]
        /// <summary>Enumerates all constructors from input type.</summary>
        public static IEnumerable<ConstructorInfo> Constructors(this Type type,
            bool includeNonPublic = false, bool includeStatic = false)
        {
            var ctors = type.GetTypeInfo().DeclaredConstructors.ToArrayOrSelf();
            if (ctors.Length == 0)
                return ctors;

            var ctor0 = ctors[0];
            var skip0 = !includeNonPublic && !ctor0.IsPublic || !includeStatic && ctor0.IsStatic;
            if (ctors.Length == 1)
                return skip0 ? ArrayTools.Empty<ConstructorInfo>() : ctors;

            if (ctors.Length == 2)
            {
                var ctor1 = ctors[1];
                var skip1 = !includeNonPublic && !ctor1.IsPublic || !includeStatic && ctor1.IsStatic;
                if (skip0 && skip1)
                    return ArrayTools.Empty<ConstructorInfo>();
                if (skip0)
                    return new[] { ctor1 };
                if (skip1)
                    return new[] { ctor0 };
                return ctors;
            }

            if (!includeNonPublic && !includeStatic)
                return ctors.Match(x => !x.IsStatic && x.IsPublic);
            if (!includeNonPublic)
                return ctors.Match(x => x.IsPublic);
            if (!includeStatic)
                return ctors.Match(x => !x.IsStatic);
            return ctors;
        }

        /// <summary>Returns public and non-public instance constructors.</summary>
        public static ConstructorInfo[] GetInstanceConstructors(this Type type)
        {
            var ctorsEnumerable = type.GetTypeInfo().DeclaredConstructors;
            var ctors = ctorsEnumerable as ConstructorInfo[] ?? ctorsEnumerable.ToArray(); 
            if (ctors.Length == 0)
                return ctors;

            var ctor0 = ctors[0];
            if (ctors.Length == 1)
                return ctor0.IsStatic ? ArrayTools.Empty<ConstructorInfo>() : ctors;

            if (ctors.Length == 2)
            {
                var ctor1 = ctors[1];
                if (ctor0.IsStatic && ctor1.IsStatic)
                    return ArrayTools.Empty<ConstructorInfo>();
                if (ctor0.IsStatic)
                    return new[] { ctor1 };
                if (ctor1.IsStatic)
                    return new[] { ctor0 };
                return ctors;
            }

            return ctors.MatchUnsafe(x => !x.IsStatic);
        }

        /// <summary>Returns public-only instance constructors.</summary>
        public static ConstructorInfo[] GetPublicInstanceConstructors(this Type type)
        {
            var ctorsEnumerable = type.GetTypeInfo().DeclaredConstructors;
            var ctors = ctorsEnumerable as ConstructorInfo[] ?? ctorsEnumerable.ToArray();
            if (ctors.Length == 0)
                return ctors;

            var ctor0 = ctors[0];
            var skip0 = !ctor0.IsPublic || ctor0.IsStatic;
            if (ctors.Length == 1)
                return skip0 ? ArrayTools.Empty<ConstructorInfo>() : ctors;

            if (ctors.Length == 2)
            {
                var ctor1 = ctors[1];
                var skip1 = !ctor1.IsPublic || ctor1.IsStatic;
                if (skip0 && skip1)
                    return ArrayTools.Empty<ConstructorInfo>();
                if (skip0)
                    return new[] { ctor1 };
                if (skip1)
                    return new[] { ctor0 };
                return ctors;
            }

            return ctors.MatchUnsafe(x => !x.IsStatic && x.IsPublic);
        }

        /// <summary>Searches and returns the first constructor by its signature, e.g. with the same number of parameters of the same type.</summary>
        public static ConstructorInfo GetConstructorOrNull(this Type type, bool includeNonPublic = false, params Type[] args)
        {
            var argsLength = args.Length;
            var ctors = includeNonPublic ? type.GetInstanceConstructors() : type.GetPublicInstanceConstructors();
            foreach (var ctor in ctors)
            {
                var ctorParams = ctor.GetParameters();
                if (ctorParams.Length == argsLength)
                {
                    var i = 0;
                    for (; i < argsLength; ++i)
                    {
                        var paramType = ctorParams[i].ParameterType;
                        if (paramType != args[i] && paramType.GetGenericDefinitionOrNull() != args[i])
                            break;
                    }

                    if (i == argsLength)
                        return ctor;
                }
            }

            return null;
        }

        /// <summary>Searches and returns constructor by its signature.</summary>
        public static ConstructorInfo GetConstructorOrNull(this Type type, params Type[] args) =>
            type.GetConstructorOrNull(true, args);

        /// <summary>Searches and returns constructor by its signature, or throws if not found</summary>
        public static ConstructorInfo Constructor(this Type type, params Type[] args) =>
            type.GetConstructorOrNull(includeNonPublic: true, args: args).ThrowIfNull(Error.UnableToFindConstructorWithArgs, type, args);

        /// <summary>Returns single constructor otherwise (if no constructor or more than one) returns null.</summary>
        public static ConstructorInfo GetSingleConstructorOrNull(this Type type, bool includeNonPublic = false)
        {
            ConstructorInfo foundCtor = null;
            if (includeNonPublic)
            {
                foreach (var ctor in type.GetTypeInfo().DeclaredConstructors)
                {
                    if (!ctor.IsStatic)
                    {
                        if (foundCtor != null)
                            return null;
                        foundCtor = ctor;
                    }
                }
            }
            else
            {
                foreach (var ctor in type.GetTypeInfo().DeclaredConstructors)
                {
                    if (!ctor.IsStatic && ctor.IsPublic)
                    {
                        if (foundCtor != null)
                            return null;
                        foundCtor = ctor;
                    }
                }
            }
            return foundCtor;
        }

        /// <summary>Returns single constructor otherwise (if no or more than one) throws an exception</summary>
        public static ConstructorInfo SingleConstructor(this Type type, bool includeNonPublic = false) =>
            type.GetSingleConstructorOrNull(includeNonPublic).ThrowIfNull(Error.UnableToFindSingleConstructor, type, includeNonPublic);

        /// <summary>Looks up for single declared method with the specified name. Returns null if method is not found.</summary>
        public static MethodInfo GetSingleMethodOrNull(this Type type, string name, bool includeNonPublic = false)
        {
            MethodInfo foundMethod = null;
            if (includeNonPublic)
            {
                foreach (var method in type.GetTypeInfo().DeclaredMethods)
                    if (method.Name == name)
                    {
                        if (foundMethod != null)
                            return null;
                        foundMethod = method;
                    }
            }
            else
            {
                foreach (var method in type.GetTypeInfo().DeclaredMethods)
                    if (method.IsPublic && method.Name == name)
                    {
                        if (foundMethod != null)
                            return null;
                        foundMethod = method;
                    }
            }
            return foundMethod;
        }

        /// <summary>Looks for single declared (not inherited) method by name, and throws if not found.</summary>
        public static MethodInfo SingleMethod(this Type type, string name, bool includeNonPublic = false) =>
            type.GetSingleMethodOrNull(name, includeNonPublic).ThrowIfNull(
                Error.UndefinedMethodWhenGettingTheSingleMethod, name, type, includeNonPublic);

        /// <summary>Looks up for method with and specified parameter types.</summary>
        public static MethodInfo Method(this Type type, string name, params Type[] args) =>
            type.GetMethodOrNull(name, args).ThrowIfNull(
                Error.UndefinedMethodWhenGettingMethodWithSpecifiedParameters, name, type, args);

        /// <summary>Looks up for method with and specified parameter types.</summary>
        public static MethodInfo GetMethodOrNull(this Type type, string name, params Type[] paramTypes)
        {
            var pTypesCount = paramTypes.Length;
            var methods = type.GetTypeInfo().DeclaredMethods.ToArrayOrSelf();
            foreach (var method in methods)
            {
                if (method.Name == name)
                {
                    var ps = method.GetParameters();
                    if (ps.Length == pTypesCount)
                    {
                        var p = 0;
                        for (; p < pTypesCount; ++p)
                        {
                            var paramType = ps[p].ParameterType;
                            if (paramType != paramTypes[p] && paramType.GetGenericDefinitionOrNull() != paramTypes[p])
                                break;
                        }

                        if (p == pTypesCount)
                            return method;
                    }
                }
            }

            return null;
        }

        /// <summary>Returns property by name, including inherited. Or null if not found.</summary>
        public static PropertyInfo Property(this Type type, string name, bool includeBase = false) =>
            type.GetPropertyOrNull(name, includeBase).ThrowIfNull(Error.UndefinedPropertyWhenGettingProperty, name, type);

        /// <summary>Returns property by name, including inherited. Or null if not found.</summary>
        public static PropertyInfo GetPropertyOrNull(this Type type, string name, bool includeBase = false)
        {
            var props = type.GetTypeInfo().DeclaredProperties.ToArrayOrSelf();
            for (var i = 0; i < props.Length; i++)
            {
                var p = props[i];
                if (p.Name == name)
                    return p;
            }

            return !includeBase ? null : type.GetTypeInfo().BaseType?.GetPropertyOrNull(name, includeBase);
        }

        /// <summary>Returns field by name, including inherited. Or null if not found.</summary>
        public static FieldInfo Field(this Type type, string name, bool includeBase = false) =>
            type.GetFieldOrNull(name, includeBase).ThrowIfNull(Error.UndefinedFieldWhenGettingField, name, type);

        /// <summary>Returns field by name, including inherited. Or null if not found.</summary>
        public static FieldInfo GetFieldOrNull(this Type type, string name, bool includeBase = false)
        {
            var fields = type.GetTypeInfo().DeclaredFields.ToArrayOrSelf();
            for (var i = 0; i < fields.Length; i++)
            {
                var f = fields[i];
                if (f.Name == name)
                    return f;
            }

            return !includeBase ? null : type.GetTypeInfo().BaseType?.GetFieldOrNull(name, includeBase);
        }

        /// <summary>Returns type assembly.</summary>
        public static Assembly GetAssembly(this Type type) => type.GetTypeInfo().Assembly;

        /// <summary>Is <c>true</c> for interface declared property explicitly implemented, e.g. <c>IInterface.Prop</c></summary>
        public static bool IsExplicitlyImplemented(this PropertyInfo property) => property.Name.Contains(".");

        /// <summary>Returns true if member is static, otherwise returns false.</summary>
        public static bool IsStatic(this MemberInfo member) =>
              member is MethodInfo method ? method.IsStatic
            : member is FieldInfo field ? field.IsStatic
            : member is PropertyInfo prop && !prop.IsExplicitlyImplemented() && prop.IsStatic(true);

        /// Find if property is static
        public static bool IsStatic(this PropertyInfo property, bool includeNonPublic = false)
        {
            // e.g.: set_Blah or get_Blah
            var propName = property.Name;
            var methods = property.DeclaringType.GetTypeInfo().DeclaredMethods.ToArrayOrSelf();
            for (var index = 0; index < methods.Length; index++)
            {
                var m = methods[index];
                if (m.IsSpecialName && (includeNonPublic || m.IsPublic))
                {
                    var name = m.Name;
                    var nameLength = name.Length;
                    if (nameLength > 4 && name[3] == '_' && nameLength - 4 == propName.Length)
                    {
                        var i = 4;
                        for (var j = 0; i < nameLength; i++, j++)
                            if (name[i] != propName[j])
                                break;
                        if (i == nameLength)
                            return m.IsStatic;
                    }
                }
            }

            return false;
        }

        /// <summary>Return either <see cref="PropertyInfo.PropertyType"/>, or <see cref="FieldInfo.FieldType"/>, 
        /// <see cref="MethodInfo.ReturnType"/>.</summary>
        public static Type GetReturnTypeOrDefault(this MemberInfo member) =>
            member is ConstructorInfo ? member.DeclaringType
            :  (member as MethodInfo)  ?.ReturnType 
            ?? (member as PropertyInfo)?.PropertyType 
            ?? (member as FieldInfo)   ?.FieldType;

        /// <summary>Returns true if field is backing field for property.</summary>
        public static bool IsBackingField(this FieldInfo field) =>
            field.Name[0] == '<';

        /// <summary>Returns true if property is indexer: aka this[].</summary>
        public static bool IsIndexer(this PropertyInfo property) =>
            property.GetIndexParameters().Length != 0;

        /// <summary>Returns true if type is generated type of hoisted closure.</summary>
        public static bool IsClosureType(this Type type) =>
            type.Name.Contains("<>c__DisplayClass");

        /// <summary>Returns attributes defined for the member/method.</summary>
        public static IEnumerable<Attribute> GetAttributes(this MemberInfo member, Type attributeType = null, bool inherit = false) =>
            member.GetCustomAttributes(attributeType ?? typeof(Attribute), inherit).Cast<Attribute>();

        /// <summary>Returns attributes defined for parameter.</summary>
        public static IEnumerable<Attribute> GetAttributes(this ParameterInfo parameter, Type attributeType = null, bool inherit = false) =>
            parameter.GetCustomAttributes(attributeType ?? typeof(Attribute), inherit).Cast<Attribute>();

        /// <summary>Get types from assembly that are loaded successfully.
        /// Hacks the <see cref="ReflectionTypeLoadException"/>to get failing to load types metadata.</summary>
        public static Type[] GetLoadedTypes(this Assembly assembly)
        {
            try
            {
                return Portable.GetAssemblyTypes(assembly).ToArrayOrSelf();
            }
            catch (ReflectionTypeLoadException ex)
            {
                return ex.Types.Where(type => type != null).ToArray();
            }
        }

        private static void ClearGenericParametersReferencedInConstraints(Type[] genericParams)
        {
            for (var i = 0; i < genericParams.Length; i++)
            {
                var genericParam = genericParams[i];
                if (genericParam == null)
                    continue;

                var genericConstraints = genericParam.GetGenericParamConstraints();
                for (var j = 0; j < genericConstraints.Length; j++)
                {
                    var genericConstraint = genericConstraints[j];
                    if (genericConstraint.IsOpenGeneric())
                    {
                        var constraintGenericParams = genericConstraint.GetGenericParamsAndArgs();
                        for (var k = 0; k < constraintGenericParams.Length; k++)
                        {
                            var constraintGenericParam = constraintGenericParams[k];
                            if (constraintGenericParam != genericParam)
                            {
                                for (var g = 0; g < genericParams.Length; ++g)
                                    if (genericParams[g] == constraintGenericParam)
                                    {
                                        genericParams[g] = null; // match
                                        break;
                                    }
                            }
                        }
                    }
                }
            }
        }

        private static void ClearMatchesFoundInGenericParameters(Type[] matchedParams, Type[] genericParams)
        {
            for (var i = 0; i < genericParams.Length; i++)
            {
                var genericParam = genericParams[i];
                if (genericParam.IsGenericParameter)
                {
                    for (var j = 0; j < matchedParams.Length; ++j)
                        if (matchedParams[j] == genericParam)
                        {
                            matchedParams[j] = null; // match
                            break;
                        }
                }
                else if (genericParam.IsOpenGeneric())
                    ClearMatchesFoundInGenericParameters(matchedParams, genericParam.GetGenericParamsAndArgs());
            }
        }

        internal static T GetDefault<T>() => default(T);
        internal static readonly MethodInfo GetDefaultMethod = 
            typeof(ReflectionTools).SingleMethod(nameof(GetDefault), true);

        /// <summary>Creates default(T) expression for provided <paramref name="type"/>.</summary>
        public static Expression GetDefaultValueExpression(this Type type) =>
            !type.IsValueType() ? Constant(null, type) : (Expression)Call(GetDefaultMethod.MakeGenericMethod(type), Empty<Expression>());
    }

    /// <summary>Provides pretty printing/debug view for number of types.</summary>
    public static class PrintTools
    {
        /// <summary>Default separator used for printing enumerable.</summary>
        public static string DefaultItemSeparator = ", " + NewLine;

        /// <summary>Prints input object by using corresponding Print methods for know types.</summary>
        /// <param name="s">Builder to append output to.</param> <param name="x">Object to print.</param>
        /// <param name="quote">(optional) Quote to use for quoting string object.</param>
        /// <param name="itemSeparator">(optional) Separator for enumerable.</param>
        /// <param name="getTypeName">(optional) Custom type printing policy.</param>
        /// <returns>String builder with appended output.</returns>
        public static StringBuilder Print(this StringBuilder s, object x,
            string quote = "\"", string itemSeparator = null, Func<Type, string> getTypeName = null) =>
            x == null ? s.Append("null")
            : x is string ? s.Print((string)x, quote)
            : x is Type ? s.Print((Type)x, getTypeName)
            : x is IPrintable ? ((IPrintable)x).Print(s, (b, p) => b.Print(p, quote, itemSeparator, getTypeName))
            : x is IScope || x is Request ? s.Append(x) // prevent recursion for IEnumerable
            : x.GetType().IsEnum() ? s.Print(x.GetType()).Append('.').Append(Enum.GetName(x.GetType(), x))
            : (x is IEnumerable<Type> || x is IEnumerable) &&
                !x.GetType().IsAssignableTo(typeof(IEnumerable<>).MakeGenericType(x.GetType())) // exclude infinite recursion and StackOverflowEx
                ? s.Print((IEnumerable)x, itemSeparator ?? DefaultItemSeparator, (_, o) => _.Print(o, quote, null, getTypeName))
            : s.Append(x);

        /// <summary>Appends string to string builder quoting with <paramref name="quote"/> if provided.</summary>
        /// <param name="s">String builder to append string to.</param> <param name="str">String to print.</param>
        /// <param name="quote">(optional) Quote to add before and after string.</param>
        /// <returns>String builder with appended string.</returns>
        public static StringBuilder Print(this StringBuilder s, string str, string quote = "\"") =>
            quote == null ? s.Append(str) : s.Append(quote).Append(str).Append(quote);

        /// <summary>Prints enumerable by using corresponding Print method for known item type.</summary>
        /// <param name="s">String builder to append output to.</param>
        /// <param name="items">Items to print.</param>
        /// <param name="separator">(optional) Custom separator if provided.</param>
        /// <param name="printItem">(optional) Custom item printer if provided.</param>
        /// <returns>String builder with appended output.</returns>
        public static StringBuilder Print(this StringBuilder s, IEnumerable items,
            string separator = ", ", Action<StringBuilder, object> printItem = null)
        {
            if (items == null)
                return s;
            printItem = printItem ?? ((_, x) => _.Print(x));
            var itemCount = 0;
            foreach (var item in items)
                printItem(itemCount++ == 0 ? s : s.Append(separator), item);
            return s;
        }

        /// <summary>Default delegate to print Type details: by default prints Type FullName and
        /// skips namespace if it start with "System."</summary>
        public static Func<Type, string> GetTypeNameDefault = t =>
#if DEBUG
            t.Name;
#else
            t.FullName != null && t.Namespace != null && !t.Namespace.StartsWith("System") ? t.FullName : t.Name;
#endif

        /// <summary>Pretty prints the <paramref name="type"/> in proper C# representation.
        /// <paramref name="getTypeName"/>Allows to specify if you want Name instead of FullName.</summary>
        public static StringBuilder Print(this StringBuilder s, Type type, Func<Type, string> getTypeName = null)
        {
            if (type == null)
                return s;

            var isArray = type.IsArray;
            if (isArray)
                type = type.GetElementType();

            var typeName = (getTypeName ?? GetTypeNameDefault).Invoke(type);

            if (!type.IsGeneric())
                return s.Append(typeName.Replace('+', '.'));

            var tickIndex = typeName.IndexOf('`');
            if (tickIndex != -1)
                typeName = typeName.Substring(0, tickIndex);

            s.Append(typeName.Replace('+', '.'));

            s.Append('<');
            var genericArgs = type.GetGenericParamsAndArgs();
            if (type.IsGenericDefinition())
                s.Append(',', genericArgs.Length - 1);
            else
                s.Print(genericArgs, ", ", (b, t) => b.Print((Type)t, getTypeName));
            s.Append('>');

            if (isArray)
                s.Append("[]");

            return s;
        }

        /// <summary>Pretty-prints the type</summary>
        public static string Print(this Type type, Func<Type, string> getTypeName = null) =>
            new StringBuilder().Print(type, getTypeName).ToString();
    }

    /// <summary>Ports some methods from .Net 4.0/4.5</summary>
    public static partial class Portable
    {
        /// <summary>Portable version of Assembly.GetTypes or Assembly.DefinedTypes.</summary>
        public static IEnumerable<Type> GetAssemblyTypes(Assembly a) => _getAssemblyTypes.Value(a);

        private static readonly Lazy<Func<Assembly, IEnumerable<Type>>> _getAssemblyTypes = Lazy.Of(GetAssemblyTypesMethod);
        private static Func<Assembly, IEnumerable<Type>> GetAssemblyTypesMethod()
        {
            var asmExpr = Parameter(typeof(Assembly), "a");

            var asmDefinedTypesProperty = typeof(Assembly).GetPropertyOrNull("DefinedTypes");

            var typesExpr = asmDefinedTypesProperty == null
                ? Call(asmExpr, typeof(Assembly).SingleMethod("GetTypes"), Empty<Expression>())
                : asmDefinedTypesProperty.PropertyType == typeof(IEnumerable<TypeInfo>)
                    ? Call(typeof(Portable).SingleMethod(nameof(ToTypes), true), Property(asmExpr, asmDefinedTypesProperty))
                    : (Expression)Property(asmExpr, asmDefinedTypesProperty);

            return FastExpressionCompiler.LightExpression.ExpressionCompiler.CompileFast(
                Lambda<Func<Assembly, IEnumerable<Type>>>(typesExpr, asmExpr));
        }

        internal static IEnumerable<Type> ToTypes(IEnumerable<TypeInfo> x) => x.Select(t => t.AsType());

        /// <summary>Portable version of PropertyInfo.GetGetMethod.</summary>
        public static MethodInfo GetGetMethodOrNull(this PropertyInfo p, bool includeNonPublic = false)
        {
            var name = "get_" + p.Name;
            var methods = p.DeclaringType.GetTypeInfo().DeclaredMethods.ToArrayOrSelf();
            for (var i = 0; i < methods.Length; i++)
            {
                var m = methods[i];
                if (m.IsSpecialName && (includeNonPublic || m.IsPublic) && m.Name == name)
                    return m;
            }

            return null;
        }

        /// <summary>Portable version of PropertyInfo.GetSetMethod.</summary>
        public static MethodInfo GetSetMethodOrNull(this PropertyInfo p, bool includeNonPublic = false)
        {
            var name = "set_" + p.Name;
            var methods = p.DeclaringType.GetTypeInfo().DeclaredMethods.ToArrayOrSelf();
            for (var i = 0; i < methods.Length; i++)
            {
                var m = methods[i];
                if (m.IsSpecialName && (includeNonPublic || m.IsPublic) && m.Name == name)
                    return m;
            }

            return null;
        }

        private static readonly Lazy<Func<int>> _getEnvCurrentManagedThreadId = Lazy.Of(() =>
        {
            var method = typeof(Environment).GetMethodOrNull("get_CurrentManagedThreadId", Empty<Type>());
            if (method == null)
                return null;

            return Lambda<Func<int>>(Call(method, Empty<Expression>()), Empty<ParameterExpression>())
#if SUPPORTS_FAST_EXPRESSION_COMPILER
                .ToLambdaExpression()
#endif
                .Compile();
        });

        /// <summary>Returns managed Thread ID either from Environment or Thread.CurrentThread whichever is available.</summary>
        [MethodImpl((MethodImplOptions)256)]
        public static int GetCurrentManagedThreadID()
        {
            var resultID = -1;
            GetCurrentManagedThreadID(ref resultID);
            if (resultID == -1)
                resultID = _getEnvCurrentManagedThreadId.Value();
            return resultID;
        }

        static partial void GetCurrentManagedThreadID(ref int threadID);
    }
} // end of DryIoc namespace

#if SUPPORTS_ASYNC_LOCAL
namespace DryIoc
{
    using System.Threading;

    /// <summary>Stores scopes propagating through async-await boundaries.</summary>
    public sealed class AsyncExecutionFlowScopeContext : IScopeContext
    {
        /// <summary>Statically known name of root scope in this context.</summary>
        public static readonly string ScopeContextName = typeof(AsyncExecutionFlowScopeContext).FullName;

        /// It is fine to use a default instance, cause the async local scope are actually a static one
        public static readonly AsyncExecutionFlowScopeContext Default = new AsyncExecutionFlowScopeContext();

        private static readonly AsyncLocal<IScope> _ambientScope = new AsyncLocal<IScope>();

        /// <summary>Returns current scope or null if no ambient scope available at the moment.</summary>
        /// <returns>Current scope or null.</returns>
        public IScope GetCurrentOrDefault() => _ambientScope.Value;

        /// <summary>Changes current scope using provided delegate. Delegate receives current scope as input and  should return new current scope.</summary>
        /// <param name="changeCurrentScope">Delegate to change the scope.</param>
        /// <remarks>Important: <paramref name="changeCurrentScope"/> may be called multiple times in concurrent environment.
        /// Make it predictable by removing any side effects.</remarks>
        /// <returns>New current scope. It is convenient to use method in "using (var newScope = ctx.SetCurrent(...))".</returns>
        public IScope SetCurrent(SetCurrentScopeHandler changeCurrentScope) =>
            _ambientScope.Value = changeCurrentScope(GetCurrentOrDefault());

        /// <summary>Nothing to dispose.</summary>
        public void Dispose() { }
    }
}
#endif
#if !SUPPORTS_ASYNC_LOCAL && SUPPORTS_SERIALIZABLE && !NETSTANDARD2_0
namespace DryIoc
{
    using System;
    using System.Threading;

    /// <summary>Stores scopes propagating through async-await boundaries.</summary>
    public sealed class AsyncExecutionFlowScopeContext : IScopeContext
    {
        /// <summary>Statically known name of root scope in this context.</summary>
        public static readonly string ScopeContextName = typeof(AsyncExecutionFlowScopeContext).FullName;

        /// It is fine to use a default instance, cause the async local scope are actually a static one
        public static readonly AsyncExecutionFlowScopeContext Default = new AsyncExecutionFlowScopeContext();

        [Serializable]
        internal sealed class ScopeEntry<T> : MarshalByRefObject
        {
            public readonly T Value;
            public ScopeEntry(T value) { Value = value; }
        }

        private static int _seedKey;
        private readonly string _scopeEntryKey = ScopeContextName + Interlocked.Increment(ref _seedKey);

        /// <summary>Returns current scope or null if no ambient scope available at the moment.</summary>
        /// <returns>Current scope or null.</returns>
        public IScope GetCurrentOrDefault() =>
            ((ScopeEntry<IScope>)System.Runtime.Remoting.Messaging.CallContext.LogicalGetData(_scopeEntryKey))?.Value;

        /// <summary>Changes current scope using provided delegate. Delegate receives current scope as input and  should return new current scope.</summary>
        /// <param name="changeCurrentScope">Delegate to change the scope.</param>
        /// <remarks>Important: <paramref name="changeCurrentScope"/> may be called multiple times in concurrent environment.
        /// Make it predictable by removing any side effects.</remarks>
        /// <returns>New current scope. It is convenient to use method in "using (var newScope = ctx.SetCurrent(...))".</returns>
        public IScope SetCurrent(SetCurrentScopeHandler changeCurrentScope)
        {
            var newScope = changeCurrentScope(GetCurrentOrDefault());
            var scopeEntry = newScope == null ? null : new ScopeEntry<IScope>(newScope);
            System.Runtime.Remoting.Messaging.CallContext.LogicalSetData(_scopeEntryKey, scopeEntry);
            return newScope;
        }

        /// <summary>Nothing to dispose.</summary>
        public void Dispose() { }
    }
}
#endif

#if !SUPPORTS_STACK_TRACE
namespace DryIoc
{
    internal class StackTrace
    {
        public override string ToString() => "<stack trace is not available on this platform>";
    }
}
#endif
#if PCL328 || NET35 || NET40 || NET403
namespace DryIoc
{
    using System.Threading;

    /// Something for portability
    public static partial class Portable
    {
        // ReSharper disable once RedundantAssignment
        static partial void GetCurrentManagedThreadID(ref int threadID)
        {
            threadID = Thread.CurrentThread.ManagedThreadId;
        }
    }
}

namespace System.Reflection
{
    using Collections.Generic;
    using Linq;

    /// <summary>Provides <see cref="GetTypeInfo"/> for the type.</summary>
    public static class TypeInfoTools
    {
        /// <summary>Wraps input type into <see cref="TypeInfo"/> structure.</summary>
        /// <param name="type">Input type.</param> <returns>Type info wrapper.</returns>
        public static TypeInfo GetTypeInfo(this Type type) => new TypeInfo(type);
    }

    /// <summary>Partial analog of TypeInfo existing in .NET 4.5 and higher.</summary>
    public struct TypeInfo
    {
        private readonly Type _type;

        /// <summary>Creates type info by wrapping input type.</summary> <param name="type">Type to wrap.</param>
        public TypeInfo(Type type)
        {
            _type = type;
        }

#pragma warning disable 1591 // "Missing XML-comment"
        public Type AsType() => _type;

        public Assembly Assembly => _type.Assembly;

        public MethodInfo GetDeclaredMethod(string name) => _type.GetMethod(name);
        public PropertyInfo GetDeclaredProperty(string name) => _type.GetProperty(name);
        public FieldInfo GetDeclaredField(string name) => _type.GetField(name);

        public IEnumerable<ConstructorInfo> DeclaredConstructors =>
            _type.GetConstructors(ALL_DECLARED ^ BindingFlags.Static);

        public IEnumerable<MemberInfo> DeclaredMembers =>
            _type.GetMembers(ALL_DECLARED);

        public IEnumerable<MethodInfo> DeclaredMethods =>
            _type.GetMethods(ALL_DECLARED);

        public IEnumerable<FieldInfo> DeclaredFields =>
            _type.GetFields(ALL_DECLARED);

        public IEnumerable<PropertyInfo> DeclaredProperties =>
            _type.GetProperties(ALL_DECLARED);

        public IEnumerable<Type> ImplementedInterfaces =>
            _type.GetInterfaces();

        public IEnumerable<Attribute> GetCustomAttributes(Type attributeType, bool inherit) =>
            _type.GetCustomAttributes(attributeType, inherit).Cast<Attribute>();

        public Type BaseType => _type.BaseType;
        public bool IsGenericType => _type.IsGenericType;
        public bool IsGenericTypeDefinition => _type.IsGenericTypeDefinition;
        public bool ContainsGenericParameters => _type.ContainsGenericParameters;
        public Type[] GenericTypeParameters => _type.GetGenericArguments();
        public Type[] GenericTypeArguments => _type.GetGenericArguments();

        public bool IsClass => _type.IsClass;
        public bool IsInterface => _type.IsInterface;
        public bool IsValueType => _type.IsValueType;
        public bool IsPrimitive => _type.IsPrimitive;
        public bool IsArray => _type.IsArray;
        public bool IsPublic => _type.IsPublic;
        public bool IsNestedPublic => _type.IsNestedPublic;
        public Type DeclaringType => _type.DeclaringType;
        public bool IsAbstract => _type.IsAbstract;
        public bool IsSealed => _type.IsSealed;
        public bool IsEnum => _type.IsEnum;

        public Type[] GetGenericParameterConstraints() => _type.GetGenericParameterConstraints();
        public Type GetElementType() => _type.GetElementType();

        public bool IsAssignableFrom(TypeInfo typeInfo) => _type.IsAssignableFrom(typeInfo.AsType());
#pragma warning restore 1591 // "Missing XML-comment"

        private const BindingFlags ALL_DECLARED =
            BindingFlags.Instance | BindingFlags.Static |
            BindingFlags.Public | BindingFlags.NonPublic |
            BindingFlags.DeclaredOnly;
    }
}
#endif

#if NET35
namespace System
{
    /// <summary>Func with 5 input parameters.</summary>
    public delegate TResult Func<T1, T2, T3, T4, T5, TResult>(
        T1 arg1,
        T2 arg2,
        T3 arg3,
        T4 arg4,
        T5 arg5);

    /// <summary>Func with 6 input parameters.</summary>
    public delegate TResult Func<T1, T2, T3, T4, T5, T6, TResult>(
        T1 arg1,
        T2 arg2,
        T3 arg3,
        T4 arg4,
        T5 arg5,
        T6 arg6);

    /// <summary>Func with 7 input parameters.</summary>
    public delegate TResult Func<T1, T2, T3, T4, T5, T6, T7, TResult>(
        T1 arg1,
        T2 arg2,
        T3 arg3,
        T4 arg4,
        T5 arg5,
        T6 arg6,
        T7 arg7);

    /// <summary>Action with 5 input parameters.</summary>
    public delegate void Action<T1, T2, T3, T4, T5>(
        T1 arg1,
        T2 arg2,
        T3 arg3,
        T4 arg4,
        T5 arg5);

    /// <summary>Action with 6 input parameters.</summary>
    public delegate void Action<T1, T2, T3, T4, T5, T6>(
        T1 arg1,
        T2 arg2,
        T3 arg3,
        T4 arg4,
        T5 arg5,
        T6 arg6);

    /// <summary>Action with 7 input parameters.</summary>
    public delegate void Action<T1, T2, T3, T4, T5, T6, T7>(
        T1 arg1,
        T2 arg2,
        T3 arg3,
        T4 arg4,
        T5 arg5,
        T6 arg6,
        T7 arg7);


    /// <summary>Wrapper for value computation required on-demand. Since computed the same value will be returned over and over again.</summary>
    /// <typeparam name="T">Type of value.</typeparam>
    public sealed class Lazy<T>
    {
        /// <summary>Creates lazy object with passed value computation delegate.</summary>
        /// <param name="valueFactory">Value computation. Will be stored until computation is done.</param>
        /// <exception cref="ArgumentNullException">Throws for null computation.</exception>
        public Lazy(Func<T> valueFactory)
        {
            if (valueFactory == null) throw new ArgumentNullException("valueFactory");
            _valueFactory = valueFactory;
        }

        /// <summary>Indicates if value is computed already, or not.</summary>
        public bool IsValueCreated { get; private set; }

        /// <summary>Computes value if it was not before, and returns it. 
        /// Value is guaranteed to be computed only once despite possible thread contention.</summary>
        /// <exception cref="InvalidOperationException">Throws if value computation is recursive.</exception>
        public T Value => IsValueCreated ? _value : Create();

#region Implementation

        private Func<T> _valueFactory;
        private T _value;
        private readonly object _valueCreationLock = new object();

        private T Create()
        {
            lock (_valueCreationLock)
            {
                if (!IsValueCreated)
                {
                    if (_valueFactory == null) throw new InvalidOperationException("The initialization function tries to access Value on this instance.");
                    var factory = _valueFactory;
                    _valueFactory = null;
                    _value = factory();
                    IsValueCreated = true;
                }
            }

            return _value;
        }

#endregion
    }

    /// <summary>Contains utility methods for creating and working with tuple.</summary>
    public static class Tuple
    {
        /// <summary>Creates a new 2-tuple, or pair. </summary>
        ///  <returns> A 2-tuple whose value is (<paramref name="item1"/>, <paramref name="item2"/>). </returns>
        /// <param name="item1">The value of the first component of the tuple.</param><param name="item2">The value of the second component of the tuple.</param><typeparam name="T1">The type of the first component of the tuple.</typeparam><typeparam name="T2">The type of the second component of the tuple.</typeparam>
        public static Tuple<T1, T2> Create<T1, T2>(T1 item1, T2 item2) => new Tuple<T1, T2>(item1, item2);
    }

    /// <summary>Represents a 2-tuple, or pair. </summary>
    /// <typeparam name="T1">The type of the tuple's first component.</typeparam><typeparam name="T2">The type of the tuple's second component.</typeparam><filterpriority>2</filterpriority>
    public sealed class Tuple<T1, T2>
    {
        private readonly T1 _item1;
        private readonly T2 _item2;

        /// <summary> Gets the value of the first component.</summary>
        public T1 Item1 => _item1;

        /// <summary> Gets the value of the current second component. </summary>
        public T2 Item2 => _item2;

        /// <summary>Initializes a new instance of the <see cref="T:System.Tuple`2"/> class.</summary>
        /// <param name="item1">The value of the tuple's first component.</param><param name="item2">The value of the tuple's second component.</param>
        public Tuple(T1 item1, T2 item2)
        {
            _item1 = item1;
            _item2 = item2;
        }

        /// <summary> Returns a value that indicates whether the current <see cref="T:System.Tuple`2"/> object is equal to a specified object.</summary>
        /// <param name="obj">The object to compare with this instance.</param>
        /// <returns> true if the current instance is equal to the specified object; otherwise, false. </returns>
        public override bool Equals(object obj)
        {
            var other = obj as Tuple<T1, T2>;
            return other != null && Equals(other.Item1, Item1) && Equals(other.Item2, Item2);
        }

        /// <summary> Returns the hash code for the current <see cref="T:System.Tuple`2"/> object. </summary>
        /// <returns> A 32-bit signed integer hash code.</returns>
        public override int GetHashCode()
        {
            var h1 = _item1 == null ? 0 : _item1.GetHashCode();
            var h2 = _item2 == null ? 0 : _item2.GetHashCode();
            return (h1 << 5) + h1 ^ h2;
        }

        /// <summary> Returns a string that represents the value of this <see cref="T:System.Tuple`2"/> instance. </summary>
        public override string ToString() => "(" + _item1 + ", " + _item2 + ")";
    }
}
#endif
#if SUPPORTS_VARIANCE
namespace DryIoc.Messages
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;

    /// Base type for messages
    public interface IMessage<out TResponse> { }

    /// Type for an empty response
    public struct EmptyResponse
    {
        /// Single value of empty response
        public static readonly EmptyResponse Value = new EmptyResponse();

        /// Single completed task for the empty response
        public static readonly Task<EmptyResponse> Task = System.Threading.Tasks.Task.FromResult(Value);
    }

    /// Message extensions
    public static class MessageExtensions
    {
        /// Converts the task to empty response task
        public static async Task<EmptyResponse> ToEmptyResponse(this Task task)
        {
            await task;
            return EmptyResponse.Value;
        }
    }

    /// Message with empty response
    public interface IMessage : IMessage<EmptyResponse> { }

    /// Base message handler
    public interface IMessageHandler<in M, R> where M : IMessage<R>
    {
        /// Generic handler
        Task<R> Handle(M message, CancellationToken cancellationToken);
    }

    /// Base message handler for message with empty response
    public interface IMessageHandler<in M> : IMessageHandler<M, EmptyResponse> where M : IMessage<EmptyResponse> { }

    /// Message handler middleware to handle the message and pass the result to the next middleware
    public interface IMessageMiddleware<in M, R>
    {
        /// `0` means the default registration order,
        /// lesser numbers incuding the `-1`, `-2` mean execute as a first,
        /// bigger numbers mean execute as a last
        int RelativeOrder { get; }

        /// Handles message and passes to the next middleware
        Task<R> Handle(M message, CancellationToken cancellationToken, Func<Task<R>> nextMiddleware);
    }

    /// Base class for implementing async handlers
    public abstract class AsyncMessageHandler<M, R> : IMessageHandler<M, R>
        where M : IMessage<R>
    {
        /// Base method to implement in your inheritor
        protected abstract Task<R> Handle(M message, CancellationToken cancellationToken);

        async Task<R> IMessageHandler<M, R>.Handle(M message, CancellationToken cancellationToken) =>
            await Handle(message, cancellationToken).ConfigureAwait(false);
    }

    /// Sequential middleware type of message handler decorator
    public class MiddlewareMessageHandler<M, R> : IMessageHandler<M, R> where M : IMessage<R>
    {
        private readonly IMessageHandler<M, R> _handler;
        private readonly IEnumerable<IMessageMiddleware<M, R>> _middlewares;

        /// Decorates message handler with optional middlewares
        public MiddlewareMessageHandler(IMessageHandler<M, R> handler, IEnumerable<IMessageMiddleware<M, R>> middlewares)
        {
            _handler = handler;
            _middlewares = middlewares;
        }

        /// Composes middlewares with handler
        public Task<R> Handle(M message, CancellationToken cancellationToken)
        {
            return _middlewares
                .OrderBy(x => x.RelativeOrder)
                .Reverse()
                .Aggregate(
                    new Func<Task<R>>(() => _handler.Handle(message, cancellationToken)),
                    (f, middleware) => () => middleware.Handle(message, cancellationToken, f))
                .Invoke();
        }
    }

    /// Broadcasting type of message handler decorator
    public class BroadcastMessageHandler<M>: IMessageHandler<M, EmptyResponse>
        where M : IMessage<EmptyResponse>
    {
        private readonly IEnumerable<IMessageHandler<M, EmptyResponse>> _handlers;

        /// Constructs the hub with the handler and optional middlewares
        public BroadcastMessageHandler(IEnumerable<IMessageHandler<M, EmptyResponse>> handlers) => 
            _handlers = handlers;

        /// Composes middlewares with handler
        public async Task<EmptyResponse> Handle(M message, CancellationToken cancellationToken)
        {
            await Task.WhenAll(_handlers.Select(h => h.Handle(message, cancellationToken)));
            return EmptyResponse.Value;
        }
    }

    /// A subject
    public class MessageMediator
    {
        private readonly IResolver _resolver;

        /// Constructs with resolver
        public MessageMediator(IResolver resolver) => 
            _resolver = resolver;

        /// Sends message with response to resolved handlers
        public Task<R> Send<M, R>(M message, CancellationToken cancellationToken) where M : IMessage<R> =>
            _resolver.Resolve<IMessageHandler<M, R>>().Handle(message, cancellationToken);

        /// Sends message with empty response to resolved handlers
        public Task Send<M>(M message, CancellationToken cancellationToken) where M : IMessage<EmptyResponse> =>
            _resolver.Resolve<IMessageHandler<M, EmptyResponse>>().Handle(message, cancellationToken);
    }
}
#endif
