diff --git a/Src/Commons.InversionOfControl.DryIoC/BoC.InversionOfControl.DryIoC.csproj b/Src/Commons.InversionOfControl.DryIoC/BoC.InversionOfControl.DryIoC.csproj index b0fe15a..2d0eaa4 100644 --- a/Src/Commons.InversionOfControl.DryIoC/BoC.InversionOfControl.DryIoC.csproj +++ b/Src/Commons.InversionOfControl.DryIoC/BoC.InversionOfControl.DryIoC.csproj @@ -46,7 +46,7 @@ - + diff --git a/Src/Commons.InversionOfControl.DryIoC/DryIoCDependencyResolver.cs b/Src/Commons.InversionOfControl.DryIoC/DryIoCDependencyResolver.cs index 4316bf5..bb3f21d 100644 --- a/Src/Commons.InversionOfControl.DryIoC/DryIoCDependencyResolver.cs +++ b/Src/Commons.InversionOfControl.DryIoC/DryIoCDependencyResolver.cs @@ -290,6 +290,8 @@ private IReuse GetReuse(LifetimeScope lifetimeScope) return Reuse.InWebRequest; case LifetimeScope.PerThread: return Reuse.InThread; + case LifetimeScope.Singleton: + return Reuse.Singleton; default: return Reuse.Transient; } diff --git a/Src/Commons.InversionOfControl.DryIoC/DryIoc/AsyncExecutionFlowScopeContext.cs b/Src/Commons.InversionOfControl.DryIoC/DryIoc/AsyncExecutionFlowScopeContext.cs index a65580d..5878a13 100644 --- a/Src/Commons.InversionOfControl.DryIoC/DryIoc/AsyncExecutionFlowScopeContext.cs +++ b/Src/Commons.InversionOfControl.DryIoC/DryIoc/AsyncExecutionFlowScopeContext.cs @@ -1,7 +1,7 @@ /* The MIT License (MIT) -Copyright (c) 2016 Maksim Volkau +Copyright (c) 2014-2016 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 diff --git a/Src/Commons.InversionOfControl.DryIoC/DryIoc/Container.cs b/Src/Commons.InversionOfControl.DryIoC/DryIoc/Container.cs index 3c2d9d2..091da35 100644 --- a/Src/Commons.InversionOfControl.DryIoC/DryIoc/Container.cs +++ b/Src/Commons.InversionOfControl.DryIoC/DryIoc/Container.cs @@ -1,7 +1,7 @@ -/* +/* The MIT License (MIT) -Copyright (c) 2016 Maksim Volkau +Copyright (c) 2013-2016 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 @@ -10,7 +10,7 @@ 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 AddOrUpdateServiceFactory +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 @@ -34,16 +34,17 @@ namespace DryIoc using System.Threading; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; + using ImTools; /// IoC Container. Documentation is available at https://bitbucket.org/dadhi/dryioc. public sealed partial class Container : IContainer, IScopeAccess { /// Creates new container with default rules . - public Container(): this(Rules.Default, Ref.Of(Registry.Default), new SingletonScope()) + public Container() : this(Rules.Default, Ref.Of(Registry.Default), new SingletonScope()) { } /// Creates new container, optionally providing to modify default container behavior. - /// (optional) Rules to modify container default resolution behavior. + /// (optional) Rules to modify container default resolution behavior. /// If not specified, then will be used. /// (optional) Scope context to use for , default is . public Container(Rules rules = null, IScopeContext scopeContext = null) @@ -61,13 +62,17 @@ public Container(Func configure, IScopeContext scopeContext = null public override string ToString() { var scope = ((IScopeAccess)this).GetCurrentScope(); - if (scope != null) - return "container with open scope: " + scope; - return "container"; + var scopeStr + = scope == null ? "Container" + : _scopeContext != null ? "Ambiently scoped container: " + scope + : "Scoped container: " + scope; + if (IsDisposed) + scopeStr = "Disposed (!) " + scopeStr; + return scopeStr; } /// Shares all of container state except Cache and specifies new rules. - /// (optional) Configure rules, if not specified then uses Rules from current container. + /// (optional) Configure rules, if not specified then uses Rules from current container. /// (optional) New scope context, if not specified then uses context from current container. /// New container. public IContainer With(Func configure = null, IScopeContext scopeContext = null) @@ -76,7 +81,7 @@ public IContainer With(Func configure = null, IScopeContext scopeC var rules = configure == null ? Rules : configure(Rules); scopeContext = scopeContext ?? _scopeContext; var registryWithoutCache = Ref.Of(_registry.Value.WithoutCache()); - return new Container(rules, registryWithoutCache, _singletonScope, scopeContext, _openedScope, _disposed); + return new Container(rules, registryWithoutCache, _singletonScope, scopeContext, _openedScope, _disposed, RootContainer); } /// Produces new container which prevents any further registrations. @@ -86,7 +91,7 @@ public IContainer With(Func configure = null, IScopeContext scopeC public IContainer WithNoMoreRegistrationAllowed(bool ignoreInsteadOfThrow = false) { var readonlyRegistry = Ref.Of(_registry.Value.WithNoMoreRegistrationAllowed(ignoreInsteadOfThrow)); - return new Container(Rules, readonlyRegistry, _singletonScope, _scopeContext, _openedScope, _disposed); + return new Container(Rules, readonlyRegistry, _singletonScope, _scopeContext, _openedScope, _disposed, RootContainer); } /// Returns new container with all expression, delegate, items cache removed/reset. @@ -96,7 +101,7 @@ public IContainer WithoutCache() { ThrowIfContainerDisposed(); var registryWithoutCache = Ref.Of(_registry.Value.WithoutCache()); - return new Container(Rules, registryWithoutCache, _singletonScope, _scopeContext, _openedScope, _disposed); + return new Container(Rules, registryWithoutCache, _singletonScope, _scopeContext, _openedScope, _disposed, RootContainer); } /// Creates new container with state shared with original except singletons and cache. @@ -107,7 +112,7 @@ public IContainer WithoutSingletonsAndCache() ThrowIfContainerDisposed(); var registryWithoutCache = Ref.Of(_registry.Value.WithoutCache()); var newSingletons = new SingletonScope(); - return new Container(Rules, registryWithoutCache, newSingletons, _scopeContext, _openedScope, _disposed); + return new Container(Rules, registryWithoutCache, newSingletons, _scopeContext, _openedScope, _disposed, RootContainer); } /// Shares all parts with original container But copies registration, so the new registration @@ -118,7 +123,7 @@ public IContainer WithRegistrationsCopy(bool preserveCache = false) { ThrowIfContainerDisposed(); var newRegistry = preserveCache ? _registry.NewRef() : Ref.Of(_registry.Value.WithoutCache()); - return new Container(Rules, newRegistry, _singletonScope, _scopeContext, _openedScope, _disposed); + return new Container(Rules, newRegistry, _singletonScope, _scopeContext, _openedScope, _disposed, RootContainer); } /// Returns ambient scope context associated with container. @@ -129,7 +134,7 @@ public IContainer WithRegistrationsCopy(bool preserveCache = false) /// In case of previous open scope, new open scope references old one as a parent. /// /// (optional) Name for opened scope to allow reuse to identify the scope. - /// (optional) Configure rules, if not specified then uses Rules from current container. + /// (optional) Configure rules, if not specified then uses Rules from current container. /// New container with different current scope. /// configure = n nestedOpenedScope.ThrowIf(scope != _openedScope, Error.NotDirectScopeParent, _openedScope, scope)); var rules = configure == null ? Rules : configure(Rules); - return new Container(rules, _registry, _singletonScope, _scopeContext, nestedOpenedScope, _disposed); + + return new Container(rules, _registry, _singletonScope, _scopeContext, nestedOpenedScope, _disposed, RootContainer ?? this); } /// The default name of root scope without ambient context. public static readonly object NonAmbientRootScopeName = "NonAmbientRootScope"; /// Creates container (facade) that fallbacks to this container for unresolved services. - /// Facade is the new empty container, with the same rules and scope context as current container. + /// Facade is the new empty container, with the same rules and scope context as current container. /// It could be used for instance to create Test facade over original container with replacing some services with test ones. /// Singletons from container are not reused by facade - when you resolve singleton directly from parent and then ask for it from child, it will return another object. /// To achieve that you may use with . @@ -173,26 +179,45 @@ public IContainer CreateFacade() return new Container(Rules.WithFallbackContainer(this), _scopeContext); } + /// Clears cache for specified service(s). + /// But does not clear instances of already resolved/created singletons and scoped services! + /// Target service type. + /// (optional) If not specified, clears cache for all . + /// (optional) If omitted, the cache will be cleared for all resgitrations of . + /// True if target service was found, false - otherwise. + public bool ClearCache(Type serviceType, FactoryType? factoryType = null, object serviceKey = null) + { + if (factoryType != null) + return _registry.Value.ClearCache(serviceType, serviceKey, factoryType.Value); + + var registry = _registry.Value; + + var clearedServices = registry.ClearCache(serviceType, serviceKey, FactoryType.Service); + var clearWrapper = registry.ClearCache(serviceType, serviceKey, FactoryType.Wrapper); + var clearDecorator = registry.ClearCache(serviceType, serviceKey, FactoryType.Decorator); + + return clearedServices || clearWrapper || clearDecorator; + } + /// Dispose either open scope, or container with singletons, if no scope opened. public void Dispose() { + if (Interlocked.CompareExchange(ref _disposed, 1, 0) != 0) + return; + // for container created with OpenScope - if (_openedScope != null && + if (_openedScope != null && !(Rules.ImplicitOpenedRootScope && _openedScope.Parent == null && _scopeContext == null)) { - if (_openedScope == null) - return; - - _openedScope.Dispose(); - - if (_scopeContext != null) - _scopeContext.SetCurrent(scope => scope == _openedScope ? scope.Parent : scope); + if (_openedScope != null) + { + _openedScope.Dispose(); + if (_scopeContext != null) + _scopeContext.SetCurrent(scope => scope == _openedScope ? scope.Parent : scope); + } } else // whole Container with singletons. { - if (Interlocked.CompareExchange(ref _disposed, 1, 0) != 0) - return; - if (_openedScope != null) _openedScope.Dispose(); @@ -202,14 +227,21 @@ public void Dispose() _singletonScope.Dispose(); _registry.Swap(Registry.Empty); - _defaultFactoryDelegateCache = Ref.Of(ImTreeMap.Empty); + _defaultFactoryDelegateCache = Ref.Of(ImMap.Empty); Rules = Rules.Default; } } + /// Scope containing container singletons. + public IScope SingletonScope { get { return _singletonScope; } } + + /// Returns root for scoped container or null for root itself. + public readonly Container RootContainer; + #region Static state + // todo: v3: remove /// State parameter expression in FactoryDelegate. public static readonly ParameterExpression StateParamExpr = Expression.Parameter(typeof(object[]), "state"); @@ -222,42 +254,44 @@ public void Dispose() public static readonly Expression ResolverExpr = Expression.Property(ResolverContextParamExpr, "Resolver"); + /// Resolver parameter expression in FactoryDelegate. + public static readonly Expression RootResolverExpr = + Expression.Call(typeof(ResolverContext), "RootResolver", ArrayTools.Empty(), ResolverContextParamExpr); + + /// Returns or based on request. + public static Expression GetResolverExpr(Request request) + { + return request.IsSingletonOrDependencyOfSingleton ? RootResolverExpr : ResolverExpr; + } + + /// Resolver parameter expression in FactoryDelegate. + public static readonly Expression SingletonScopeExpr = + Expression.Call(typeof(ResolverContext), "SingletonScope", ArrayTools.Empty(), ResolverContextParamExpr); + /// Access to scopes in FactoryDelegate. public static readonly Expression ScopesExpr = Expression.Property(ResolverContextParamExpr, "Scopes"); - /// Resolution scope parameter expression in FactoryDelegate. - public static readonly ParameterExpression ResolutionScopeParamExpr = - Expression.Parameter(typeof(IScope), "scope"); + /// Resolver parameter expression in FactoryDelegate. + public static readonly Expression RootScopesExpr = + Expression.Call(typeof(ResolverContext), "RootScopes", ArrayTools.Empty(), ResolverContextParamExpr); - /// Creates state item access expression given item index. - /// State items actually are singleton items. So that method knows about Singleton items structure. - /// Index of item. - /// Expression. - public static Expression GetStateItemExpression(int itemIndex) + /// Returns or based on request. + public static Expression GetScopesExpr(Request request) { - var bucketSize = SingletonScope.BucketSize; - if (itemIndex < bucketSize) - return Expression.ArrayIndex(StateParamExpr, Expression.Constant(itemIndex, typeof(int))); - - // Result: ((object[][])_singletonItems[0])[itemIndex/bucketSize][itemIndex%bucketSize]; - var bucketsExpr = Expression.Convert( - Expression.ArrayIndex(StateParamExpr, Expression.Constant(0, typeof(int))), - typeof(object[][])); - - var bucketExpr = Expression.ArrayIndex(bucketsExpr, - Expression.Constant(itemIndex / bucketSize - 1, typeof(int))); - - return Expression.ArrayIndex(bucketExpr, - Expression.Constant(itemIndex % bucketSize, typeof(int))); + return request.IsSingletonOrDependencyOfSingleton ? RootScopesExpr : ScopesExpr; } + /// Resolution scope parameter expression in FactoryDelegate. + public static readonly ParameterExpression ResolutionScopeParamExpr = + Expression.Parameter(typeof(IScope), "scope"); + internal static Expression GetResolutionScopeExpression(Request request) { if (request.Scope != null) return ResolutionScopeParamExpr; - // the only situation when we could here is: where are in first resolution call (in the root) + // the only situation when we could be here: the root resolution call // and scope was not created on the boundary of Resolve call. var parent = request.Enumerate().Last(); @@ -267,21 +301,21 @@ internal static Expression GetResolutionScopeExpression(Request request) var parentServiceKeyExpr = container.GetOrAddStateItemExpression(parent.ServiceKey, typeof(object)); // if assign in expression is supported then use it. + var scopesExpr = GetScopesExpr(request); if (_expressionAssignMethod != null) { - var getOrNewScopeExpr = Expression.Call(ScopesExpr, "GetOrNewResolutionScope", + var getOrNewScopeExpr = Expression.Call(scopesExpr, "GetOrNewResolutionScope", ArrayTools.Empty(), ResolutionScopeParamExpr, parentServiceTypeExpr, parentServiceKeyExpr); var parameters = new object[] { ResolutionScopeParamExpr, getOrNewScopeExpr }; - return (Expression)_expressionAssignMethod.Invoke(null, parameters); + return (Expression)_expressionAssignMethod.Value.Invoke(null, parameters); } - return Expression.Call(ScopesExpr, "GetOrCreateResolutionScope", + return Expression.Call(scopesExpr, "GetOrCreateResolutionScope", ArrayTools.Empty(), ResolutionScopeParamExpr, parentServiceTypeExpr, parentServiceKeyExpr); } - private static readonly MethodInfo _expressionAssignMethod = - typeof(Expression).GetMethodOrNull("Assign", typeof(Expression), typeof(Expression)); - + private static readonly Lazy _expressionAssignMethod = new Lazy(() => + typeof(Expression).GetMethodOrNull("Assign", typeof(Expression), typeof(Expression))); #endregion @@ -301,10 +335,10 @@ public IEnumerable GetServiceRegistrations() /// Type of service to resolve later. /// (optional) Service key of any type with and /// implemented. - /// (optional) Says how to handle existing registration with the same + /// (optional) Says how to handle existing registration with the same /// and . /// Confirms that service and implementation types are statically checked by compiler. - /// True if factory was added to registry, false otherwise. + /// True if factory was added to registry, false otherwise. /// False may be in case of setting and already existing factory. public void Register(Factory factory, Type serviceType, object serviceKey, IfAlreadyRegistered ifAlreadyRegistered, bool isStaticallyChecked) { @@ -314,8 +348,8 @@ public void Register(Factory factory, Type serviceType, object serviceKey, IfAlr if (ifAlreadyRegistered == IfAlreadyRegistered.AppendNotKeyed) ifAlreadyRegistered = Rules.DefaultIfAlreadyRegistered; - // Improves performance a bit by attempt to swapping registry while it is still unchanged, - // if attempt fails then fallback to normal Swap with retry. + // Improves performance a bit by attempt to swapping registry while it is still unchanged, + // if attempt fails then fallback to normal Swap with retry. var registry = _registry.Value; if (!_registry.TrySwapIfStillCurrent(registry, registry.Register(factory, serviceType, ifAlreadyRegistered, serviceKey))) _registry.Swap(r => r.Register(factory, serviceType, ifAlreadyRegistered, serviceKey)); @@ -330,18 +364,23 @@ private void ThrowIfInvalidRegistration(Factory factory, Type serviceType, objec serviceType.ThrowIfNull(); factory.ThrowIfNull(); } - + var setup = factory.Setup; if (setup.FactoryType != FactoryType.Wrapper) { - if (factory.Reuse == null && !factory.Setup.UseParentReuse && - (factory.ImplementationType ?? serviceType).IsAssignableTo(typeof(IDisposable)) && - setup.AllowDisposableTransient == false && Rules.ThrowOnRegisteringDisposableTransient) - Throw.It(Error.RegisteredDisposableTransientWontBeDisposedByContainer, serviceType, - serviceKey ?? "{no key}", this); + if ((factory.Reuse ?? Rules.DefaultReuseInsteadOfTransient) == Reuse.Transient && + !factory.Setup.UseParentReuse && + !(factory.FactoryType == FactoryType.Decorator && ((Setup.DecoratorSetup)factory.Setup).UseDecorateeReuse) && + (factory.ImplementationType ?? serviceType).IsAssignableTo(typeof(IDisposable)) && + !setup.AllowDisposableTransient && Rules.ThrowOnRegisteringDisposableTransient) + { + Throw.It(Error.RegisteredDisposableTransientWontBeDisposedByContainer, + serviceType, serviceKey ?? "{no key}", factory); + } } - else if (serviceType.IsGeneric() && - !((Setup.WrapperSetup)setup).AlwaysWrapsRequiredServiceType) + else if (serviceType.IsGeneric() && + !((Setup.WrapperSetup)setup).AlwaysWrapsRequiredServiceType && + ((Setup.WrapperSetup)setup).Unwrap == null) { var typeArgCount = serviceType.GetGenericParamsAndArgs().Length; var typeArgIndex = ((Setup.WrapperSetup)setup).WrappedServiceTypeArgIndex; @@ -360,9 +399,11 @@ private void ThrowIfInvalidRegistration(Factory factory, Type serviceType, objec var ctors = reflectionFactory.ImplementationType.GetPublicInstanceConstructors().ToArrayOrSelf(); if (ctors.Length != 1) Throw.It(Error.NoDefinedMethodToSelectFromMultipleConstructors, reflectionFactory.ImplementationType, ctors); + else + reflectionFactory.SetKnownSingleCtor(ctors[0]); } - if (!isStaticallyChecked && + if (!isStaticallyChecked && reflectionFactory.ImplementationType != null) { var implType = reflectionFactory.ImplementationType; @@ -403,8 +444,10 @@ private void ThrowIfInvalidRegistration(Factory factory, Type serviceType, objec implementedTypes.Where(t => t.GetGenericDefinitionOrNull() == serviceType)); } else if (implType.IsGeneric() && serviceType.IsOpenGeneric()) + { Throw.It(Error.RegisteringNotAGenericTypedefServiceType, serviceType, serviceType.GetGenericDefinitionOrNull()); + } else Throw.It(Error.RegisteringOpenGenericImplWithNonGenericService, implType, serviceType); } @@ -424,10 +467,11 @@ private void ThrowIfInvalidRegistration(Factory factory, Type serviceType, objec public bool IsRegistered(Type serviceType, object serviceKey, FactoryType factoryType, Func condition) { ThrowIfContainerDisposed(); - return _registry.Value.IsRegistered(serviceType.ThrowIfNull(), serviceKey, factoryType, condition); + var factories = _registry.Value.GetRegisteredFactories(serviceType.ThrowIfNull(), serviceKey, factoryType, condition); + return !factories.IsNullOrEmpty(); } - /// Removes specified factory from registry. + /// Removes specified factory from registry. /// Factory is removed only from registry, if there is relevant cache, it will be kept. /// Use to remove all the cache. /// Service type to look for. @@ -443,6 +487,31 @@ public void Unregister(Type serviceType, object serviceKey, FactoryType factoryT #endregion + #region Direct Container Resolve methods to avoid interface dispatch + + /// Returns instance of type. + /// The type of the requested service. + /// The requested service instance. + [MethodImpl(MethodImplHints.AggressingInlining)] + public TService Resolve() + { + return (TService)Resolve(typeof(TService)); + } + + /// Resolves default (non-keyed) service from container and returns created service object. + /// Service type to search and to return. + /// The requested service instance. + [MethodImpl(MethodImplHints.AggressingInlining)] + public object Resolve(Type serviceType) + { + var factoryDelegate = _defaultFactoryDelegateCache.Value.GetValueOrDefault(serviceType); + return factoryDelegate != null + ? factoryDelegate(null, _thisContainerWeakRef, null) + : ResolveAndCacheDefaultDelegate(serviceType, false, null); + } + + #endregion + #region IResolver [MethodImpl(MethodImplHints.AggressingInlining)] @@ -450,26 +519,28 @@ object IResolver.Resolve(Type serviceType, bool ifUnresolvedReturnDefault) { var factoryDelegate = _defaultFactoryDelegateCache.Value.GetValueOrDefault(serviceType); return factoryDelegate != null - ? factoryDelegate(_singletonItems, _containerWeakRef, null) + ? factoryDelegate(null, _thisContainerWeakRef, null) : ResolveAndCacheDefaultDelegate(serviceType, ifUnresolvedReturnDefault, null); } - object IResolver.Resolve(Type serviceType, object serviceKey, bool ifUnresolvedReturnDefault, Type requiredServiceType, RequestInfo preResolveParent, IScope scope) + object IResolver.Resolve(Type serviceType, object serviceKey, bool ifUnresolvedReturnDefault, Type requiredServiceType, RequestInfo preResolveParent, + IScope scope) { preResolveParent = preResolveParent ?? RequestInfo.Empty; - object cacheEntryKey = serviceType; - if (serviceKey != null) - cacheEntryKey = new KV(cacheEntryKey, serviceKey); + var cacheEntryKey = serviceKey == null + ? (object)serviceType + : new KV(serviceType, serviceKey); object cacheContextKey = requiredServiceType; if (!preResolveParent.IsEmpty) - cacheContextKey = cacheContextKey == null ? (object)preResolveParent - : new KV(cacheContextKey, preResolveParent); + cacheContextKey = cacheContextKey == null + ? (object)preResolveParent + : KV.Of(cacheContextKey, preResolveParent); // Check cache first: - var registryValue = _registry.Value; - var cacheRef = registryValue.KeyedFactoryDelegateCache; + var registry = _registry.Value; + var cacheRef = registry.KeyedFactoryDelegateCache; var cacheEntry = cacheRef.Value.GetValueOrDefault(cacheEntryKey); if (cacheEntry != null) { @@ -478,32 +549,44 @@ object IResolver.Resolve(Type serviceType, object serviceKey, bool ifUnresolvedR : (cacheEntry.Value ?? ImTreeMap.Empty).GetValueOrDefault(cacheContextKey); if (cachedFactoryDelegate != null) - return cachedFactoryDelegate(_singletonItems, _containerWeakRef, scope); + return cachedFactoryDelegate(null, _thisContainerWeakRef, scope); } // Cache is missed, so get the factory and put it into cache: ThrowIfContainerDisposed(); var ifUnresolved = ifUnresolvedReturnDefault ? IfUnresolved.ReturnDefault : IfUnresolved.Throw; - var request = _emptyRequest.Push(serviceType, serviceKey, ifUnresolved, requiredServiceType, scope, preResolveParent); + var request = Request.Create(this, serviceType, serviceKey, ifUnresolved, requiredServiceType, scope, + preResolveParent); + var factory = ((IContainer)this).ResolveFactory(request); - var factoryDelegate = factory == null ? null : factory.GetDelegateOrDefault(request); + + // Hack: may mutate (set) not null request service key. + if (serviceKey == null && request.ServiceKey != null) + cacheEntryKey = new KV(serviceType, request.ServiceKey); + + if (factory == null) + return null; + + var factoryDelegate = factory.GetDelegateOrDefault(request); if (factoryDelegate == null) return null; - var service = factoryDelegate(_singletonItems, _containerWeakRef, scope); + var service = factoryDelegate(null, _thisContainerWeakRef, scope); - if (registryValue.Services.IsEmpty) + if (registry.Services.IsEmpty) return service; - // Cache factory Only after we successfully got the service from it. - var cachedContextFactories = (cacheEntry == null ? null : cacheEntry.Value) ?? ImTreeMap.Empty; + // Cache factory only when we successfully called the factory delegate, to prevent failing delegates to be cached. + // Additionally disable caching when: + // no services registered, so the service probably empty collection wrapper or alike. + var cachedContextFactories = + (cacheEntry == null ? null : cacheEntry.Value) ?? + ImTreeMap.Empty; + if (cacheContextKey == null) - cacheEntry = new KV>( - factoryDelegate, - cachedContextFactories); + cacheEntry = KV.Of(factoryDelegate, cachedContextFactories); else - cacheEntry = new KV>( - cacheEntry == null ? null : cacheEntry.Key, + cacheEntry = KV.Of(cacheEntry == null ? null : cacheEntry.Key, cachedContextFactories.AddOrUpdate(cacheContextKey, factoryDelegate)); var cacheVal = cacheRef.Value; @@ -518,8 +601,8 @@ private object ResolveAndCacheDefaultDelegate(Type serviceType, bool ifUnresolve ThrowIfContainerDisposed(); var ifUnresolved = ifUnresolvedReturnDefault ? IfUnresolved.ReturnDefault : IfUnresolved.Throw; - var request = _emptyRequest.Push(serviceType, ifUnresolved: ifUnresolved, scope: scope); - var factory = ((IContainer)this).ResolveFactory(request); // NOTE may mutate request + var request = Request.Create(this, serviceType, ifUnresolved: ifUnresolved, scope: scope); + var factory = ((IContainer)this).ResolveFactory(request); // HACK: may mutate request, but it should be safe // The situation is possible for multiple default services registered. if (request.ServiceKey != null) @@ -530,10 +613,10 @@ private object ResolveAndCacheDefaultDelegate(Type serviceType, bool ifUnresolve return null; var registryValue = _registry.Value; - var service = factoryDelegate(_singletonItems, _containerWeakRef, scope); + var service = factoryDelegate(null, _thisContainerWeakRef, scope); - // Caching disabled only if: - // - if no services were registered, so the service probably empty collection wrapper or alike. + // Additionally disable caching when: + // no services registered, so the service probably empty collection wrapper or alike. if (!registryValue.Services.IsEmpty) { var cacheRef = registryValue.DefaultFactoryDelegateCache; @@ -547,14 +630,20 @@ private object ResolveAndCacheDefaultDelegate(Type serviceType, bool ifUnresolve // todo: v3: remove unused composite key and required type parameters IEnumerable IResolver.ResolveMany( - Type serviceType, object serviceKey, Type requiredServiceType, - object compositeParentKey, Type compositeParentRequiredType, + Type serviceType, object serviceKey, Type requiredServiceType, + object compositeParentKey, Type compositeParentRequiredType, RequestInfo preResolveParent, IScope scope) { - preResolveParent = preResolveParent ?? RequestInfo.Empty; + var requiredItemType = requiredServiceType ?? serviceType; + + // Emulating the collection parent so that collection related rules and conditions were applied + // the same way as if resolving IEnumerable + if (preResolveParent == null || preResolveParent.IsEmpty) + preResolveParent = RequestInfo.Empty.Push( + typeof(IEnumerable), requiredItemType, serviceKey, IfUnresolved.Throw, + 0, FactoryType.Wrapper, implementationType: null, reuse: null, flags: RequestFlags.IsServiceCollection); var container = (IContainer)this; - var requiredItemType = requiredServiceType ?? serviceType; var items = container.GetAllServiceFactories(requiredItemType); IEnumerable openGenericItems = null; @@ -565,7 +654,7 @@ private object ResolveAndCacheDefaultDelegate(Type serviceType, bool ifUnresolve .Select(x => new ServiceRegistrationInfo(x.Value, requiredItemOpenGenericType, x.Key)); } - // Append registered generic types with compatible variance, + // Append registered generic types with compatible variance, // e.g. for IHandler - IHandler is compatible with IHandler if B : A. IEnumerable variantGenericItems = null; if (requiredItemType.IsGeneric() && container.Rules.VariantGenericTypesInResolvedCollection) @@ -595,15 +684,17 @@ private object ResolveAndCacheDefaultDelegate(Type serviceType, bool ifUnresolve variantGenericItems = variantGenericItems.Where(it => it.Factory.Setup.MatchesMetadata(metadataKey, metadata)); } - // exclude composite parent from items - var parent = preResolveParent.FactoryType != FactoryType.Wrapper - ? preResolveParent : preResolveParent.Parent; + // Exclude composite parent service from items, skip decorators + var parent = preResolveParent; + if (parent.FactoryType != FactoryType.Service) + parent = parent.Enumerate().FirstOrDefault(p => p.FactoryType == FactoryType.Service) ?? RequestInfo.Empty; + if (!parent.IsEmpty && parent.GetActualServiceType() == requiredItemType) { items = items.Where(x => x.Value.FactoryID != parent.FactoryID); if (openGenericItems != null) - openGenericItems = openGenericItems.Where(x => + openGenericItems = openGenericItems.Where(x => !x.Factory.FactoryGenerator.GeneratedFactories.Enumerate().Any(f => f.Value.FactoryID == parent.FactoryID && f.Key.Key == parent.ServiceType && f.Key.Value == parent.ServiceKey)); @@ -612,10 +703,9 @@ private object ResolveAndCacheDefaultDelegate(Type serviceType, bool ifUnresolve variantGenericItems = variantGenericItems.Where(x => x.Factory.FactoryID != parent.FactoryID); } - IResolver resolver = this; foreach (var item in items) { - var service = resolver.Resolve(serviceType, item.Key, + var service = container.Resolve(serviceType, item.Key, true, requiredServiceType, preResolveParent, scope); if (service != null) // skip unresolved items yield return service; @@ -625,7 +715,7 @@ private object ResolveAndCacheDefaultDelegate(Type serviceType, bool ifUnresolve foreach (var item in openGenericItems) { var serviceKeyWithOpenGenericRequiredType = new[] { item.ServiceType, item.OptionalServiceKey }; - var service = resolver.Resolve(serviceType, serviceKeyWithOpenGenericRequiredType, + var service = container.Resolve(serviceType, serviceKeyWithOpenGenericRequiredType, true, requiredItemType, preResolveParent, scope); if (service != null) // skip unresolved items yield return service; @@ -634,7 +724,7 @@ private object ResolveAndCacheDefaultDelegate(Type serviceType, bool ifUnresolve if (variantGenericItems != null) foreach (var item in variantGenericItems) { - var service = resolver.Resolve(serviceType, item.OptionalServiceKey, + var service = container.Resolve(serviceType, item.OptionalServiceKey, true, item.ServiceType, preResolveParent, scope); if (service != null) // skip unresolved items yield return service; @@ -663,20 +753,14 @@ private void ThrowIfContainerDisposed() #endregion - #region IResolverContext - - /// Scope containing container singletons. - IScope IScopeAccess.SingletonScope - { - get { return _singletonScope; } - } + #region IScopeAccess IScope IScopeAccess.GetCurrentScope() { return ((IScopeAccess)this).GetCurrentNamedScope(null, false); } - /// Gets current scope matching the . + /// Gets current scope matching the . /// If name is null then current scope is returned, or if there is no current scope then exception thrown. /// May be null Says to throw if no scope found. /// Found scope or throws exception. @@ -687,7 +771,7 @@ IScope IScopeAccess.GetCurrentNamedScope(object name, bool throwIfNotFound) return currentScope == null ? (throwIfNotFound ? Throw.For(Error.NoCurrentScope) : null) : GetMatchingScopeOrDefault(currentScope, name) - ?? (throwIfNotFound ? Throw.For(Error.NoMatchedScopeFound, name) : null); + ?? (throwIfNotFound ? Throw.For(Error.NoMatchedScopeFound, currentScope, name) : null); } private static IScope GetMatchingScopeOrDefault(IScope scope, object name) @@ -701,7 +785,7 @@ private static IScope GetMatchingScopeOrDefault(IScope scope, object name) // note: The method required for .NET 3.5 which does not have Expression.Assign, so the need for "ref" parameter (BTW "ref" is not supported in XAMARIN) /// Check if scope is not null, then just returns it, otherwise will create and return it. /// May be null scope. - /// Marking scope with resolved service type. + /// Marking scope with resolved service type. /// Marking scope with resolved service key. /// Input ensuring it is not null. IScope IScopeAccess.GetOrCreateResolutionScope(ref IScope scope, Type serviceType, object serviceKey) @@ -711,7 +795,7 @@ IScope IScopeAccess.GetOrCreateResolutionScope(ref IScope scope, Type serviceTyp /// Check if scope is not null, then just returns it, otherwise will create and return it. /// May be null scope. - /// Marking scope with resolved service type. + /// Marking scope with resolved service type. /// Marking scope with resolved service key. /// Input ensuring it is not null. public IScope GetOrNewResolutionScope(IScope scope, Type serviceType, object serviceKey) @@ -719,9 +803,9 @@ public IScope GetOrNewResolutionScope(IScope scope, Type serviceType, object ser return scope ?? new Scope(null, new KV(serviceType, serviceKey)); } - /// If both and are null, + /// If both and are null, /// then returns input . - /// Otherwise searches scope hierarchy to find first scope with: Type assignable and + /// Otherwise searches scope hierarchy to find first scope with: Type assignable and /// Key equal to . /// Scope to start matching with Type and Key specified. /// Type to match. Key to match. @@ -731,12 +815,12 @@ public IScope GetOrNewResolutionScope(IScope scope, Type serviceType, object ser IScope IScopeAccess.GetMatchingResolutionScope(IScope scope, Type assignableFromServiceType, object serviceKey, bool outermost, bool throwIfNotFound) { - return GetMatchingScopeOrDefault(scope, assignableFromServiceType, serviceKey, outermost) + return FindMatchingResolutionScope(scope, assignableFromServiceType, serviceKey, outermost) ?? (!throwIfNotFound ? null - : Throw.For(Error.NoMatchedScopeFound, new KV(assignableFromServiceType, serviceKey))); + : Throw.For(Error.NoMatchedScopeFound, scope, new KV(assignableFromServiceType, serviceKey))); } - private static IScope GetMatchingScopeOrDefault(IScope scope, Type assignableFromServiceType, object serviceKey, + private static IScope FindMatchingResolutionScope(IScope scope, Type assignableFromServiceType, object serviceKey, bool outermost) { if (assignableFromServiceType == null && serviceKey == null) @@ -746,8 +830,11 @@ public IScope GetOrNewResolutionScope(IScope scope, Type serviceType, object ser while (scope != null) { var name = scope.Name as KV; - if (name != null && - (assignableFromServiceType == null || name.Key.IsAssignableTo(assignableFromServiceType)) && + if (name != null && ( + assignableFromServiceType == null || + name.Key.IsAssignableTo(assignableFromServiceType) || + assignableFromServiceType.IsOpenGeneric() && + name.Key.GetGenericDefinitionOrNull().IsAssignableTo(assignableFromServiceType)) && (serviceKey == null || serviceKey.Equals(name.Value))) { matchedScope = scope; @@ -770,19 +857,21 @@ public IScope GetOrNewResolutionScope(IScope scope, Type serviceType, object ser /// Indicates that container is disposed. public bool IsDisposed { - get { return _disposed == 1; } + get { return _disposed == 1 || _singletonScope.IsDisposed; } } - /// Empty request bound to container. All other requests are created by pushing to empty request. + // todo: v3: remove + /// Obsolete: replaced with /. Request IContainer.EmptyRequest { - get { return _emptyRequest; } + get { return Request.CreateEmpty(this); } } + // todo: v3: Rename to ResolverContext /// Self weak reference, with readable message when container is GCed/Disposed. ContainerWeakRef IContainer.ContainerWeakRef { - get { return _containerWeakRef; } + get { return _thisContainerWeakRef; } } Factory IContainer.ResolveFactory(Request request) @@ -801,7 +890,7 @@ Factory IContainer.ResolveFactory(Request request) } if (factory != null && factory.FactoryGenerator != null) - factory = factory.FactoryGenerator.GetGeneratedFactoryOrDefault(request); + factory = factory.FactoryGenerator.GetGeneratedFactory(request); if (factory == null && request.IfUnresolved == IfUnresolved.Throw) ThrowUnableToResolve(request); @@ -868,9 +957,19 @@ Factory IContainer.GetServiceFactoryOrDefault(Request request) { var openGenericEntry = serviceFactories.GetValueOrDefault(serviceType.GetGenericTypeDefinition()); if (openGenericEntry != null) + factories = factories.Concat(GetRegistryEntryKeyFactoryPairs(openGenericEntry)); + } + + var dynamicRegistrationProviders = Rules.DynamicRegistrationProviders; + if (!dynamicRegistrationProviders.IsNullOrEmpty()) + { + for (var i = 0; i < dynamicRegistrationProviders.Length; i++) { - var openGenericFactories = GetRegistryEntryKeyFactoryPairs(openGenericEntry).ToArray(); - factories = factories.Concat(openGenericFactories); + var provider = dynamicRegistrationProviders[i]; + var dynamicFactories = provider(serviceType, null, FactoryType.Service); + // todo: Do I need to filter for FactoryType.Service? + if (dynamicFactories != null) + factories = factories.Concat(dynamicFactories); } } @@ -888,17 +987,27 @@ Expression IContainer.GetDecoratorExpressionOrDefault(Request request) { // return early if no decorators registered and no fallback containers to provide them if (_registry.Value.Decorators.IsEmpty && - request.Container.Rules.FallbackContainers.IsNullOrEmpty()) + request.Rules.FallbackContainers.IsNullOrEmpty()) return null; - var serviceType = request.ServiceType; - var container = request.Container; + var arrayElementType = request.ServiceType.GetArrayElementTypeOrNull(); + if (arrayElementType != null) + request = request.WithChangedServiceInfo(info => info + .With(typeof(IEnumerable<>).MakeGenericType(arrayElementType))); // Define the list of ids for the already applied decorators int[] appliedDecoratorIDs = null; - // Get decorators for the service type + var container = request.Container; + + var serviceType = request.ServiceType; 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)); + if (!decorators.IsNullOrEmpty()) { appliedDecoratorIDs = GetAppliedDecoratorIDs(request); @@ -909,16 +1018,25 @@ Expression IContainer.GetDecoratorExpressionOrDefault(Request request) // Append open-generic decorators var genericDecorators = ArrayTools.Empty(); - var openGenericServiceType = serviceType.GetGenericDefinitionOrNull(); - if (openGenericServiceType != null) - genericDecorators = container.GetDecoratorFactoriesOrDefault(openGenericServiceType); + var openGenServiceType = serviceType.GetGenericDefinitionOrNull(); + if (openGenServiceType != null) + genericDecorators = container.GetDecoratorFactoriesOrDefault(openGenServiceType); + + // Combine with open-generic required type if they are different from service type + if (requiredServiceType != serviceType) + { + var openGenRequiredType = requiredServiceType.GetGenericDefinitionOrNull(); + if (openGenRequiredType != null && openGenRequiredType != openGenServiceType) + genericDecorators = genericDecorators.Append(container.GetDecoratorFactoriesOrDefault(openGenRequiredType)); + } // Append generic type argument decorators, registered as Object // Note: the condition for type args should be checked before generating the closed generic version var typeArgDecorators = container.GetDecoratorFactoriesOrDefault(typeof(object)); if (!typeArgDecorators.IsNullOrEmpty()) - genericDecorators = genericDecorators.Append( - typeArgDecorators.Where(f => f.CheckCondition(request)).ToArrayOrSelf()); + genericDecorators = genericDecorators.Append(typeArgDecorators + .Where(d => d.CheckCondition(request)) + .ToArrayOrSelf()); // Filter out already applied generic decorators // And combine with rest of decorators @@ -927,7 +1045,7 @@ Expression IContainer.GetDecoratorExpressionOrDefault(Request request) appliedDecoratorIDs = appliedDecoratorIDs ?? GetAppliedDecoratorIDs(request); if (!appliedDecoratorIDs.IsNullOrEmpty()) genericDecorators = genericDecorators - .Where(d => d.FactoryGenerator == null + .Where(d => d.FactoryGenerator == null ? appliedDecoratorIDs.IndexOf(d.FactoryID) == -1 : d.FactoryGenerator.GeneratedFactories.Enumerate() .All(f => appliedDecoratorIDs.IndexOf(f.Value.FactoryID) == -1)) @@ -937,15 +1055,15 @@ Expression IContainer.GetDecoratorExpressionOrDefault(Request request) if (!genericDecorators.IsNullOrEmpty()) { genericDecorators = genericDecorators - .Select(d => d.FactoryGenerator == null ? d - : d.FactoryGenerator.GetGeneratedFactoryOrDefault(request)) + .Select(d => d.FactoryGenerator == null ? d + : d.FactoryGenerator.GetGeneratedFactory(request)) .Where(d => d != null) .ToArrayOrSelf(); decorators = decorators.Append(genericDecorators); } } - // Filter out the recursive decorators: + // Filter out the recursive decorators: // the decorator with the same which was applied before up to the root if (!decorators.IsNullOrEmpty()) { @@ -980,13 +1098,24 @@ Expression IContainer.GetDecoratorExpressionOrDefault(Request request) .FirstOrDefault(d => d.CheckCondition(request)); } - return decorator == null ? null : decorator.GetExpressionOrDefault(request); + if (decorator == null) + return null; + + 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 = Expression.Call(typeof(Enumerable), "ToArray", new[] { arrayElementType }, decoratorExpr); + + return decoratorExpr; } private static int[] GetAppliedDecoratorIDs(Request request) { var parent = request.ParentOrWrapper; - return parent.IsEmpty + return parent.IsEmpty ? ArrayTools.Empty() : parent.Enumerate() .TakeWhile(p => p.FactoryType != FactoryType.Service) // until the another service @@ -997,7 +1126,7 @@ private static int[] GetAppliedDecoratorIDs(Request request) Factory IContainer.GetWrapperFactoryOrDefault(Type serviceType) { - return _registry.Value.GetWrapperOrDefault(serviceType); + return _registry.Value.Wrappers.GetValueOrDefault(serviceType.GetGenericDefinitionOrNull() ?? serviceType); } Factory[] IContainer.GetDecoratorFactoriesOrDefault(Type serviceType) @@ -1047,10 +1176,10 @@ Type IContainer.GetWrappedType(Type serviceType, Type requiredServiceType) /// For given instance resolves and sets properties and fields. /// Service instance with properties to resolve and initialize. /// (optional) Function to select properties and fields, overrides all other rules if specified. - /// If not specified then method will use container , + /// If not specified then method will use container , /// or if not specified method fallbacks to . /// Instance with assigned properties and fields. - /// Different Rules could be combined together using method. + /// Different Rules could be combined together using method. public object InjectPropertiesAndFields(object instance, PropertiesAndFieldsSelector propertiesAndFields) { propertiesAndFields = propertiesAndFields @@ -1059,9 +1188,11 @@ public object InjectPropertiesAndFields(object instance, PropertiesAndFieldsSele var instanceType = instance.ThrowIfNull().GetType(); - var resolver = (IResolver)this; - var request = _emptyRequest.Push(instanceType).WithResolvedFactory(new InstanceFactory(instance, null, Setup.Default)); + var request = Request.Create(this, instanceType) + .WithResolvedFactory(new UsedInstanceFactory(instanceType)); + var requestInfo = request.RequestInfo; + var resolver = (IResolver)this; foreach (var serviceInfo in propertiesAndFields(request)) if (serviceInfo != null) @@ -1073,7 +1204,8 @@ public object InjectPropertiesAndFields(object instance, PropertiesAndFieldsSele details.ServiceKey, details.IfUnresolved == IfUnresolved.ReturnDefault, details.RequiredServiceType, - preResolveParent: requestInfo, scope: null); + preResolveParent: requestInfo, + scope: null); if (value != null) serviceInfo.SetValue(instance, value); @@ -1104,31 +1236,42 @@ public Expression GetCachedFactoryExpressionOrDefault(int factoryID) return _registry.Value.FactoryExpressionCache.Value.GetValueOrDefault(factoryID) as Expression; } + // todo: v3: remove /// State item objects which may include: singleton instances for fast access, reuses, reuse wrappers, factory delegates, etc. public object[] ResolutionStateCache { - get { return _singletonItems; } - } - - /// Adds item if it is not already added to singleton state, returns added or existing item index. - /// Item to find in existing items with or add if not found. - /// Index of found or added item. - public int GetOrAddStateItem(object item) - { - return _singletonScope.GetOrAdd(item); + get { return null; } } - /// If possible wraps added item in (possible for primitive type, Type, strings), - /// otherwise invokes and wraps access to added item (by returned index) into expression: state => state.Get(index). - /// Item to wrap or to add. (optional) Specific type of item, otherwise item . - /// (optional) Enable filtering of stateful items. + /// Converts known items into custom expression or wraps in . + /// Item to convert. + /// (optional) Type of item, otherwise item . + /// (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. /// Returns constant or state access expression for added items. public Expression GetOrAddStateItemExpression(object item, Type itemType = null, bool throwIfStateRequired = false) { - itemType = itemType ?? (item == null ? typeof(object) : item.GetType()); - var primitiveExpr = GetPrimitiveOrArrayExprOrDefault(item, itemType); - if (primitiveExpr != null) - return primitiveExpr; + if (item == null) + return itemType != null + ? Expression.Constant(null, itemType) + : Expression.Constant(null); + + if (item is DefaultKey) + return Expression.Call(typeof(DefaultKey), "Of", ArrayTools.Empty(), + Expression.Constant(((DefaultKey)item).RegistrationOrder)); + + itemType = itemType ?? item.GetType(); + if (itemType.IsPrimitive() || + itemType.IsAssignableTo(typeof(Type))) + return Expression.Constant(item, itemType); + + if (itemType.IsArray) + { + var elemType = itemType.GetElementType().ThrowIfNull(); + var elems = ((IEnumerable)item).Cast().Select(it => GetOrAddStateItemExpression(it, null, throwIfStateRequired)); + var elemExprs = Expression.NewArrayInit(elemType, elems); + return elemExprs; + } if (Rules.ItemToExpressionConverter != null) { @@ -1137,107 +1280,122 @@ public Expression GetOrAddStateItemExpression(object item, Type itemType = null, return itemExpr; } - Throw.If(throwIfStateRequired || Rules.ThrowIfRuntimeStateRequired, Error.StateIsRequiredToUseItem, item); + Throw.If(throwIfStateRequired || Rules.ThrowIfRuntimeStateRequired, + Error.StateIsRequiredToUseItem, item); - var itemIndex = GetOrAddStateItem(item); - var stateItemExpr = GetStateItemExpression(itemIndex); - return Expression.Convert(stateItemExpr, itemType); + return Expression.Constant(item, itemType); } - private static Expression GetPrimitiveOrArrayExprOrDefault(object item, Type itemType) + /// + public int GetOrAddStateItem(object item) { - if (item == null) - return itemType != null - ? Expression.Constant(null, itemType) - : Expression.Constant(null); - - itemType = itemType ?? item.GetType(); - - if (itemType == typeof(DefaultKey)) - return Expression.Call(typeof(DefaultKey), "Of", ArrayTools.Empty(), - Expression.Constant(((DefaultKey)item).RegistrationOrder)); - - if (itemType.IsArray) - { - var itType = itemType.GetElementType().ThrowIfNull(); - var items = ((IEnumerable)item).Cast().Select(it => GetPrimitiveOrArrayExprOrDefault(it, null)); - var itExprs = Expression.NewArrayInit(itType, items); - return itExprs; - } - - return itemType.IsPrimitive() || itemType.IsAssignableTo(typeof(Type)) - ? Expression.Constant(item, itemType) - : null; + return -1; } #endregion #region Factories Add/Get - private sealed class FactoriesEntry + internal sealed class FactoriesEntry { public readonly DefaultKey LastDefaultKey; public readonly ImTreeMap Factories; + // lastDefaultKey may be null public FactoriesEntry(DefaultKey lastDefaultKey, ImTreeMap factories) { LastDefaultKey = lastDefaultKey; Factories = factories; } + + public static readonly FactoriesEntry Empty = new FactoriesEntry(null, ImTreeMap.Empty); + + public FactoriesEntry With(Factory factory, object serviceKey = null) + { + var lastDefaultKey = serviceKey != null ? LastDefaultKey // remains the same + : LastDefaultKey == null ? DefaultKey.Value + : LastDefaultKey.Next(); + + var factories = Factories.AddOrUpdate(serviceKey ?? lastDefaultKey, factory); + + return new FactoriesEntry(lastDefaultKey, factories); + } } private Factory GetServiceFactoryOrDefault(Request request, Rules.FactorySelectorRule factorySelector) { - var entry = GetServiceFactoryEntryOrDefault(request); - if (entry == null) // no entry - no factories: return earlier + var factoryOrFactories = GetServiceFactoryOrFactoriesOrNull(request); + if (factoryOrFactories == null) return null; - var singleFactory = entry as Factory; + var factory = factoryOrFactories as Factory; if (factorySelector != null) { - if (singleFactory != null) - return !singleFactory.CheckCondition(request) ? null - : factorySelector(request, new[] {new KeyValuePair(DefaultKey.Value, singleFactory) }); + if (factory != null) + return !factory.CheckCondition(request) ? null + : factorySelector(request, new[] { new KeyValuePair(DefaultKey.Value, factory) }); - var allFactories = ((FactoriesEntry)entry).Factories.Enumerate() + var selectedFactories = ((IEnumerable>)factoryOrFactories) .Where(f => f.Value.CheckCondition(request)) .Select(f => new KeyValuePair(f.Key, f.Value)) .ToArray(); - return factorySelector(request, allFactories); + return factorySelector(request, selectedFactories); } var serviceKey = request.ServiceKey; - if (singleFactory != null) + if (factory != null) { return (serviceKey == null || DefaultKey.Value.Equals(serviceKey)) - && singleFactory.CheckCondition(request) ? singleFactory : null; + && factory.CheckCondition(request) + ? factory : null; } - var factories = ((FactoriesEntry)entry).Factories; + var factories = (IEnumerable>)factoryOrFactories; if (serviceKey != null) { - singleFactory = factories.GetValueOrDefault(serviceKey); - return singleFactory != null && singleFactory.CheckCondition(request) ? singleFactory : null; + var keyFactory = factories.FirstOrDefault(f => serviceKey.Equals(f.Key)); + return keyFactory != null + && keyFactory.Value.CheckCondition(request) ? keyFactory.Value : null; } - var matchedFactories = factories.Enumerate(); var metadataKey = request.MetadataKey; var metadata = request.Metadata; if (metadataKey != null || metadata != null) - matchedFactories = matchedFactories.Where(f => f.Value.Setup.MatchesMetadata(metadataKey, metadata)); + factories = factories.Where(f => f.Value.Setup.MatchesMetadata(metadataKey, metadata)); - var defaultFactories = matchedFactories + var defaultFactories = factories .Where(f => f.Key is DefaultKey && f.Value.CheckCondition(request)) - .ToArray(); + .ToArrayOrSelf(); + + // Select single scoped factory if any + if (defaultFactories.Length > 1) + { + if (Rules.ImplicitCheckForReuseMatchingScope && this.GetCurrentScope() != null) + { + var scopedFactories = defaultFactories.Where(f => f.Value.Reuse is CurrentScopeReuse).ToArray(); + if (scopedFactories.Length == 1) + defaultFactories = scopedFactories; + } + + // check that impl generic definition is matched to service type, and selected matched only factories. + // the generated factories are cached - so there should not be repeating of the check, and not match of perf decrease. + if (defaultFactories.Length != 0) + { + defaultFactories = defaultFactories.Where(f => + f.Value.FactoryGenerator == null || + f.Value.FactoryGenerator.GetGeneratedFactory(request, ifErrorReturnDefault: true) != null) + .ToArrayOrSelf(); + } + } if (defaultFactories.Length == 1) { var defaultFactory = defaultFactories[0]; + + // NOTE: For resolution root sets correct default key to be used in delegate cache. if (request.IsResolutionCall) - { - // NOTE: For resolution root sets correct default key to be used in delegate cache. request.ChangeServiceKey(defaultFactory.Key); - } + return defaultFactory.Value; } @@ -1247,7 +1405,7 @@ private Factory GetServiceFactoryOrDefault(Request request, Rules.FactorySelecto return null; } - private object GetServiceFactoryEntryOrDefault(Request request) + private object GetServiceFactoryOrFactoriesOrNull(Request request) { var serviceKey = request.ServiceKey; var serviceFactories = _registry.Value.Services; @@ -1266,10 +1424,12 @@ private object GetServiceFactoryEntryOrDefault(Request request) if (actualServiceType.IsClosedGeneric()) { var serviceKeyWithOpenGenericRequiredType = serviceKey as object[]; - if (serviceKeyWithOpenGenericRequiredType != null && serviceKeyWithOpenGenericRequiredType.Length == 2) + if (serviceKeyWithOpenGenericRequiredType != null && + serviceKeyWithOpenGenericRequiredType.Length == 2) { var openGenericType = serviceKeyWithOpenGenericRequiredType[0] as Type; - if (openGenericType != null && openGenericType == actualServiceType.GetGenericDefinitionOrNull()) + if (openGenericType != null && + openGenericType == actualServiceType.GetGenericDefinitionOrNull()) { actualServiceType = openGenericType; serviceKey = serviceKeyWithOpenGenericRequiredType[1]; @@ -1283,26 +1443,30 @@ private object GetServiceFactoryEntryOrDefault(Request request) var entry = serviceFactories.GetValueOrDefault(actualServiceType); - // Special case for closed-generic lookup type: - // When entry is not found - // Or the key in entry is not found + // For closed-generic lookup type: + // When entry is not found + // or the key in entry is not found // Then go to the open-generic services if (actualServiceType.IsClosedGeneric()) { if (entry == null || serviceKey != null && ( - entry is FactoriesEntry && - ((FactoriesEntry)entry).Factories.GetValueOrDefault(serviceKey) == null || - entry is Factory && - !serviceKey.Equals(DefaultKey.Value))) + entry is Factory && !serviceKey.Equals(DefaultKey.Value) || + entry is FactoriesEntry && ((FactoriesEntry)entry).Factories.GetValueOrDefault(serviceKey) == null)) { var lookupOpenGenericType = actualServiceType.GetGenericTypeDefinition(); var openGenericEntry = serviceFactories.GetValueOrDefault(lookupOpenGenericType); - entry = openGenericEntry ?? entry; // stay with original entry if open generic is not found; + if (openGenericEntry != null) + { + entry = openGenericEntry; + } } } - return entry; + if (entry == null) + return null; + + return (object)(entry as Factory) ?? ((FactoriesEntry)entry).Factories.Enumerate(); } #endregion @@ -1312,21 +1476,181 @@ private object GetServiceFactoryEntryOrDefault(Request request) private int _disposed; private readonly Ref _registry; - private Ref> _defaultFactoryDelegateCache; + private Ref> _defaultFactoryDelegateCache; - private readonly ContainerWeakRef _containerWeakRef; - private readonly Request _emptyRequest; + // pre-created and stored for performance reasons + private readonly ContainerWeakRef _thisContainerWeakRef; private readonly SingletonScope _singletonScope; - private readonly object[] _singletonItems; - private readonly IScope _openedScope; + internal readonly IScope _openedScope; private readonly IScopeContext _scopeContext; - private sealed class Registry + internal void UseInstanceInternal(Type serviceType, object instance, object serviceKey) { - public static readonly Registry Empty = new Registry(); + ThrowIfContainerDisposed(); + + var scope = _openedScope ?? _singletonScope; + var instanceType = instance == null ? typeof(object) : instance.GetType(); + + _registry.Swap(r => + { + UsedInstanceFactory newInstanceFactory; + var entry = r.Services.GetValueOrDefault(serviceType); + if (entry == null) + { + newInstanceFactory = new UsedInstanceFactory(instanceType); + scope.SetOrAdd(scope.GetScopedItemIdOrSelf(newInstanceFactory.FactoryID), instance); + + var servicesWithFactory = serviceKey == null + ? r.Services.AddOrUpdate(serviceType, newInstanceFactory) + : r.Services.AddOrUpdate(serviceType, FactoriesEntry.Empty.With(newInstanceFactory, serviceKey)); + + return r.WithServices(servicesWithFactory); + } + + var defaultFactory = entry as Factory; + if (defaultFactory != null) + { + // new factory is also a default one, check if we can reuse the old factory + if (serviceKey == null) + { + // The condition for reusing the factory: + var defaultInstanceFactory = defaultFactory as UsedInstanceFactory; + if (defaultInstanceFactory != null) + { + scope.SetOrAdd(scope.GetScopedItemIdOrSelf(defaultInstanceFactory.FactoryID), instance); + return r; + } + } + + // otherwise register a new factory + newInstanceFactory = new UsedInstanceFactory(instanceType); + scope.SetOrAdd(scope.GetScopedItemIdOrSelf(newInstanceFactory.FactoryID), instance); + + return r.WithServices(r.Services.AddOrUpdate(serviceType, + FactoriesEntry.Empty.With(defaultFactory).With(newInstanceFactory, serviceKey))); + } + + // the only remaining option is multiple factories entry + var factoriesEntry = (FactoriesEntry)entry; + if (serviceKey == null) + { + // if any default factories, find the instance factory to reuse. Throw if multiple. + if (factoriesEntry.LastDefaultKey != null) + { + // As we always permit / re-use the Single factory only, so we cannot have more than one + var defaultInstanceFactory = factoriesEntry.Factories.Enumerate() + .FirstOrDefault(it => it.Key is DefaultKey && it.Value is UsedInstanceFactory); + + if (defaultInstanceFactory != null) + { + scope.SetOrAdd(scope.GetScopedItemIdOrSelf(defaultInstanceFactory.Value.FactoryID), instance); + return r; + } + } + } + else // for multiple factories check for existing service key + { + var keyedFactory = factoriesEntry.Factories.GetValueOrDefault(serviceKey); + if (keyedFactory != null) + { + var keyedInstanceFactory = keyedFactory as UsedInstanceFactory; + if (keyedInstanceFactory != null) + { + scope.SetOrAdd(scope.GetScopedItemIdOrSelf(keyedInstanceFactory.FactoryID), instance); + return r; + } + + Throw.It(Error.UnableToUseInstanceForExistingNonInstanceFactory, + KV.Of(serviceKey, instance), keyedFactory); + } + } + + newInstanceFactory = new UsedInstanceFactory(instanceType); + scope.SetOrAdd(scope.GetScopedItemIdOrSelf(newInstanceFactory.FactoryID), instance); + return r.WithServices(r.Services.AddOrUpdate(serviceType, + factoriesEntry.With(newInstanceFactory, serviceKey))); + }); + } + + // Just a wrapper, with only goal to provide and expression for instance access bound to FactoryID + internal sealed class UsedInstanceFactory : Factory + { + public override Type ImplementationType { get { return _instanceType; } } + private readonly Type _instanceType; + + public UsedInstanceFactory(Type instanceType) + { + _instanceType = instanceType; + } + + /// Called for Resolution call/root. + public override FactoryDelegate GetDelegateOrDefault(Request request) + { + if (request.IsResolutionRoot) + { + var decoratedExpr = request.Container.GetDecoratorExpressionOrDefault(request.WithResolvedFactory(this)); + if (decoratedExpr != null) + return decoratedExpr.CompileToDelegate(); + } + + return GetInstanceFromScopeChainOrSingletons; + } + + /// Called for Injection as dependency. + public override Expression GetExpressionOrDefault(Request request) + { + return request.Container.GetDecoratorExpressionOrDefault(request.WithResolvedFactory(this)) + ?? CreateExpressionOrDefault(request); + } + + public override Expression CreateExpressionOrDefault(Request request) + { + return Resolver.CreateResolutionExpression(request, isRuntimeDependency: true); + } + + #region Implementation + + private object GetInstanceFromScopeChainOrSingletons(object[] _, IResolverContext r, IScope __) + { + var scope = r.Scopes.GetCurrentScope(); + while (scope != null) + { + var result = GetAndUnwrapOrDefault(scope, FactoryID); + if (result != null) + return result; + scope = scope.Parent; + } + + return GetAndUnwrapOrDefault(r.SingletonScope(), FactoryID); + } + + private static object GetAndUnwrapOrDefault(IScope scope, int factoryId) + { + var id = scope.GetScopedItemIdOrSelf(factoryId); + var value = scope.GetOrAdd(id, () => null); + + if (value == null) + return null; + + var weaklyReferenced = value as WeakReference; + if (weaklyReferenced != null) + value = weaklyReferenced.Target.ThrowIfNull(Error.WeakRefReuseWrapperGCed); + var preventDisposable = value as object[]; + if (preventDisposable != null && preventDisposable.Length == 1) + value = preventDisposable[0]; + + return value; + } + + #endregion + } + + internal sealed class Registry + { + public static readonly Registry Empty = new Registry(); public static readonly Registry Default = new Registry(WrappersSupport.Wrappers); // Factories: @@ -1335,30 +1659,31 @@ private sealed class Registry public readonly ImTreeMap Wrappers; // Cache: - public readonly Ref> DefaultFactoryDelegateCache; + public readonly Ref FactoryExpressionCache; + + public readonly Ref> DefaultFactoryDelegateCache; // key: KV where Key is ServiceType and object is ServiceKey - // value: FactoryDelegate or/and IntTreeMap + // value: FactoryDelegate or/and IntTreeMap<{requiredServicType+preResolvedParent}, FactoryDelegate> public readonly Ref>>> KeyedFactoryDelegateCache; - public readonly Ref FactoryExpressionCache; - private enum IsChangePermitted { Permitted, Error, Ignored } private readonly IsChangePermitted _isChangePermitted; public Registry WithoutCache() { return new Registry(Services, Decorators, Wrappers, - Ref.Of(ImTreeMap.Empty), + Ref.Of(ImMap.Empty), Ref.Of(ImTreeMap>>.Empty), - Ref.Of(ImTreeMapIntToObj.Empty), _isChangePermitted); + Ref.Of(ImTreeMapIntToObj.Empty), + _isChangePermitted); } private Registry(ImTreeMap wrapperFactories = null) : this(ImTreeMap.Empty, ImTreeMap.Empty, wrapperFactories ?? ImTreeMap.Empty, - Ref.Of(ImTreeMap.Empty), + Ref.Of(ImMap.Empty), Ref.Of(ImTreeMap>>.Empty), Ref.Of(ImTreeMapIntToObj.Empty), IsChangePermitted.Permitted) @@ -1368,7 +1693,7 @@ private Registry(ImTreeMap wrapperFactories = null) ImTreeMap services, ImTreeMap decorators, ImTreeMap wrappers, - Ref> defaultFactoryDelegateCache, + Ref> defaultFactoryDelegateCache, Ref>>> keyedFactoryDelegateCache, Ref factoryExpressionCache, IsChangePermitted isChangePermitted) @@ -1382,7 +1707,7 @@ private Registry(ImTreeMap wrapperFactories = null) _isChangePermitted = isChangePermitted; } - private Registry WithServices(ImTreeMap services) + internal Registry WithServices(ImTreeMap services) { return services == Services ? this : new Registry(services, Decorators, Wrappers, @@ -1395,7 +1720,7 @@ private Registry WithDecorators(ImTreeMap decorators) return decorators == Decorators ? this : new Registry(Services, decorators, Wrappers, DefaultFactoryDelegateCache.NewRef(), KeyedFactoryDelegateCache.NewRef(), - FactoryExpressionCache.NewRef(), _isChangePermitted); + FactoryExpressionCache.NewRef(), _isChangePermitted); } private Registry WithWrappers(ImTreeMap wrappers) @@ -1439,42 +1764,76 @@ public Registry Register(Factory factory, Type serviceType, IfAlreadyRegistered Wrappers.AddOrUpdate(serviceType, factory)); } - public bool IsRegistered(Type serviceType, object serviceKey, FactoryType factoryType, Func condition) + public Factory[] GetRegisteredFactories(Type serviceType, object serviceKey, FactoryType factoryType, + Func condition) { serviceType = serviceType.ThrowIfNull(); switch (factoryType) { case FactoryType.Wrapper: - var wrapper = GetWrapperOrDefault(serviceType); - return wrapper != null && (condition == null || condition(wrapper)); + var arrayElementType = serviceType.GetArrayElementTypeOrNull(); + if (arrayElementType != null) + serviceType = typeof(IEnumerable<>).MakeGenericType(arrayElementType); + + var wrapper = Wrappers.GetValueOrDefault(serviceType.GetGenericDefinitionOrNull() ?? serviceType); + return wrapper != null && (condition == null || condition(wrapper)) + ? new[] { wrapper } + : null; case FactoryType.Decorator: var decorators = Decorators.GetValueOrDefault(serviceType); - return decorators != null && decorators.Length != 0 - && (condition == null || decorators.Any(condition)); - default: + var openGenServiceType = serviceType.GetGenericDefinitionOrNull(); + if (openGenServiceType != null) + decorators = decorators.Append(Decorators.GetValueOrDefault(openGenServiceType)); + + if (decorators != null && decorators.Length != 0) + return condition == null + ? decorators + : decorators.Where(condition).ToArray(); + return null; + + default: var entry = Services.GetValueOrDefault(serviceType); if (entry == null) - return false; + return null; var factory = entry as Factory; if (factory != null) - return (serviceKey == null || DefaultKey.Value.Equals(serviceKey)) - && (condition == null || condition(factory)); + { + if (serviceKey == null || DefaultKey.Value.Equals(serviceKey)) + return condition == null || condition(factory) + ? new[] { factory } + : null; + return null; + } var factories = ((FactoriesEntry)entry).Factories; if (serviceKey == null) - return condition == null || factories.Enumerate().Any(f => condition(f.Value)); + { + var selectedFactories = condition == null + ? factories.Enumerate() + : factories.Enumerate().Where(f => condition(f.Value)); + return selectedFactories.Select(f => f.Value).ToArray(); + } factory = factories.GetValueOrDefault(serviceKey); - return factory != null && (condition == null || condition(factory)); + return factory != null && (condition == null || condition(factory)) + ? new[] { factory } + : null; } } - public Factory GetWrapperOrDefault(Type serviceType) + public bool ClearCache(Type serviceType, object serviceKey, FactoryType factoryType) { - return Wrappers.GetValueOrDefault(serviceType.GetGenericDefinitionOrNull() ?? serviceType); + var factories = GetRegisteredFactories(serviceType, serviceKey, factoryType, null); + if (factories.IsNullOrEmpty()) + return false; + + for (var i = 0; i < factories.Length; i++) + WithoutFactoryCache(factories[i], serviceType, serviceKey); + + return true; } private Registry WithService(Factory factory, Type serviceType, object serviceKey, IfAlreadyRegistered ifAlreadyRegistered) @@ -1493,8 +1852,7 @@ private Registry WithService(Factory factory, Type serviceType, object serviceKe var oldFactoriesEntry = oldEntry as FactoriesEntry; if (oldFactoriesEntry != null && oldFactoriesEntry.LastDefaultKey == null) // no default registered yet - return new FactoriesEntry(DefaultKey.Value, - oldFactoriesEntry.Factories.AddOrUpdate(DefaultKey.Value, newFactory)); + return oldFactoriesEntry.With(newFactory); var oldFactory = oldFactoriesEntry == null ? (Factory)oldEntry : null; switch (ifAlreadyRegistered) @@ -1536,26 +1894,27 @@ private Registry WithService(Factory factory, Type serviceType, object serviceKe oldFactory != null && oldFactory.ImplementationType != implementationType || oldFactoriesEntry != null && oldFactoriesEntry.Factories.Enumerate() .All(f => f.Value.ImplementationType != implementationType)) - return AppendNonKeyed(oldFactoriesEntry, oldFactory, newFactory); + { + return (oldFactoriesEntry ?? FactoriesEntry.Empty.With(oldFactory)).With(newFactory); + } return oldEntry; default: - return AppendNonKeyed(oldFactoriesEntry, oldFactory, newFactory); + return (oldFactoriesEntry ?? FactoriesEntry.Empty.With(oldFactory)).With(newFactory); } }); } else // serviceKey != null { - var factories = new FactoriesEntry(null, ImTreeMap.Empty.AddOrUpdate(serviceKey, factory)); + var factories = FactoriesEntry.Empty.With(factory, serviceKey); services = Services.AddOrUpdate(serviceType, factories, (oldEntry, newEntry) => { if (oldEntry == null) return newEntry; if (oldEntry is Factory) // if registered is default, just add it to new entry - return new FactoriesEntry(DefaultKey.Value, - ((FactoriesEntry)newEntry).Factories.AddOrUpdate(DefaultKey.Value, (Factory)oldEntry)); + return ((FactoriesEntry)newEntry).With((Factory)oldEntry); var oldFactories = (FactoriesEntry)oldEntry; return new FactoriesEntry(oldFactories.LastDefaultKey, @@ -1573,8 +1932,6 @@ private Registry WithService(Factory factory, Type serviceType, object serviceKe replacedFactory = oldFactory; return newFactory; - case IfAlreadyRegistered.Throw: - case IfAlreadyRegistered.AppendNewImplementation: default: return Throw.For(Error.UnableToRegisterDuplicateKey, serviceType, newFactory, serviceKey, oldFactory); } @@ -1582,7 +1939,7 @@ private Registry WithService(Factory factory, Type serviceType, object serviceKe }); } - // Note: We are reusing replaced factory (with same setup and reuse) cache by inheriting its ID. + // Note: We are reusing replaced factory (with same setup and reuse) by using the ID. // It is possible because cache depends only on ID. var reuseReplacedInstanceFactory = false; if (replacedFactory != null) @@ -1608,28 +1965,16 @@ private Registry WithService(Factory factory, Type serviceType, object serviceKe !reuseReplacedInstanceFactory) { if (replacedFactory != null) - registry = WithoutFactoryCache(registry, replacedFactory, serviceType, serviceKey); + registry = registry.WithoutFactoryCache(replacedFactory, serviceType, serviceKey); else if (replacedFactories != null) foreach (var f in replacedFactories.Enumerate()) - registry = WithoutFactoryCache(registry, f.Value, serviceType, serviceKey); + registry = registry.WithoutFactoryCache(f.Value, serviceType, serviceKey); } } return registry; } - private static object AppendNonKeyed(FactoriesEntry oldFactories, Factory oldEntry, Factory newFactory) - { - if (oldFactories == null) - return new FactoriesEntry(DefaultKey.Value.Next(), - ImTreeMap.Empty - .AddOrUpdate(DefaultKey.Value, oldEntry) - .AddOrUpdate(DefaultKey.Value.Next(), newFactory)); - - var nextKey = oldFactories.LastDefaultKey.Next(); - return new FactoriesEntry(nextKey, oldFactories.Factories.AddOrUpdate(nextKey, newFactory)); - } - public Registry Unregister(FactoryType factoryType, Type serviceType, object serviceKey, Func condition) { if (_isChangePermitted != IsChangePermitted.Permitted) @@ -1649,8 +1994,7 @@ public Registry Unregister(FactoryType factoryType, Type serviceType, object ser return null; })); - return removedWrapper == null ? this - : WithoutFactoryCache(registry, removedWrapper, serviceType); + return removedWrapper == null ? this : registry.WithoutFactoryCache(removedWrapper, serviceType); case FactoryType.Decorator: Factory[] removedDecorators = null; @@ -1665,7 +2009,8 @@ public Registry Unregister(FactoryType factoryType, Type serviceType, object ser return this; foreach (var removedDecorator in removedDecorators) - registry = WithoutFactoryCache(registry, removedDecorator, serviceType); + registry = registry.WithoutFactoryCache(removedDecorator, serviceType); + return registry; default: @@ -1750,39 +2095,39 @@ private Registry UnregisterServiceFactory(Type serviceType, object serviceKey = var registry = WithServices(services); if (removed is Factory) - return WithoutFactoryCache(registry, (Factory)removed, serviceType, serviceKey); + return registry.WithoutFactoryCache((Factory)removed, serviceType, serviceKey); var removedFactories = removed as Factory[] ?? ((FactoriesEntry)removed).Factories.Enumerate().Select(f => f.Value).ToArray(); foreach (var removedFactory in removedFactories) - registry = WithoutFactoryCache(registry, removedFactory, serviceType, serviceKey); + registry = registry.WithoutFactoryCache(removedFactory, serviceType, serviceKey); return registry; } - private static Registry WithoutFactoryCache(Registry registry, Factory factory, Type serviceType, object serviceKey = null) + // Does not change registry, returning Registry just for convinience of fluent syntax + private Registry WithoutFactoryCache(Factory factory, Type serviceType, object serviceKey = null) { if (factory.FactoryGenerator != null) { foreach (var f in factory.FactoryGenerator.GeneratedFactories.Enumerate()) - WithoutFactoryCache(registry, f.Value, f.Key.Key, f.Key.Value); + WithoutFactoryCache(f.Value, f.Key.Key, f.Key.Value); } else { // clean expression cache using FactoryID as key - registry.FactoryExpressionCache.Swap(_ => _.Update(factory.FactoryID, null)); + FactoryExpressionCache.Swap(_ => _.Update(factory.FactoryID, null)); // clean default factory cache - registry.DefaultFactoryDelegateCache.Swap(_ => _.Update(serviceType, null)); + DefaultFactoryDelegateCache.Swap(_ => _.Update(serviceType, null)); // clean keyed/context cache from keyed and context based resolutions - var keyedCacheKey = serviceKey == null ? serviceType - : (object)new KV(serviceType, serviceKey); - registry.KeyedFactoryDelegateCache.Swap(_ => _.Update(keyedCacheKey, null)); + var keyedCacheKey = serviceKey == null ? (object)serviceType : new KV(serviceType, serviceKey); + KeyedFactoryDelegateCache.Swap(_ => _.Update(keyedCacheKey, null)); } - return registry; + return this; } public Registry WithNoMoreRegistrationAllowed(bool ignoreInsteadOfThrow) @@ -1794,8 +2139,9 @@ public Registry WithNoMoreRegistrationAllowed(bool ignoreInsteadOfThrow) } } - private Container(Rules rules, Ref registry, SingletonScope singletonScope, - IScopeContext scopeContext = null, IScope openedScope = null, int disposed = 0) + private Container(Rules rules, Ref registry, SingletonScope singletonScope, + IScopeContext scopeContext = null, IScope openedScope = null, int disposed = 0, + Container rootContainer = null) { _disposed = disposed; @@ -1805,7 +2151,6 @@ public Registry WithNoMoreRegistrationAllowed(bool ignoreInsteadOfThrow) _defaultFactoryDelegateCache = registry.Value.DefaultFactoryDelegateCache; _singletonScope = singletonScope; - _singletonItems = singletonScope.Items; _scopeContext = scopeContext; @@ -1814,8 +2159,9 @@ public Registry WithNoMoreRegistrationAllowed(bool ignoreInsteadOfThrow) else if (scopeContext == null && rules.ImplicitOpenedRootScope) // only valid for non ambient context _openedScope = new Scope(null, NonAmbientRootScopeName); - _containerWeakRef = new ContainerWeakRef(this); - _emptyRequest = Request.CreateEmpty(_containerWeakRef); + _thisContainerWeakRef = new ContainerWeakRef(this); + + RootContainer = rootContainer; } #endregion @@ -1825,15 +2171,15 @@ public Registry WithNoMoreRegistrationAllowed(bool ignoreInsteadOfThrow) public static class ContainerTools { /// For given instance resolves and sets properties and fields. - /// It respects rules set per container, - /// or if rules are not set it uses , + /// It respects rules set per container, + /// or if rules are not set it uses , /// or you can specify your own rules with parameter. /// Input and returned instance type.Service (wrapped) /// Usually a container instance, cause implements /// Service instance with properties to resolve and initialize. /// (optional) Function to select properties and fields, overrides all other rules if specified. /// Input instance with resolved dependencies, to enable fluent method composition. - /// Different Rules could be combined together using method. + /// Different Rules could be combined together using method. public static TService InjectPropertiesAndFields(this IContainer container, TService instance, PropertiesAndFieldsSelector propertiesAndFields = null) { @@ -1867,9 +2213,9 @@ public static T New(this IContainer container, Made made = null) return (T)container.New(typeof(T), made); } - /// Creates service given strongly-typed creation expression. + /// Creates service given strongly-typed creation expression. /// Can be used to invoke arbitrary method returning some value with injecting its parameters from container. - /// Method or constructor result type. + /// Method or constructor result type. /// Container to use for injecting dependencies. /// Creation expression. /// Created result. @@ -1878,7 +2224,7 @@ public static T New(this IContainer container, Made.TypedMade made) return (T)container.New(typeof(T), made); } - /// Registers new service type with factory for registered service type. + /// Registers new service type with factory for registered service type. /// Throw if no such registered service type in container. /// Container New service type. /// Existing registered service type. @@ -1887,14 +2233,14 @@ public static T New(this IContainer container, Made.TypedMade made) public static void RegisterMapping(this IContainer container, Type serviceType, Type registeredServiceType, object serviceKey = null, object registeredServiceKey = null) { - var request = container.EmptyRequest.Push(registeredServiceType, registeredServiceKey); + var request = Request.Create(container, registeredServiceType, registeredServiceKey); var factory = container.GetServiceFactoryOrDefault(request); factory.ThrowIfNull(Error.RegisterMappingNotFoundRegisteredService, registeredServiceType, registeredServiceKey); container.Register(factory, serviceType, serviceKey, IfAlreadyRegistered.Keep, false); } - /// Registers new service type with factory for registered service type. + /// Registers new service type with factory for registered service type. /// Throw if no such registered service type in container. /// Container /// New service type. @@ -1938,13 +2284,13 @@ public static T New(this IContainer container, Made.TypedMade made) { var types = implTypeAssemblies.ThrowIfNull() .SelectMany(assembly => assembly.GetLoadedTypes()) - .Where(type => !type.IsAbstract() && !type.IsCompilerGenerated()) + .Where(Registrator.IsImplementationType) .ToArray(); return container.WithAutoFallbackResolution(types, changeDefaultReuse, condition); } /// Creates new container with provided parameters and properties - /// to pass the custom dependency values for injection. The old parameters and properties are overridden, + /// to pass the custom dependency values for injection. The old parameters and properties are overridden, /// but not replaced. /// Container to work with. /// (optional) Parameters specification, can be used to proved custom values. @@ -1997,7 +2343,7 @@ public static T New(this IContainer container, Made.TypedMade made) { try { - var request = generatingContainer.EmptyRequest.Push(r.ServiceType, r.OptionalServiceKey); + var request = Request.Create(generatingContainer, r.ServiceType, r.OptionalServiceKey); var factoryExpr = r.Factory.GetExpressionOrDefault(request).WrapInFactoryExpression(); resolutionExprList.Add(new KeyValuePair>(r, factoryExpr)); } @@ -2014,7 +2360,7 @@ public static T New(this IContainer container, Made.TypedMade made) } /// Used to find potential problems when resolving the passed services . - /// Method will collect the exceptions when resolving or injecting the specific registration. + /// Method will collect the exceptions when resolving or injecting the specific registration. /// Does not create any actual service objects. /// for container /// (optional) Examined resolved services. If empty will try to resolve every service in container. @@ -2022,7 +2368,7 @@ public static T New(this IContainer container, Made.TypedMade made) public static KeyValuePair[] Validate( this IContainer container, params Type[] resolutionRoots) { - return container.VerifyResolutions(resolutionRoots.IsNullOrEmpty() + return container.VerifyResolutions(resolutionRoots.IsNullOrEmpty() ? (Func)null : registration => resolutionRoots.IndexOf(registration.ServiceType) != -1); } @@ -2058,7 +2404,7 @@ public static bool IsSupportedInjectedCustomValueType(Type customValueType) public static Expression RequestInfoToExpression(this IContainer container, RequestInfo request) { if (request.IsEmpty) - return _emptyRequestInfoExpr; + return _emptyRequestInfoExpr.Value; // recursively ask for parent expression until it is empty var parentRequestInfoExpr = container.RequestInfoToExpression(request.ParentOrWrapper); @@ -2072,46 +2418,49 @@ public static Expression RequestInfoToExpression(this IContainer container, Requ var metadata = request.Metadata; var factoryType = request.FactoryType; var ifUnresolved = request.IfUnresolved; + var flags = request.Flags; var serviceTypeExpr = Expression.Constant(serviceType, typeof(Type)); var factoryIdExpr = Expression.Constant(factoryID, typeof(int)); var implTypeExpr = Expression.Constant(implementationType, typeof(Type)); - var reuseExpr = request.Reuse.ToExpression(container); - - // Try simplified versions of Push first, before the Push with all arguments provided: + var reuseExpr = request.Reuse == null + ? Expression.Constant(null, typeof(IReuse)) + : ((IReuseV3)request.Reuse).ToExpression(it => container.GetOrAddStateItemExpression(it)); if (ifUnresolved == IfUnresolved.Throw && requiredServiceType == null && serviceKey == null && metadataKey == null && metadata == null && - factoryType == FactoryType.Service) + factoryType == FactoryType.Service && flags == default(RequestFlags)) return Expression.Call(parentRequestInfoExpr, "Push", ArrayTools.Empty(), serviceTypeExpr, factoryIdExpr, implTypeExpr, reuseExpr); var requiredServiceTypeExpr = Expression.Constant(requiredServiceType, typeof(Type)); var servicekeyExpr = Expression.Convert(container.GetOrAddStateItemExpression(serviceKey), typeof(object)); var factoryTypeExpr = Expression.Constant(factoryType, typeof(FactoryType)); + var flagsExpr = Expression.Constant(flags, typeof(RequestFlags)); if (ifUnresolved == IfUnresolved.Throw && metadataKey == null && metadata == null) return Expression.Call(parentRequestInfoExpr, "Push", ArrayTools.Empty(), serviceTypeExpr, requiredServiceTypeExpr, servicekeyExpr, - factoryIdExpr, factoryTypeExpr, implTypeExpr, reuseExpr); + factoryIdExpr, factoryTypeExpr, implTypeExpr, reuseExpr, flagsExpr); var ifUnresolvedExpr = Expression.Constant(ifUnresolved, typeof(IfUnresolved)); if (metadataKey == null && metadata == null) return Expression.Call(parentRequestInfoExpr, "Push", ArrayTools.Empty(), serviceTypeExpr, requiredServiceTypeExpr, servicekeyExpr, ifUnresolvedExpr, - factoryIdExpr, factoryTypeExpr, implTypeExpr, reuseExpr); + factoryIdExpr, factoryTypeExpr, implTypeExpr, reuseExpr, flagsExpr); var metadataKeyExpr = Expression.Constant(metadataKey, typeof(string)); var metadataExpr = Expression.Convert(container.GetOrAddStateItemExpression(metadata), typeof(object)); - return Expression.Call(parentRequestInfoExpr, "Push", ArrayTools.Empty(), - serviceTypeExpr, requiredServiceTypeExpr, servicekeyExpr, metadataKeyExpr, metadataExpr, ifUnresolvedExpr, - factoryIdExpr, factoryTypeExpr, implTypeExpr, reuseExpr); + return Expression.Call(parentRequestInfoExpr, "Push", ArrayTools.Empty(), + serviceTypeExpr, requiredServiceTypeExpr, servicekeyExpr, metadataKeyExpr, metadataExpr, ifUnresolvedExpr, + factoryIdExpr, factoryTypeExpr, implTypeExpr, reuseExpr, flagsExpr); } - private static readonly Expression _emptyRequestInfoExpr = ReflectionTools.ToExpression(() => RequestInfo.Empty); + private static readonly Lazy _emptyRequestInfoExpr = new Lazy(() => + Expression.Field(null, typeof(RequestInfo).GetFieldOrNull("Empty"))); // todo: v3: replace with more direct access /// Returns the current scope, or null if not opened and is not set. @@ -2123,61 +2472,44 @@ public static IScope GetCurrentScope(this IContainer container, object name = nu { return container.ContainerWeakRef.Scopes.GetCurrentNamedScope(name, throwIfNotFound); } - } - /// Converter of given reuse to its code representation as expression tree. - public static class ReuseToExpressionConverter - { - /// Converts given reuse to its code representation in expression tree. - /// Reuse to convert - public static Expression ToExpression(this IReuse reuse, IContainer container) + /// Clears delegate and expression cache for specified . + /// But does not clear instances of already resolved/created singletons and scoped services! + /// Target service or wrapper type. + /// Container to operate. + /// (optional) If not specified, clears cache for all . + /// (optional) If omitted, the cache will be cleared for all resgitrations of . + /// True if type is found in the cache and cleared - false otherwise. + public static bool ClearCache(this IContainer container, FactoryType? factoryType = null, object serviceKey = null) { - var reuseToExpr = reuse as IConvertibleToExpression; - if (reuseToExpr != null) - return reuseToExpr.Convert(); - - if (reuse is SingletonReuse) - return _singletonReuseExpr; - - var currentScopeReuse = reuse as CurrentScopeReuse; - if (currentScopeReuse != null) - { - if (currentScopeReuse.Name == null) - return _inCurrentScopeReuseExpr; - return Expression.Call(typeof(Reuse), "InCurrentNamedScope", ArrayTools.Empty(), - container.GetOrAddStateItemExpression(currentScopeReuse.Name)); - } - - var resolutionScopeReuse = reuse as ResolutionScopeReuse; - if (resolutionScopeReuse != null) - { - if (resolutionScopeReuse.AssignableFromServiceType == null && - resolutionScopeReuse.ServiceKey == null && - resolutionScopeReuse.Outermost == false) - return _inResolutionScopeReuseExpr; - return Expression.Call(typeof(Reuse), "InResolutionScopeOf", ArrayTools.Empty(), - Expression.Constant(resolutionScopeReuse.AssignableFromServiceType, typeof(Type)), - container.GetOrAddStateItemExpression(resolutionScopeReuse.ServiceKey), - Expression.Constant(resolutionScopeReuse.Outermost, typeof(bool))); - } - - // transient or no reuse is default - return Expression.Constant(null, typeof(IReuse)); + return container.ClearCache(typeof(T), factoryType, serviceKey); } - private static readonly Expression _singletonReuseExpr = ReflectionTools.ToExpression(() => Reuse.Singleton); - private static readonly Expression _inCurrentScopeReuseExpr = ReflectionTools.ToExpression(() => Reuse.InCurrentScope); - private static readonly Expression _inResolutionScopeReuseExpr = ReflectionTools.ToExpression(() => Reuse.InResolutionScope); + /// Clears delegate and expression cache for specified service. + /// But does not clear instances of already resolved/created singletons and scoped services! + /// Container to operate. + /// Target service type. + /// (optional) If not specified, clears cache for all . + /// (optional) If omitted, the cache will be cleared for all resgitrations of . + /// True if type is found in the cache and cleared - false otherwise. + public static bool ClearCache(this IContainer container, Type serviceType, + FactoryType? factoryType = null, object serviceKey = null) + { + // todo: v3: remove cast. Move to IContainer. + return ((Container)container).ClearCache(serviceType, factoryType, serviceKey); + } } /// Interface used to convert reuse instance to expression. public interface IConvertibleToExpression { - /// Returns expression representation. subj. - Expression Convert(); + /// Returns expression representation without closure. + /// Delegate converting of sub-items, constants to container. + /// Expression representation. + Expression ToExpression(Func fallbackConverter); } - /// Used to represent multiple default service keys. + /// Used to represent multiple default service keys. /// Exposes to determine order of service added. public sealed class DefaultKey { @@ -2228,42 +2560,80 @@ private DefaultKey(int registrationOrder) } } - /// Returns reference to actual resolver implementation. - /// Minimizes dependency to Factory Delegate on container. + /// Holds all required info by . public interface IResolverContext { - /// Provides access to resolver implementation. + /// Provides access to current / scoped resolver. IResolver Resolver { get; } - /// Scopes access. + /// Access to the singleton and current scopes. IScopeAccess Scopes { get; } } - /// Guards access to WeakReference target with more DryIoc specific exceptions. + /// Provides the shortcuts and sugar based onto + /// to be consumed in + public static class ResolverContext + { + /// Returns subj. + /// + public static IResolver RootResolver(this IResolverContext ctx) + { + return ctx.RootContainer(); + } + + /// Returns subj. + /// + public static IScopeAccess RootScopes(this IResolverContext ctx) + { + return ctx.RootContainer(); + } + + /// Returns subj. + /// + public static IScope SingletonScope(this IResolverContext ctx) + { + return ctx.RootContainer().SingletonScope; + } + + private static Container RootContainer(this IResolverContext ctx) + { + var containerRef = ((ContainerWeakRef)ctx); + return containerRef.GetTarget(maybeDisposed: true).RootContainer + ?? containerRef.GetTarget(); + } + } + + /// Wraps access to WeakReference target with DryIoc specific exceptions. public sealed class ContainerWeakRef : IResolverContext { - /// Provides access to resolver implementation. + /// Provides access to current / scoped resolver. public IResolver Resolver { get { return GetTarget(); } } - /// Scope access. + /// Access to the singleton and current scopes. public IScopeAccess Scopes { get { return GetTarget(); } } /// Container access. public IContainer Container { get { return GetTarget(); } } /// Returns target container when it is not null and not disposed. Otherwise throws exception. + /// (optional) If set will return even disposed container. /// Target container. - public Container GetTarget() + public Container GetTarget(bool maybeDisposed = false) { var container = _ref.Target as Container; - return container != null && !container.IsDisposed ? container + return container != null && (maybeDisposed || !container.IsDisposed) + ? container : container == null ? Throw.For(Error.ContainerIsGarbageCollected) : Throw.For(Error.ContainerIsDisposed); } - /// Creates weak reference wrapper over passed container object. Object to wrap. - public ContainerWeakRef(IContainer container) { _ref = new WeakReference(container); } + /// Creates weak reference wrapper over passed container object. + /// Container to reference. + public ContainerWeakRef(IContainer container) + { + _ref = new WeakReference(container); + } private readonly WeakReference _ref; } @@ -2280,46 +2650,49 @@ public Container GetTarget() public delegate object FactoryDelegate(object[] state, IResolverContext r, IScope scope); /// Handles default conversation of expression into . - public static partial class FactoryCompiler + public static partial class FastExpressionCompiler { + private static readonly Type[] _factoryDelegateParamTypes = + { typeof(object[]), typeof(IResolverContext), typeof(IScope) }; + + private static readonly ParameterExpression[] _factoryDelegateParamExprs = + { Container.StateParamExpr, Container.ResolverContextParamExpr, Container.ResolutionScopeParamExpr }; + /// Wraps service creation expression (body) into and returns result lambda expression. /// Service expression (body) to wrap. Created lambda expression. public static Expression WrapInFactoryExpression(this Expression expression) { - return Expression.Lambda(OptimizeExpression(expression), _factoryDelegateParamsExpr); + return Expression.Lambda(OptimizeExpression(expression), _factoryDelegateParamExprs); } - static partial void CompileToDelegate(Expression expression, ref FactoryDelegate result); + static partial void TryCompile(ref TDelegate compileDelegate, + Expression bodyExpr, + ParameterExpression[] paramExprs, + Type[] paramTypes, + Type returnType) where TDelegate : class; - /// First wraps the input service creation expression into lambda expression and - /// then compiles lambda expression to actual used for service resolution. - /// By default it is using Expression.Compile but if corresponding rule specified (available on .Net 4.0 and higher), - /// it will compile to DymanicMethod/Assembly. - /// Service expression (body) to wrap. - /// To access container state that may be required for compilation. + /// First wraps the input service expression into lambda expression and + /// then compiles lambda expression to actual used for service resolution. + /// Service creation expression. /// Compiled factory delegate to use for service resolution. - public static FactoryDelegate CompileToDelegate(this Expression expression, IContainer container) + public static FactoryDelegate CompileToDelegate(this Expression expression) { expression = OptimizeExpression(expression); - // Optimization for fast singleton compilation - if (expression.NodeType == ExpressionType.ArrayIndex) + + // Optimize: just extract singleton from expression without compiling + if (expression.NodeType == ExpressionType.Constant) { - var arrayIndexExpr = (BinaryExpression)expression; - if (arrayIndexExpr.Left.NodeType == ExpressionType.Parameter) - { - var index = (int)((ConstantExpression)arrayIndexExpr.Right).Value; - if (index < container.ResolutionStateCache.Length) - { - var value = container.ResolutionStateCache[index]; - return (state, context, scope) => value; - } - } + var value = ((ConstantExpression)expression).Value; + return (state, context, scope) => value; } FactoryDelegate factoryDelegate = null; - CompileToDelegate(expression, ref factoryDelegate); - // ReSharper disable once ConstantNullCoalescingCondition - return factoryDelegate ?? Expression.Lambda(expression, _factoryDelegateParamsExpr).Compile(); + TryCompile(ref factoryDelegate, expression, _factoryDelegateParamExprs, _factoryDelegateParamTypes, typeof(object)); + + if (factoryDelegate != null) + return factoryDelegate; + + return Expression.Lambda(expression, _factoryDelegateParamExprs).Compile(); } private static Expression OptimizeExpression(Expression expression) @@ -2330,9 +2703,6 @@ private static Expression OptimizeExpression(Expression expression) expression = Expression.Convert(expression, typeof(object)); return expression; } - - private static readonly ParameterExpression[] _factoryDelegateParamsExpr = - { Container.StateParamExpr, Container.ResolverContextParamExpr, Container.ResolutionScopeParamExpr }; } /// Adds to Container support for: @@ -2345,15 +2715,22 @@ private static Expression OptimizeExpression(Expression expression) /// public static class WrappersSupport { - /// Supported Func types up to 4 input parameters. + /// Supported Func types. public static readonly Type[] FuncTypes = { typeof(Func<>), typeof(Func<,>), typeof(Func<,,>), typeof(Func<,,,>), typeof(Func<,,,,>), typeof(Func<,,,,,>), typeof(Func<,,,,,,>), typeof(Func<,,,,,,,>) }; + /// Supported Action types. + public static readonly Type[] ActionTypes = + { + typeof(Action), typeof(Action<>), typeof(Action<,>), typeof(Action<,,>), typeof(Action<,,,>), + typeof(Action<,,,,>), typeof(Action<,,,,,>), typeof(Action<,,,,,,>) + }; + /// Supported open-generic collection types. - public static readonly IEnumerable ArrayInterfaces = + public static readonly IEnumerable ArrayInterfaces = typeof(object[]).GetImplementedInterfaces() .Where(t => t.IsGeneric()) .Select(t => t.GetGenericTypeDefinition()) @@ -2400,7 +2777,7 @@ public static bool IsFuncWithArgs(this Type type) new ExpressionFactory(GetLazyEnumerableExpressionOrDefault, setup: Setup.Wrapper)); wrappers = wrappers.AddOrUpdate(typeof(Lazy<>), - new ExpressionFactory(GetLazyExpressionOrDefault, setup: Setup.Wrapper)); + new ExpressionFactory(r => GetLazyExpressionOrDefault(r), setup: Setup.Wrapper)); wrappers = wrappers.AddOrUpdate(typeof(KeyValuePair<,>), new ExpressionFactory(GetKeyValuePairExpressionOrDefault, setup: Setup.WrapperWith(1))); @@ -2415,11 +2792,16 @@ public static bool IsFuncWithArgs(this Type type) new ExpressionFactory(GetLambdaExpressionExpressionOrDefault, setup: Setup.Wrapper)); wrappers = wrappers.AddOrUpdate(typeof(Func<>), - new ExpressionFactory(GetFuncExpressionOrDefault, setup: Setup.Wrapper)); + new ExpressionFactory(GetFuncOrActionExpressionOrDefault, setup: Setup.Wrapper)); for (var i = 0; i < FuncTypes.Length; i++) wrappers = wrappers.AddOrUpdate(FuncTypes[i], - new ExpressionFactory(GetFuncExpressionOrDefault, setup: Setup.WrapperWith(i))); + new ExpressionFactory(GetFuncOrActionExpressionOrDefault, setup: Setup.WrapperWith(i))); + + for (var i = 0; i < ActionTypes.Length; i++) + wrappers = wrappers.AddOrUpdate(ActionTypes[i], + new ExpressionFactory(GetFuncOrActionExpressionOrDefault, + setup: Setup.WrapperWith(unwrap: _ => typeof(void)))); wrappers = AddContainerInterfacesAndDisposableScope(wrappers); @@ -2428,21 +2810,26 @@ public static bool IsFuncWithArgs(this Type type) private static ImTreeMap AddContainerInterfacesAndDisposableScope(ImTreeMap wrappers) { - wrappers = wrappers.AddOrUpdate(typeof(IResolver), - new ExpressionFactory(_ => Container.ResolverExpr, setup: Setup.Wrapper)); + wrappers = wrappers.AddOrUpdate(typeof(IResolver), + new ExpressionFactory(Container.GetResolverExpr, setup: Setup.Wrapper)); + + // todo: replace convert with exposed Container property on ResolverContext. + var containerFactory = new ExpressionFactory(r => + Expression.Convert(Container.GetResolverExpr(r), r.ServiceType), + setup: Setup.Wrapper); - var containerFactory = new ExpressionFactory(r => - Expression.Convert(Container.ResolverExpr, r.ServiceType), setup: Setup.Wrapper); - wrappers = wrappers.AddOrUpdate(typeof(IRegistrator), containerFactory); - wrappers = wrappers.AddOrUpdate(typeof(IContainer), containerFactory); + wrappers = wrappers + .AddOrUpdate(typeof(IRegistrator), containerFactory) + .AddOrUpdate(typeof(IContainer), containerFactory); - wrappers = wrappers.AddOrUpdate(typeof(IDisposable), + wrappers = wrappers.AddOrUpdate(typeof(IDisposable), new ExpressionFactory(r => r.IsResolutionRoot ? null : Container.GetResolutionScopeExpression(r), setup: Setup.Wrapper)); return wrappers; } + // todo: Probably move to container to consolidate work with factories. /// Returns wrapper factory. For open-generic wrapper - generated closed factory first. /// Wrapper request. /// Found wrapper factory or default null otherwise. @@ -2456,7 +2843,11 @@ public static Factory ResolveWrapperOrGetDefault(Request request) var factory = request.Container.GetWrapperFactoryOrDefault(actualServiceType); if (factory != null && factory.FactoryGenerator != null) - factory = factory.FactoryGenerator.GetGeneratedFactoryOrDefault(request); + factory = factory.FactoryGenerator.GetGeneratedFactory(request); + + if (factory != null && factory.Setup.Condition != null && + !factory.Setup.Condition(request.RequestInfo)) + return null; return factory; } @@ -2467,12 +2858,16 @@ private static Expression GetArrayExpression(Request request) var container = request.Container; var rules = container.Rules; - if (rules.ResolveIEnumerableAsLazyEnumerable && - collectionType.GetGenericDefinitionOrNull() == typeof(IEnumerable<>)) - return GetLazyEnumerableExpressionOrDefault(request); - var itemType = collectionType.GetArrayElementTypeOrNull() ?? collectionType.GetGenericParamsAndArgs()[0]; + if (rules.ResolveIEnumerableAsLazyEnumerable) + { + var lazyEnumerableExpr = GetLazyEnumerableExpressionOrDefault(request); + if (collectionType.GetGenericDefinitionOrNull() != typeof(IEnumerable<>)) + return Expression.Call(typeof(Enumerable), "ToArray", new[] { itemType }, lazyEnumerableExpr); + return lazyEnumerableExpr; + } + var requiredItemType = container.GetWrappedType(itemType, request.RequiredServiceType); var items = container.GetAllServiceFactories(requiredItemType) @@ -2483,15 +2878,15 @@ private static Expression GetArrayExpression(Request request) { var requiredItemOpenGenericType = requiredItemType.GetGenericDefinitionOrNull(); var openGenericItems = container.GetAllServiceFactories(requiredItemOpenGenericType) - .Select(f => new ServiceRegistrationInfo(f.Value, + .Select(f => new ServiceRegistrationInfo(f.Value, requiredItemType, // NOTE: Special service key with info about open-generic factory service type - new[] { requiredItemOpenGenericType, f.Key })) + new[] { requiredItemOpenGenericType, f.Key })) .ToArray(); items = items.Append(openGenericItems); } - // Append registered generic types with compatible variance, + // Append registered generic types with compatible variance, // e.g. for IHandler - IHandler is compatible with IHandler if B : A. var includeVariantGenericItems = requiredItemType.IsGeneric() && rules.VariantGenericTypesInResolvedCollection; if (includeVariantGenericItems) @@ -2506,10 +2901,12 @@ private static Expression GetArrayExpression(Request request) items = items.Append(variantGenericItems); } - // Composite pattern support: filter out composite root in available keys + // Composite pattern support: filter out composite parent service skip wrappers and decorators var parent = request.Parent; - if (!parent.IsEmpty && - parent.GetActualServiceType() == requiredItemType) // check fast for the parent of the same type + if (parent.FactoryType != FactoryType.Service) + parent = parent.Enumerate().FirstOrDefault(p => p.FactoryType == FactoryType.Service) ?? RequestInfo.Empty; + + if (!parent.IsEmpty && parent.GetActualServiceType() == requiredItemType) // check fast for the parent of the same type { items = items.Where(x => x.Factory.FactoryID != parent.FactoryID && (x.Factory.FactoryGenerator == null || !x.Factory.FactoryGenerator.GeneratedFactories.Enumerate().Any(f => @@ -2577,7 +2974,8 @@ private static Expression GetLazyEnumerableExpressionOrDefault(Request request) Throw.It(Error.NotPossibleToResolveLazyEnumerableInsideFuncWithArgs, request); var container = request.Container; - var itemType = request.ServiceType.GetGenericParamsAndArgs()[0]; + var collectionType = request.ServiceType; + var itemType = collectionType.GetArrayElementTypeOrNull() ?? collectionType.GetGenericParamsAndArgs()[0]; var requiredItemType = container.GetWrappedType(itemType, request.RequiredServiceType); // Composite pattern support: find composite parent key to exclude from result. @@ -2590,16 +2988,18 @@ private static Expression GetLazyEnumerableExpressionOrDefault(Request request) compositeParentRequiredType = parent.RequiredServiceType; } - var preresolveParent = container.RequestInfoToExpression(request.RequestInfo); + var resolverExpr = Container.GetResolverExpr(request); + var resolutionScopeExpr = Container.GetResolutionScopeExpression(request); + var preResolveParentExpr = container.RequestInfoToExpression(request.RequestInfo); - var callResolveManyExpr = Expression.Call(Container.ResolverExpr, _resolveManyMethod, + var callResolveManyExpr = Expression.Call(resolverExpr, _resolveManyMethod, Expression.Constant(itemType), container.GetOrAddStateItemExpression(request.ServiceKey), Expression.Constant(requiredItemType), container.GetOrAddStateItemExpression(compositeParentKey), Expression.Constant(compositeParentRequiredType, typeof(Type)), - preresolveParent, - Container.GetResolutionScopeExpression(request)); + preResolveParentExpr, + resolutionScopeExpr); if (itemType != typeof(object)) // cast to object is not required cause Resolve already return IEnumerable callResolveManyExpr = Expression.Call(typeof(Enumerable), "Cast", new[] { itemType }, callResolveManyExpr); @@ -2608,8 +3008,11 @@ private static Expression GetLazyEnumerableExpressionOrDefault(Request request) return Expression.New(lazyEnumerableCtor, callResolveManyExpr); } - // Result: r => new Lazy(() => r.Resolver.Resolve(key, ifUnresolved, requiredType)); - private static Expression GetLazyExpressionOrDefault(Request request) + /// Gets the expression for wrapper. + /// The resolution request. + /// if set to true then check for service registration before creating resolution expression. + /// Expression: r => new Lazy{TService}(() => r.Resolver.Resolve{TService}(key, ifUnresolved, requiredType)); + public static Expression GetLazyExpressionOrDefault(Request request, bool nullWrapperForUnresolvedService = false) { if (request.IsWrappedInFuncWithArgs(immediateParent: true)) Throw.It(Error.NotPossibleToResolveLazyInsideFuncWithArgs, request); @@ -2617,6 +3020,15 @@ private static Expression GetLazyExpressionOrDefault(Request request) var lazyType = request.GetActualServiceType(); var serviceType = lazyType.GetGenericParamsAndArgs()[0]; var serviceRequest = request.Push(serviceType); + + var serviceFactory = request.Container.ResolveFactory(serviceRequest); + if (serviceFactory == null) + return request.IfUnresolved == IfUnresolved.ReturnDefault + ? Expression.Constant(null, lazyType) + : null; + + serviceRequest = serviceRequest.WithResolvedFactory(serviceFactory, skipRecursiveDependencyCheck: true); + var serviceExpr = Resolver.CreateResolutionExpression(serviceRequest); // Note: the conversion is required in .NET 3.5 to handle lack of covariance for Func @@ -2630,31 +3042,56 @@ private static Expression GetLazyExpressionOrDefault(Request request) return Expression.New(wrapperCtor, factoryExpr); } - private static Expression GetFuncExpressionOrDefault(Request request) + private static Expression GetFuncOrActionExpressionOrDefault(Request request) { - var funcType = request.GetActualServiceType(); - var funcArgs = funcType.GetGenericParamsAndArgs(); - var serviceType = funcArgs[funcArgs.Length - 1]; + var wrapperType = request.GetActualServiceType(); + var isAction = wrapperType == typeof(Action); + if (!isAction) + { + var openGenericWrapperType = wrapperType.GetGenericDefinitionOrNull().ThrowIfNull(); + var funcIndex = FuncTypes.IndexOf(openGenericWrapperType); + if (funcIndex == -1) + { + isAction = ActionTypes.IndexOf(openGenericWrapperType) != -1; + Throw.If(!isAction); + } + } + + var argTypes = wrapperType.GetGenericParamsAndArgs(); + var argCount = isAction ? argTypes.Length : argTypes.Length - 1; + var serviceType = isAction ? typeof(void) : argTypes[argCount]; + + var flags = RequestFlags.IsWrappedInFunc; - ParameterExpression[] funcArgExprs = null; - if (funcArgs.Length > 1) + var argExprs = new ParameterExpression[argCount]; // may be empty, that's OK + if (argCount != 0) { - request = request.WithFuncArgs(funcType); - funcArgExprs = request.FuncArgs.Value; + for (var i = 0; i < argCount; ++i) + { + var argType = argTypes[i]; + var argName = "_" + argType.Name + i; // valid unique argument names for code generation + argExprs[i] = Expression.Parameter(argType, argName); + } + + request = request.WithArgs(argExprs); + flags |= RequestFlags.IsWrappedInFuncWithArgs; } - var serviceRequest = request.Push(serviceType); + var serviceRequest = request.Push(serviceType, flags: flags); var serviceFactory = request.Container.ResolveFactory(serviceRequest); - var serviceExpr = serviceFactory == null ? null : serviceFactory.GetExpressionOrDefault(serviceRequest); + if (serviceFactory == null) + return null; + + var serviceExpr = serviceFactory.GetExpressionOrDefault(serviceRequest); if (serviceExpr == null) return null; // Note: the conversation is required in .NET 3.5 to handle lack of covariance for Func // So that Func may be used for Func - if (serviceExpr.Type != serviceType) + if (!isAction && serviceExpr.Type != serviceType) serviceExpr = Expression.Convert(serviceExpr, serviceType); - return Expression.Lambda(funcType, serviceExpr, funcArgExprs); + return Expression.Lambda(wrapperType, serviceExpr, argExprs); } private static Expression GetLambdaExpressionExpressionOrDefault(Request request) @@ -2690,16 +3127,24 @@ private static Expression GetKeyValuePairExpressionOrDefault(Request request) return pairExpr; } - /// + /// Universal expression factory to wrap service with 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 - /// - private static Expression GetMetaExpressionOrDefault(Request request) + /// + /// Requested service. + /// Wrapper creation expression. + public static Expression GetMetaExpressionOrDefault(Request request) { var metaType = request.GetActualServiceType(); var typeArgs = metaType.GetGenericParamsAndArgs(); + + var metaCtor = metaType.GetConstructorOrNull(args: typeArgs) + .ThrowIfNull(Error.NotFoundMetaCtorWithTwoArgs, typeArgs, request); + var metadataType = typeArgs[1]; var serviceType = typeArgs[0]; @@ -2711,6 +3156,8 @@ private static Expression GetMetaExpressionOrDefault(Request request) if (serviceKey != null) factories = factories.Where(f => serviceKey.Equals(f.Key)); + // note: this may be issue potential of selecting only the first factory + // if the service keys for some reason are not unique var result = factories .FirstOrDefault(factory => { @@ -2749,10 +3196,7 @@ private static Expression GetMetaExpressionOrDefault(Request request) } var metadataExpr = request.Container.GetOrAddStateItemExpression(resultMetadata, metadataType); - - var metaCtor = metaType.GetSingleConstructorOrNull().ThrowIfNull(); - var metaExpr = Expression.New(metaCtor, serviceExpr, metadataExpr); - return metaExpr; + return Expression.New(metaCtor, serviceExpr, metadataExpr); } } @@ -2762,10 +3206,38 @@ public sealed class Rules /// No rules as staring point. public static readonly Rules Default = new Rules(); - /// Dependency nesting level where to split object graph into separate Resolve call - /// to optimize performance of large object graph. - /// At the moment the value is predefined. Not sure if it should be user-defined. - public readonly int LevelToSplitObjectGraphIntoResolveCalls = 6; + /// Default value for + public const int DefaultMaxObjectGraphSize = 32; + + /// Max number of dependencies including nested ones, + /// before splitting the graph with Resolve calls. + public int MaxObjectGraphSize { get; private set; } + + /// Sets . + /// To disable the limit please use + /// New value. Should be 1 or higher. + /// New rules. + public Rules WithMaxObjectGraphSize(int size) + { + Throw.If(size < 1); + var newRules = (Rules)MemberwiseClone(); + newRules.MaxObjectGraphSize = size; + return newRules; + } + + /// Disables the limitation, + /// so that object graph won't be split due this setting. + /// New rules. + public Rules WithoutMaxObjectGraphSize() + { + var newRules = (Rules)MemberwiseClone(); + newRules.MaxObjectGraphSize = -1; + return newRules; + } + + // todo: v3: Rename to remove + /// Obsolete: replaced with + public int LevelToSplitObjectGraphIntoResolveCalls { get; private set; } /// Shorthand to public FactoryMethodSelector FactoryMethod { get { return _made.FactoryMethod; } } @@ -2790,6 +3262,7 @@ public sealed class Rules return With(Made.Of(factoryMethod, parameters, propertiesAndFields)); } + // todo: may be add a override with option, e.g. to fallback from made.FactoryMethod to previous FM, used by MEF at least /// Returns new instance of the rules with specified . /// New Made.Of rules. /// Instructs to override registration level Made.Of @@ -2813,12 +3286,12 @@ public Rules With(Made made, bool overrideRegistrationMade = false) /// Single selected factory, or null if unable to select. public delegate Factory FactorySelectorRule(Request request, KeyValuePair[] factories); - /// Rules to select single matched factory default and keyed registered factory/factories. + /// Rules to select single matched factory default and keyed registered factory/factories. /// Selectors applied in specified array order, until first returns not null . /// Default behavior is throw on multiple registered default factories, cause it is not obvious what to use. public FactorySelectorRule FactorySelector { get; private set; } - /// Sets + /// Sets /// Selectors to set, could be null to use default approach. New rules. public Rules WithFactorySelector(FactorySelectorRule rule) { @@ -2849,8 +3322,32 @@ public static FactorySelectorRule SelectKeyedOverDefaultFactory(object serviceKe ?? factories.FirstOrDefault(f => f.Key.Equals(null)).Value; } + /// Specify the method signature for returning mutiple keyed factories. This is dynamic analog to the normal Container Registry. + /// + /// (optional) If null will request all factories of + /// Specifies what kind of service is requested. + /// Key-Factory pairs. + public delegate IEnumerable> DynamicRegistrationProvider( + Type serviceType, + object serviceKey, + FactoryType requiredFactoryType + // todo: other options like IfAlreadyRegistered? + ); + + /// Providers for resolving multiple not-registered services. Null by default. + public DynamicRegistrationProvider[] DynamicRegistrationProviders { get; private set; } + + /// Appends handler to current unknown service providers. + /// Rules to append. New Rules. + public Rules WithDynamicRegistrations(params DynamicRegistrationProvider[] rules) + { + var newRules = (Rules)MemberwiseClone(); + newRules.DynamicRegistrationProviders = newRules.DynamicRegistrationProviders.Append(rules); + return newRules; + } + /// Defines delegate to return factory for request not resolved by registered factories or prior rules. - /// Applied in specified array order until return not null . + /// Applied in specified array order until return not null . /// Request to return factory for Factory to resolve request, or null if unable to resolve. public delegate Factory UnknownServiceResolver(Request request); @@ -2867,7 +3364,7 @@ public Rules WithUnknownServiceResolvers(params UnknownServiceResolver[] rules) } /// Removes specified resolver from unknown service resolvers, and returns new Rules. - /// If no resolver was found then will stay the same instance, + /// If no resolver was found then will stay the same instance, /// so it could be check for remove success or fail. /// Rule tor remove. New rules. public Rules WithoutUnknownServiceResolver(UnknownServiceResolver rule) @@ -2952,24 +3449,25 @@ public Rules WithoutFallbackContainer(IContainer container) return newRules; } - /// . + /// See public IReuse DefaultReuseInsteadOfTransient { get; private set; } - /// Sets different default reuse per container rules. Default is Transient. - /// Reuse value. - /// New rules with new reuse. - public Rules WithDefaultReuseInsteadOfTransient(IReuse defaultReuseInsteadOfTransient) + // todo: v3: Rename to WithDefaultReuse, because using rules.WithDefaultReuseInsteadOfTransient(Reuse.Transient)) seems off. + /// The reuse used in case if reuse is unspecified (null) in Register methods. + /// Reuse to set. If null the will be used + /// New rules. + public Rules WithDefaultReuseInsteadOfTransient(IReuse reuse) { var newRules = (Rules)MemberwiseClone(); - newRules.DefaultReuseInsteadOfTransient = defaultReuseInsteadOfTransient; + newRules.DefaultReuseInsteadOfTransient = reuse ?? Reuse.Transient; return newRules; } - /// Given item object and its type should return item "pure" expression presentation, - /// without side-effects or external dependencies. + /// Given item object and its type should return item "pure" expression presentation, + /// without side-effects or external dependencies. /// e.g. for string "blah" Expression.Constant("blah", typeof(string)). /// If unable to convert should return null. - /// Item object. Item is not null. + /// Item object. Item is not null. /// Item type. Item type is not null. /// Expression or null. public delegate Expression ItemToExpressionConverterRule(object item, Type itemType); @@ -2999,7 +3497,7 @@ public bool ThrowIfDependencyHasShorterReuseLifespan public Rules WithoutThrowIfDependencyHasShorterReuseLifespan() { var newRules = (Rules)MemberwiseClone(); - newRules._settings ^= Settings.ThrowIfDependencyHasShorterReuseLifespan; + newRules._settings &= ~Settings.ThrowIfDependencyHasShorterReuseLifespan; return newRules; } @@ -3017,7 +3515,7 @@ public bool ThrowOnRegisteringDisposableTransient public Rules WithoutThrowOnRegisteringDisposableTransient() { var newRules = (Rules)MemberwiseClone(); - newRules._settings ^= Settings.ThrowOnRegisteringDisposableTransient; + newRules._settings &= ~Settings.ThrowOnRegisteringDisposableTransient; return newRules; } @@ -3028,11 +3526,11 @@ public bool TrackingDisposableTransients } /// Turns tracking of disposable transients in dependency parent scope, or in current scope if service - /// is resolved directly. - /// + /// is resolved directly. + /// /// If 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. @@ -3042,8 +3540,8 @@ public bool TrackingDisposableTransients public Rules WithTrackingDisposableTransients() { var newRules = (Rules)MemberwiseClone(); - newRules._settings |= Settings.TrackingDisposableTransients; - newRules._settings ^= Settings.ThrowOnRegisteringDisposableTransient; + newRules._settings |= Settings.TrackingDisposableTransients; // turning On + newRules._settings &= ~Settings.ThrowOnRegisteringDisposableTransient; // turning Off return newRules; } @@ -3058,7 +3556,7 @@ public bool EagerCachingSingletonForFasterAccess public Rules WithoutEagerCachingSingletonForFasterAccess() { var newRules = (Rules)MemberwiseClone(); - newRules._settings ^= Settings.EagerCachingSingletonForFasterAccess; + newRules._settings &= ~Settings.EagerCachingSingletonForFasterAccess; return newRules; } @@ -3087,7 +3585,7 @@ public bool ImplicitCheckForReuseMatchingScope public Rules WithoutImplicitCheckForReuseMatchingScope() { var newRules = (Rules)MemberwiseClone(); - newRules._settings ^= Settings.ImplicitCheckForReuseMatchingScope; + newRules._settings &= ~Settings.ImplicitCheckForReuseMatchingScope; return newRules; } @@ -3117,7 +3615,7 @@ public bool VariantGenericTypesInResolvedCollection public Rules WithoutVariantGenericTypesInResolvedCollection() { var newRules = (Rules)MemberwiseClone(); - newRules._settings ^= Settings.VariantGenericTypesInResolvedCollection; + newRules._settings &= ~Settings.VariantGenericTypesInResolvedCollection; return newRules; } @@ -3141,8 +3639,9 @@ public bool ImplicitOpenedRootScope } /// Specifies to open scope as soon as container is created (the same as for Singleton scope). - /// That way you don't need to call . - /// Implicitly opened scope will be disposed together with Singletons when container is disposed. + /// That way you don't need to call . + /// Implicitly opened scope will be disposed together with Singletons when container is disposed. + /// The name of root scope is . /// The setting is only valid for container without ambient scope context. /// Returns new rules with flag set. public Rules WithImplicitRootOpenScope() @@ -3174,6 +3673,8 @@ private Rules() { _made = Made.Default; _settings = DEFAULT_SETTINGS; + DefaultReuseInsteadOfTransient = Reuse.Transient; + MaxObjectGraphSize = DefaultMaxObjectGraphSize; } private Made _made; @@ -3181,15 +3682,15 @@ private Rules() [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, - ImplicitRootOpenScope = 1 << 8, - ThrowIfRuntimeStateRequired = 1 << 9 + ThrowIfDependencyHasShorterReuseLifespan = 1 << 1, + ThrowOnRegisteringDisposableTransient = 1 << 2, + TrackingDisposableTransients = 1 << 3, + ImplicitCheckForReuseMatchingScope = 1 << 4, + VariantGenericTypesInResolvedCollection = 1 << 5, + ResolveIEnumerableAsLazyEnumerable = 1 << 6, + EagerCachingSingletonForFasterAccess = 1 << 7, + ImplicitRootOpenScope = 1 << 8, + ThrowIfRuntimeStateRequired = 1 << 9 } private const Settings DEFAULT_SETTINGS = @@ -3222,6 +3723,19 @@ public static FactoryMethod Of(MemberInfo ctorOrMethodOrMember, ServiceInfo fact return new FactoryMethod(ctorOrMethodOrMember, factoryInfo); } + /// Discovers the static factory method or member by name in . + /// Should play nice with C# nameof operator. + /// Name or method or member. + /// Class with static member. + /// Factory method info. + public static FactoryMethod Of(string methodOrMemberName) + { + var methodOrMember = typeof(TFactory).GetAllMembers() + .SingleOrDefault(m => m.Name == methodOrMemberName) + .ThrowIfNull(); + return Of(methodOrMember); + } + /// Pretty prints wrapped method. Printed string. public override string ToString() { @@ -3229,7 +3743,11 @@ public override string ToString() .Append("::").Append(ConstructorOrMethodOrMember).ToString(); } - private static FactoryMethodSelector Constructor(bool mostResolvable = false, bool includeNonPublic = false) + // todo: may be add the @default constructor option + /// Easy way to specify non-public or / and most resolvable constructor. + /// + /// Constructor or null if not found. + public static FactoryMethodSelector Constructor(bool mostResolvable = false, bool includeNonPublic = false) { return request => { @@ -3247,10 +3765,10 @@ private static FactoryMethodSelector Constructor(bool mostResolvable = false, bo .Select(c => new { Ctor = c, Params = c.GetParameters() }) .OrderByDescending(x => x.Params.Length); - var rules = request.Container.Rules; - var selector = rules.OverrideRegistrationMade - ? rules.Parameters.OverrideWith(request.Made.Parameters) - : request.Made.Parameters.OverrideWith(rules.Parameters); + var containerRules = request.Rules; + var selector = containerRules.OverrideRegistrationMade + ? request.Made.Parameters.OverrideWith(containerRules.Parameters) + : containerRules.Parameters.OverrideWith(request.Made.Parameters); var parameterSelector = selector(request); @@ -3263,10 +3781,13 @@ private static FactoryMethodSelector Constructor(bool mostResolvable = false, bo } else { - // For Func with arguments, - // match constructor should contain all input arguments and + // For Func with arguments, + // match constructor should contain all input arguments and // the rest should be resolvable. - var funcType = request.ParentOrWrapper.ServiceType; + var funcType = !request.RawParent.IsEmpty + ? request.RawParent.ServiceType + : request.PreResolveParent.ServiceType; + var funcArgs = funcType.GetGenericParamsAndArgs(); var inputArgCount = funcArgs.Length - 1; @@ -3281,8 +3802,8 @@ private static FactoryMethodSelector Constructor(bool mostResolvable = false, bo var inputArgIndex = funcArgs.IndexOf(p.ParameterType); if (inputArgIndex == -1 || inputArgIndex == inputArgCount || (matchedIndecesMask & inputArgIndex << 1) != 0) - // input argument was already matched by another parameter - return false; + // input argument was already matched by another parameter + return false; matchedIndecesMask |= inputArgIndex << 1; return true; })).All(p => IsResolvableParameter(p, parameterSelector, request)); @@ -3297,12 +3818,12 @@ private static FactoryMethodSelector Constructor(bool mostResolvable = false, bo /// Searches for public constructor with most resolvable parameters or throws if not found. /// Works both for resolving service and Func{TArgs..., TService} - public static readonly FactoryMethodSelector ConstructorWithResolvableArguments = + public static readonly FactoryMethodSelector ConstructorWithResolvableArguments = Constructor(mostResolvable: true); /// Searches for constructor (including non public ones) with most resolvable parameters or throws if not found. /// Works both for resolving service and Func{TArgs..., TService} - public static readonly FactoryMethodSelector ConstructorWithResolvableArgumentsIncludingNonPublic = + public static readonly FactoryMethodSelector ConstructorWithResolvableArgumentsIncludingNonPublic = Constructor(mostResolvable: true, includeNonPublic: true); /// Checks that parameter is selected on requested path and with provided parameter selector. @@ -3312,12 +3833,12 @@ private static FactoryMethodSelector Constructor(bool mostResolvable = false, bo Func parameterSelector, Request request) { var parameterServiceInfo = parameterSelector(parameter) ?? ParameterServiceInfo.Of(parameter); - var parameterRequest = request.Push(parameterServiceInfo.WithDetails(ServiceDetails.IfUnresolvedReturnDefault, request)); + var parameterRequest = request.Push(parameterServiceInfo.WithDetails(ServiceDetails.IfUnresolvedReturnDefault, null)); if (parameterServiceInfo.Details.HasCustomValue) { var customValue = parameterServiceInfo.Details.CustomValue; - return customValue == null + return customValue == null || customValue.GetType().IsAssignableTo(parameterRequest.ServiceType); } @@ -3349,7 +3870,7 @@ public class Made /// That's mean the whole made become context based which affects caching public bool HasCustomDependencyValue { get; private set; } - /// Specifies how constructor parameters should be resolved: + /// Specifies how constructor parameters should be resolved: /// parameter service key and type, throw or return default value if parameter is unresolved. public ParameterSelector Parameters { get; private set; } @@ -3383,7 +3904,7 @@ public class Made /// Specifies injections rules for Constructor, Parameters, Properties and Fields. If no rules specified returns rules. /// (optional) (optional) (optional) /// New injection rules or . - public static Made Of(FactoryMethodSelector factoryMethod = null, + public static Made Of(FactoryMethodSelector factoryMethod = null, ParameterSelector parameters = null, PropertiesAndFieldsSelector propertiesAndFields = null) { return factoryMethod == null && parameters == null && propertiesAndFields == null @@ -3399,7 +3920,7 @@ public class Made { var methodReturnType = factoryMethod.ConstructorOrMethodOrMember.GetReturnTypeOrDefault(); - // Normalizes open-generic type to open-generic definition, + // Normalizes open-generic type to open-generic definition, // because for base classes and return types it may not be the case. if (methodReturnType != null && methodReturnType.IsOpenGeneric()) methodReturnType = methodReturnType.GetGenericTypeDefinition(); @@ -3415,7 +3936,7 @@ public class Made public static Made Of(MemberInfo factoryMethodOrMember, ServiceInfo factoryInfo = null, ParameterSelector parameters = null, PropertiesAndFieldsSelector propertiesAndFields = null) { - return Of(DryIoc.FactoryMethod.Of(factoryMethodOrMember, factoryInfo)); + return Of(DryIoc.FactoryMethod.Of(factoryMethodOrMember, factoryInfo), parameters, propertiesAndFields); } /// Creates factory specification with method or member selector based on request. @@ -3443,7 +3964,7 @@ public class Made /// Defines factory method using expression of constructor call (with properties), or static method call. /// Type with constructor or static method. - /// Expression tree with call to constructor with properties: + /// Expression tree with call to constructor with properties: /// new Car(Arg.Of()) { Color = Arg.Of("CarColor") }]]> /// or static method call Car.Create(Arg.Of())]]> /// (optional) Primitive custom values for dependencies. @@ -3458,7 +3979,7 @@ public class Made /// 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 . /// Factory type. Factory product type. - /// Returns or resolves factory instance. + /// Returns or resolves factory instance. /// Method, property or field expression returning service. /// (optional) Primitive custom values for dependencies. /// New Made specification. @@ -3679,7 +4200,7 @@ public sealed class TypedMade : Made var hasPrevArg = false; var argExprs = methodCallExpr.Arguments; - if (argExprs.Count == 2 && + if (argExprs.Count == 2 && argExprs[0].Type == typeof(string) && argExprs[1].Type != typeof(IfUnresolved)) // matches the Of overload for metadata { @@ -3735,7 +4256,7 @@ private static object GetArgExpressionValueOrThrow(Expression arg) return memberField.GetValue(memberOwner.Value); } } - + return Throw.For(Error.UnexpectedExpressionInsteadOfConstant, arg); } @@ -3822,7 +4343,7 @@ public static class Arg /// Ignored. public static TRequired Of(TRequired defaultValue, IfUnresolved ifUnresolved, object serviceKey) { return default(TRequired); } - /// Specifies argument index starting from 0 to use corresponding custom value factory, + /// Specifies argument index starting from 0 to use corresponding custom value factory, /// similar to String.Format "{0}, {1}, etc". /// Type of dependency. Difference from actual parameter type is ignored. /// Argument index starting from 0 Ignored. @@ -3852,7 +4373,7 @@ public static class Registrator /// Any implementation, e.g. . /// The service type to register. /// Implementation type. Concrete and open-generic class are supported. - /// (optional) implementation, e.g. . + /// (optional) implementation, e.g. . /// Default value means no reuse, aka Transient. /// (optional) specifies . /// (optional) Factory setup, by default is () @@ -3933,7 +4454,7 @@ public static class Registrator IfAlreadyRegistered ifAlreadyRegistered = IfAlreadyRegistered.AppendNotKeyed, object serviceKey = null) where TMadeResult : TService { - var factory = new ReflectionFactory(null, reuse, made, setup); + var factory = new ReflectionFactory(default(Type), reuse, made, setup); registrator.Register(factory, typeof(TService), serviceKey, ifAlreadyRegistered, isStaticallyChecked: true); } @@ -3959,9 +4480,10 @@ public static class Registrator /// Concrete or open-generic implementation type. public delegate void RegisterManyAction(IRegistrator r, Type[] serviceTypes, Type implType); + // todo: perf: Add optional @isStaticallyChecked to skip check for implemented types. /// Registers many service types with the same implementation. /// Registrator/Container - /// 1 or more service types. + /// 1 or more service types. /// Should implement service types. Will throw if not. /// (optional) (optional) How to create implementation instance. /// (optional) (optional) By default @@ -3997,7 +4519,7 @@ public static class Registrator /// Returns only those types that could be used as service types of . It means that /// for open-generic its service type should supply all type arguments /// Used by RegisterMany method. - /// Source type: may be concrete, abstract or generic definition. + /// Source type: may be concrete, abstract or generic definition. /// (optional) Include non public service types. /// Array of types or empty. public static Type[] GetImplementedServiceTypes(this Type type, bool nonPublicServiceTypes = false) @@ -4006,7 +4528,7 @@ public static Type[] GetImplementedServiceTypes(this Type type, bool nonPublicSe var selectedServiceTypes = serviceTypes.Where(t => (nonPublicServiceTypes || t.IsPublicOrNestedPublic()) && - // using Namespace+Name instead of FullName because later is null for generic type definitions + // using Namespace+Name instead of FullName because latter is null for generic type definitions ExcludedGeneralPurposeServiceTypes.IndexOf((t.Namespace + "." + t.Name).Split('`')[0]) == -1); if (type.IsGenericDefinition()) @@ -4023,14 +4545,21 @@ public static Type[] GetImplementedServiceTypes(this Type type, bool nonPublicSe /// Types. public static IEnumerable GetImplementationTypes(this Assembly assembly) { - return Portable.GetAssemblyTypes(assembly) - .Where(type => type.IsClass() && !type.IsAbstract() && !type.IsCompilerGenerated()); + return Portable.GetAssemblyTypes(assembly).Where(IsImplementationType); + } + + /// 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. + /// Type to check. True if implementation type. + public static bool IsImplementationType(this Type type) + { + return type.IsClass() && !type.IsAbstract() && !type.IsCompilerGenerated(); } /// Registers many implementations with their auto-figured service types. /// Registrator/Container to register with. /// Implementation type provider. - /// (optional) User specified registration action: + /// (optional) User specified registration action: /// may be used to filter registrations or specify non-default registration options, e.g. Reuse or ServiceKey, etc. /// (optional) Include non public service types. public static void RegisterMany(this IRegistrator registrator, IEnumerable implTypes, RegisterManyAction action, @@ -4082,7 +4611,7 @@ public static IEnumerable GetImplementationTypes(this Assembly assembly) /// (optional) Allow to select constructor/method to create service, specify how to inject its parameters and properties/fields. /// (optional) Factory setup, by default is , check class for available setups. /// (optional) policy to deal with case when service with such type and name is already registered. - /// (optional) Condition to select only specific service type to register. + /// (optional) Condition to select only specific service type to register. /// (optional) Include non public service types. /// (optional) service key (name). Could be of any of type with overridden and . public static void RegisterMany(this IRegistrator registrator, @@ -4108,7 +4637,7 @@ public static IEnumerable GetImplementationTypes(this Assembly assembly) /// (optional) implementation, e.g. . Default value means no reuse, aka Transient. /// (optional) Factory setup, by default is , check class for available setups. /// (optional) policy to deal with case when service with such type and name is already registered. - /// (optional) Condition to select only specific service type to register. + /// (optional) Condition to select only specific service type to register. /// (optional) Include non public service types. /// (optional) service key (name). Could be of any of type with overridden and . public static void RegisterMany(this IRegistrator registrator, Made.TypedMade made, @@ -4124,7 +4653,7 @@ public static IEnumerable GetImplementationTypes(this Assembly assembly) /// Registers many implementations with their auto-figured service types. /// Registrator/Container to register with. /// Assemblies with implementation/service types to register. - /// (optional) User specified registration action: + /// (optional) User specified registration action: /// may be used to filter registrations or specify non-default registration options, e.g. Reuse or ServiceKey, etc. /// (optional) Include non public service types. public static void RegisterMany(this IRegistrator registrator, IEnumerable implTypeAssemblies, @@ -4134,6 +4663,8 @@ public static IEnumerable GetImplementationTypes(this Assembly assembly) registrator.RegisterMany(implTypes, action, nonPublicServiceTypes); } + // todo: Add overload to specify list of service types to support case when I know contracts (service types) and provide impl locations (assemblies) + // and do not care about concrete implementation which is good principle. /// Registers many implementations with their auto-figured service types. /// Registrator/Container to register with. /// Assemblies with implementation/service types to register. @@ -4166,9 +4697,9 @@ public static IEnumerable GetImplementationTypes(this Assembly assembly) /// (optional) policy to deal with case when service with such type and name is already registered. /// (optional). Could be of any of type with overridden and . /// 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), + /// 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 instead: + /// Consider using instead: /// (Made.Of(() => new Car(Arg.Of())))]]>. /// public static void RegisterDelegate(this IRegistrator registrator, Func factoryDelegate, @@ -4190,9 +4721,9 @@ public static IEnumerable GetImplementationTypes(this Assembly assembly) /// (optional) policy to deal with case when service with such type and name is already registered. /// (optional) Could be of any of type with overridden and . /// 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), + /// 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 instead: + /// Consider using instead: /// (Made.Of(() => new Car(Arg.Of())))]]>. /// public static void RegisterDelegate(this IRegistrator registrator, Type serviceType, Func factoryDelegate, @@ -4221,7 +4752,7 @@ public static IEnumerable GetImplementationTypes(this Assembly assembly) // unique key to binds decorator factory and decorator registrations var factoryKey = new object(); - registrator.RegisterDelegate(_ => + registrator.RegisterDelegate(_ => new DecoratorDelegateFactory(getDecorator), serviceKey: factoryKey); @@ -4258,10 +4789,15 @@ public TDecoratee Decorate(TDecoratee decoratee, IResolver resolver) Throw.If(reuse is ResolutionScopeReuse, Error.ResolutionScopeIsNotSupportedForRegisterInstance, instance); reuse = reuse ?? Reuse.Singleton; - var setup = Setup.Default; + var scopedReuse = reuse as CurrentScopeReuse; + var scope = scopedReuse != null + ? ((IScopeAccess)container).GetCurrentNamedScope(scopedReuse.Name, throwIfNotFound: true) + : ((Container)container).SingletonScope; + + var setup = _defaultInstanceSetup; if (preventDisposal) { - instance = new[] {instance}; + instance = new[] { instance }; setup = _preventDisposableInstanceSetup; } if (weaklyReferenced) @@ -4289,7 +4825,7 @@ public TDecoratee Decorate(TDecoratee decoratee, IResolver resolver) return; } - var canReuseAlreadyRegisteredFactory = + var canReuseAlreadyRegisteredFactory = factory != null && factory.Reuse == reuse && factory.Setup == setup; if (canReuseAlreadyRegisteredFactory) factory.ReplaceInstance(instance); @@ -4299,15 +4835,17 @@ public TDecoratee Decorate(TDecoratee decoratee, IResolver resolver) if (!canReuseAlreadyRegisteredFactory) container.Register(factory, serviceType, serviceKey, ifAlreadyRegistered, isStaticallyChecked: false); - var request = container.EmptyRequest.Push(serviceType, serviceKey); - reuse.GetScopeOrDefault(request) - .ThrowIfNull(Error.NoMatchingScopeWhenRegisteringInstance, instance, reuse) - .SetOrAdd(reuse.GetScopedItemIdOrSelf(factory.FactoryID, request), instance); + scope.SetOrAdd(scope.GetScopedItemIdOrSelf(factory.FactoryID), instance); } - private static readonly Setup _weaklyReferencedInstanceSetup = Setup.With(weaklyReferenced: true); - private static readonly Setup _preventDisposableInstanceSetup = Setup.With(preventDisposal: true); - private static readonly Setup _weaklyReferencedAndPreventDisposableInstanceSetup = Setup.With(weaklyReferenced: true, preventDisposal: true); + private static readonly Setup _defaultInstanceSetup = + Setup.With(asResolutionCall: true); + private static readonly Setup _weaklyReferencedInstanceSetup = + Setup.With(weaklyReferenced: true, asResolutionCall: true); + private static readonly Setup _preventDisposableInstanceSetup = + Setup.With(preventDisposal: true, asResolutionCall: true); + private static readonly Setup _weaklyReferencedAndPreventDisposableInstanceSetup = + Setup.With(weaklyReferenced: true, preventDisposal: true, asResolutionCall: true); // todo: v3: remove /// Obsolete: use @@ -4319,7 +4857,7 @@ public TDecoratee Decorate(TDecoratee decoratee, IResolver resolver) preventDisposal, weaklyReferenced, serviceKey); } - /// Stores the externally created instance into open scope or singleton, + /// Stores the externally created instance into open scope or singleton, /// replacing the existing registration and instance if any. /// Specified instance type. May be a base type or interface of instance actual type. /// Container to register @@ -4333,7 +4871,7 @@ public TDecoratee Decorate(TDecoratee decoratee, IResolver resolver) container.UseInstance(typeof(TService), instance, preventDisposal, weaklyReferenced, serviceKey); } - /// Stores the externally created instance into open scope or singleton, + /// Stores the externally created instance into open scope or singleton, /// replacing the existing registration and instance if any. /// Container to register /// Runtime service type to register instance with @@ -4347,62 +4885,30 @@ public TDecoratee Decorate(TDecoratee decoratee, IResolver resolver) if (instance != null) instance.ThrowIfNotOf(serviceType, Error.RegisteringInstanceNotAssignableToServiceType); - var scope = container.GetCurrentScope(); - var reuse = scope != null ? Reuse.InCurrentNamedScope(scope.Name) : Reuse.Singleton; - - var setup = Setup.Default; if (preventDisposal) - { instance = new[] { instance }; - setup = _preventDisposableInstanceSetup; - } + if (weaklyReferenced) - { instance = new WeakReference(instance); - setup = preventDisposal - ? _weaklyReferencedAndPreventDisposableInstanceSetup - : _weaklyReferencedInstanceSetup; - } - - var factories = container.GetAllServiceFactories(serviceType); - if (serviceKey != null) - factories = factories.Where(f => serviceKey.Equals(f.Key)); - - InstanceFactory factory = null; - - // Replace the single factory - var factoriesList = factories.ToArray(); - if (factoriesList.Length == 1) - factory = factoriesList[0].Value as InstanceFactory; - - if (factory != null && factory.Reuse == reuse && factory.Setup == setup) - factory.ReplaceInstance(instance); - else - { - factory = new InstanceFactory(instance, reuse, setup); - container.Register(factory, serviceType, serviceKey, IfAlreadyRegistered.Replace, isStaticallyChecked: false); - } - var request = container.EmptyRequest.Push(serviceType, serviceKey); - reuse.GetScopeOrDefault(request) - .ThrowIfNull(Error.NoMatchingScopeWhenRegisteringInstance, instance, reuse) - .SetOrAdd(reuse.GetScopedItemIdOrSelf(factory.FactoryID, request), instance); + // todo: v3: remove the hack + ((Container)container).UseInstanceInternal(serviceType, instance, serviceKey); } /// Registers initializing action that will be called after service is resolved just before returning it to caller. /// Check example below for using initializer to automatically subscribe to singleton event aggregator. - /// You can register multiple initializers for single service. - /// Or you can register initializer for type to be applied for all services and use + /// You can register multiple initializers for single service. + /// Or you can register initializer for type to be applied for all services and use /// to filter target services. /// Any type implemented by requested service type including service type itself and object type. /// Usually is object. - /// Delegate with object and + /// Delegate with object and /// to resolve additional services required by initializer. /// (optional) Additional condition to select required target. /// (Reuse.Singleton); /// container.Register(); - /// + /// /// // Registers initializer for all subscribers implementing ISubscriber. /// container.RegisterInitiliazer((s, r) => r.Resolve().Subscribe(s)); /// ]]> @@ -4411,14 +4917,13 @@ public TDecoratee Decorate(TDecoratee decoratee, IResolver resolver) { initialize.ThrowIfNull(); registrator.Register( - made: Made.Of(r => _initializerMethod.MakeGenericMethod(typeof(TTarget), r.ServiceType), + made: Made.Of(r => _initializerMethod.MakeGenericMethod(typeof(TTarget), r.ServiceType), parameters: Parameters.Of.Type(_ => initialize)), - setup: Setup.DecoratorWith(useDecorateeReuse: true, condition: - r => r.GetKnownImplementationOrServiceType().IsAssignableTo(typeof(TTarget)) - && (condition == null || condition(r)))); + setup: Setup.DecoratorWith(useDecorateeReuse: true, + condition: r => r.ServiceType.IsAssignableTo(typeof(TTarget)) && (condition == null || condition(r)))); } - private static readonly MethodInfo _initializerMethod = + private static readonly MethodInfo _initializerMethod = typeof(Registrator).GetSingleMethodOrNull("Initializer", includeNonPublic: true).ThrowIfNull(); internal static TService Initializer( @@ -4430,17 +4935,17 @@ public TDecoratee Decorate(TDecoratee decoratee, IResolver resolver) /// Registers dispose action for reused target service. /// Target service type. - /// Registrator to use. + /// Registrator to use. /// Actual dispose action to be invoke when scope is disposed. /// (optional) Additional way to identify the service. - public static void RegisterDisposer(this IRegistrator registrator, + public static void RegisterDisposer(this IRegistrator registrator, Action dispose, Func condition = null) { dispose.ThrowIfNull(); var disposerKey = new object(); - registrator.RegisterDelegate(_ => new Disposer(dispose), + registrator.RegisterDelegate(_ => new Disposer(dispose), serviceKey: disposerKey, setup: Setup.With(useParentReuse: true)); @@ -4486,7 +4991,8 @@ public void Dispose() /// Returns true if is registered in container or its open generic definition is registered in container. /// Usually to explore or any other implementation. /// The type of the registered service. - /// (optional) Could be of any of type with overridden and . + /// (optional) Identifies registration via service key. + /// Not provided or null service key means to check the alone with any service key. /// (optional) factory type to lookup, by default. /// (optional) condition to specify what registered factory do you expect. /// True if is registered, false - otherwise. @@ -4499,7 +5005,8 @@ public void Dispose() /// Returns true if type is registered in container or its open generic definition is registered in container. /// The type of service. /// Usually to explore or any other implementation. - /// (optional) Could be of any of type with overridden and . + /// (optional) Identifies registration via service key. + /// Not provided or null service key means to check the alone with any service key. /// (optional) factory type to lookup, by default. /// (optional) condition to specify what registered factory do you expect. /// True if name="serviceType"/> is registered, false - otherwise. @@ -4669,20 +5176,20 @@ public static TService Resolve(this IResolver resolver, Type requiredS return resolver.ResolveMany(serviceType, behavior, serviceKey); } - internal static Expression CreateResolutionExpression(Request request, bool openResolutionScope = false) + internal static Expression CreateResolutionExpression(Request request, + bool openResolutionScope = false, bool isRuntimeDependency = false) { - PopulateDependencyResolutionCallExpressions(request, openResolutionScope); + request.ContainsNestedResolutionCall = true; var container = request.Container; - var serviceType = request.ServiceType; - var serviceKey = request.ServiceKey; - var newPreResolveParent = request.ParentOrWrapper; + if (!isRuntimeDependency && container.Rules.DependencyResolutionCallExpressions != null) + PopulateDependencyResolutionCallExpressions(request, openResolutionScope); - var serviceTypeExpr = Expression.Constant(serviceType, typeof(Type)); + var serviceTypeExpr = Expression.Constant(request.ServiceType, typeof(Type)); var ifUnresolvedExpr = Expression.Constant(request.IfUnresolved == IfUnresolved.ReturnDefault, typeof(bool)); var requiredServiceTypeExpr = Expression.Constant(request.RequiredServiceType, typeof(Type)); - var serviceKeyExpr = container.GetOrAddStateItemExpression(serviceKey, typeof(object)); + var serviceKeyExpr = container.GetOrAddStateItemExpression(request.ServiceKey, typeof(object)); // first ensure that we have parent scope if any to propagate it across resolution call boundaries var scopeExpr = Container.GetResolutionScopeExpression(request); @@ -4693,52 +5200,51 @@ internal static Expression CreateResolutionExpression(Request request, bool open var actualServiceTypeExpr = Expression.Constant(request.GetActualServiceType(), typeof(Type)); var scopeCtor = typeof(Scope).GetSingleConstructorOrNull().ThrowIfNull(); scopeExpr = Expression.New(scopeCtor, scopeExpr, - Expression.New(typeof(KV).GetSingleConstructorOrNull().ThrowIfNull(), + Expression.New(typeof(KV).GetSingleConstructorOrNull().ThrowIfNull(), actualServiceTypeExpr, serviceKeyExpr)); } + var resolverExpr = Container.GetResolverExpr(request); + // Only parent is converted to be passed to Resolve (the current request is formed by rest of Resolve parameters) - var preResolveParentExpr = container.RequestInfoToExpression(newPreResolveParent); + var parentRequestInfo = request.RawParent.IsEmpty ? request.PreResolveParent : request.RawParent.RequestInfo; + var preResolveParentExpr = container.RequestInfoToExpression(parentRequestInfo); var resolveCallExpr = Expression.Call( - Container.ResolverExpr, "Resolve", ArrayTools.Empty(), - serviceTypeExpr, serviceKeyExpr, ifUnresolvedExpr, requiredServiceTypeExpr, + resolverExpr, "Resolve", ArrayTools.Empty(), + serviceTypeExpr, serviceKeyExpr, ifUnresolvedExpr, requiredServiceTypeExpr, preResolveParentExpr, scopeExpr); - var resolveExpr = Expression.Convert(resolveCallExpr, serviceType); - return resolveExpr; + return Expression.Convert(resolveCallExpr, request.ServiceType); } private static void PopulateDependencyResolutionCallExpressions(Request request, bool openResolutionScope) { - var serviceType = request.ServiceType; - var serviceKey = request.ServiceKey; - var newPreResolveParent = request.ParentOrWrapper; - - var container = request.Container; - // Actually calls nested Resolution Call and stores produced expression in collection: // - if the collection to accumulate call expressions is defined and: // - Resolve call is the first nested in chain // - Resolve call is not repeated for recursive dependency, e.g. new A(new Lazy r.Resolve()>) and new B(new A()) - if (container.Rules.DependencyResolutionCallExpressions != null && - (request.PreResolveParent.IsEmpty || - !request.PreResolveParent.EqualsWithoutParent(newPreResolveParent))) + var preResolveParent = request.PreResolveParent; + if (preResolveParent.IsEmpty || + !request.RawParent.IsEmpty && !preResolveParent.EqualsWithoutParent(request.RawParent)) { + var serviceType = request.ServiceType; + var serviceKey = request.ServiceKey; + // Create scope for first nesting level or where corresponding setting is saying so var scope = request.Scope; if (scope == null || openResolutionScope) scope = new Scope(scope, new KV(serviceType, serviceKey)); - var newRequest = request.Container.EmptyRequest.Push(serviceType, serviceKey, + var newRequest = Request.Create(request.Container, serviceType, serviceKey, request.IfUnresolved, request.RequiredServiceType, scope, - newPreResolveParent); + request.ParentOrWrapper); var factory = request.Container.ResolveFactory(newRequest); var factoryExpr = factory == null ? null : factory.GetExpressionOrDefault(newRequest); if (factoryExpr != null) - container.Rules.DependencyResolutionCallExpressions.Swap( - expr => expr.AddOrUpdate(newRequest.RequestInfo, factoryExpr)); + request.Rules.DependencyResolutionCallExpressions.Swap(it => + it.AddOrUpdate(newRequest.RequestInfo, factoryExpr)); } } } @@ -4752,7 +5258,7 @@ public enum ResolveManyBehavior AsFixedArray } - /// Provides information required for service resolution: service type, + /// Provides information required for service resolution: service type, /// and optional : key, what to do if service unresolved, and required service type. public interface IServiceInfo { @@ -4790,7 +5296,7 @@ public class ServiceDetails if (defaultValue != null && ifUnresolved == IfUnresolved.Throw) ifUnresolved = IfUnresolved.ReturnDefault; - return new ServiceDetails(requiredServiceType, ifUnresolved, + return new ServiceDetails(requiredServiceType, ifUnresolved, serviceKey, metadataKey, metadata, defaultValue, hasCustomValue: false); } @@ -4842,12 +5348,15 @@ public override string ToString() 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(new KeyValuePair(MetadataKey, Metadata)); if (IfUnresolved != IfUnresolved.Throw) (s.Length == 0 ? s.Append('{') : s.Append(", ")).Append(IfUnresolved); return (s.Length == 0 ? s : s.Append('}')).ToString(); } - private ServiceDetails(Type requiredServiceType, IfUnresolved ifUnresolved, + private ServiceDetails(Type requiredServiceType, IfUnresolved ifUnresolved, object serviceKey, string metadataKey, object metadata, object value, bool hasCustomValue) { @@ -4864,50 +5373,51 @@ public override string ToString() /// Contains tools for combining or propagating of independent of its concrete implementations. public static class ServiceInfoTools { + /// Creates service info with new type but keeping the details. + /// Source info. New service type. + /// New info. + public static IServiceInfo With(this IServiceInfo source, Type serviceType) + { + return source.Create(serviceType, source.Details); + } + // todo: Should be renamed or better to be removed, the whole operation should be hidden behind abstraction + // todo: Remove request parameter as it is not used anymore /// Combines service info with details: the main task is to combine service and required service type. /// Type of . - /// Source info. Details to combine with info. + /// Source info. Details to combine with info. /// Owner request. Original source or new combined info. public static T WithDetails(this T serviceInfo, ServiceDetails details, Request request) where T : IServiceInfo { - return WithRequiredServiceType(serviceInfo, details, request); + 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); + } + + return WithRequiredServiceType(serviceInfo, details, null); } internal static T WithRequiredServiceType(T serviceInfo, ServiceDetails details, Request request) where T : IServiceInfo { var serviceType = serviceInfo.ServiceType; - var requiredServiceType = details == null ? null : details.RequiredServiceType; - if (requiredServiceType != null) - { - if (requiredServiceType == serviceType) - { - details = ServiceDetails.Of(null, details.ServiceKey, details.IfUnresolved); - } - else if (requiredServiceType.IsOpenGeneric()) - { - // Checks that open-generic has corresponding closed service type to fill in its generic parameters. - var openGenericServiceType = serviceType.GetGenericDefinitionOrNull(); - if (openGenericServiceType == null || - requiredServiceType != openGenericServiceType && - Array.IndexOf(requiredServiceType.GetImplementedTypes(), openGenericServiceType) == -1) - Throw.It(Error.ServiceIsNotAssignableFromOpenGenericRequiredServiceType, - openGenericServiceType, requiredServiceType, request); - } - else - { - var container = request.Container; - var wrappedType = container.GetWrappedType(serviceType, null); - if (wrappedType != null) - { - var wrappedRequiredType = container.GetWrappedType(requiredServiceType, null); - wrappedType.ThrowIfNotImplementedBy(wrappedRequiredType, Error.WrappedNotAssignableFromRequiredType, - request); - } - } - } + var requiredServiceType = details.RequiredServiceType; + + if (requiredServiceType != null && requiredServiceType == serviceType) + details = ServiceDetails.Of(null, + details.ServiceKey, details.IfUnresolved, details.DefaultValue, + details.MetadataKey, details.Metadata); return serviceType == serviceInfo.ServiceType && (details == null || details == serviceInfo.Details) @@ -4917,7 +5427,7 @@ internal static T WithRequiredServiceType(T serviceInfo, ServiceDetails detai // todo: v3: remove unused @shouldInheritServiceKey parameter // todo: v3: make @container parameter non optional - /// Enables propagation/inheritance of info between dependency and its owner: + /// Enables propagation/inheritance of info between dependency and its owner: /// for instance for wrappers. /// Dependency info. /// Dependency holder/owner info. @@ -4926,11 +5436,9 @@ internal static T WithRequiredServiceType(T serviceInfo, ServiceDetails detai /// required for /// Either input dependency info, or new info with properties inherited from the owner. public static IServiceInfo InheritInfoFromDependencyOwner(this IServiceInfo dependency, IServiceInfo owner, - bool shouldInheritServiceKey = false, FactoryType ownerType = FactoryType.Service, + bool shouldInheritServiceKey = false, FactoryType ownerType = FactoryType.Service, IContainer container = null) { - container = container.ThrowIfNull(); - var ownerDetails = owner.Details; if (ownerDetails == null || ownerDetails == ServiceDetails.Default) return dependency; @@ -4951,7 +5459,8 @@ internal static T WithRequiredServiceType(T serviceInfo, ServiceDetails detai // propagate key and meta to the actual service if (ownerType == FactoryType.Wrapper || - ownerType == FactoryType.Decorator && // propagate key only to decorated (and possibly wrapped) service + // for decorated dependency, but not for other decorator dependencies + ownerType == FactoryType.Decorator && container.GetWrappedType(serviceType, requiredServiceType).IsAssignableTo(owner.ServiceType)) { if (serviceKey == null) @@ -4966,7 +5475,7 @@ internal static T WithRequiredServiceType(T serviceInfo, ServiceDetails detai } } - if (ownerType != FactoryType.Service && ownerRequiredServiceType != null && + if (ownerType != FactoryType.Service && ownerRequiredServiceType != null && requiredServiceType == null) // if only dependency does not have its own requiredServiceType = ownerRequiredServiceType; @@ -4978,13 +5487,24 @@ internal static T WithRequiredServiceType(T serviceInfo, ServiceDetails detai if (serviceType == requiredServiceType) requiredServiceType = null; - var serviceDetails = ServiceDetails.Of(requiredServiceType, - serviceKey, ifUnresolved, dependencyDetails.DefaultValue, + var serviceDetails = ServiceDetails.Of(requiredServiceType, + serviceKey, ifUnresolved, dependencyDetails.DefaultValue, metadataKey, metadata); return dependency.Create(serviceType, serviceDetails); } + /// Returns required service type if it is specified and assignable to service type, + /// otherwise returns service type. + /// The type to be used for lookup in registry. + public static Type GetActualServiceType(this IServiceInfo info) + { + var requiredServiceType = info.Details.RequiredServiceType; + + return requiredServiceType != null && requiredServiceType.IsAssignableTo(info.ServiceType) + ? requiredServiceType : info.ServiceType; + } + /// Appends info string representation into provided builder. /// String builder to print to. Info to print. /// String builder with appended info. @@ -4996,16 +5516,19 @@ public static StringBuilder Print(this StringBuilder s, IServiceInfo info) } } - /// Represents custom or resolution root service info, there is separate representation for parameter, + /// Represents custom or resolution root service info, there is separate representation for parameter, /// property and field dependencies. public class ServiceInfo : IServiceInfo { + /// Empty service info for convenience. + public static readonly IServiceInfo Empty = new ServiceInfo(null); + /// Creates info out of provided settings /// Service type /// (optional) If unresolved policy. Set to Throw if not specified. /// (optional) Service key. /// Created info. - public static ServiceInfo Of(Type serviceType, + public static ServiceInfo Of(Type serviceType, IfUnresolved ifUnresolved = IfUnresolved.Throw, object serviceKey = null) { return Of(serviceType, null, ifUnresolved, serviceKey); @@ -5016,19 +5539,21 @@ public class ServiceInfo : IServiceInfo /// Registered service type to search for. /// (optional) If unresolved policy. Set to Throw if not specified. /// (optional) Service key. - /// (optional) Required metadata key + /// (optional) Required metadata key /// Required metadata or the value if key passed. /// Created info. - public static ServiceInfo Of(Type serviceType, Type requiredServiceType, + public static ServiceInfo Of(Type serviceType, Type requiredServiceType, IfUnresolved ifUnresolved = IfUnresolved.Throw, object serviceKey = null, string metadataKey = null, object metadata = null) { serviceType.ThrowIfNull(); return serviceKey == null && requiredServiceType == null && metadataKey == null && metadata == null - && ifUnresolved == IfUnresolved.Throw + ? (ifUnresolved == IfUnresolved.Throw ? new ServiceInfo(serviceType) - : new WithDetails(serviceType, + : new WithDetails(serviceType, + ServiceDetails.IfUnresolvedReturnDefault)) + : new WithDetails(serviceType, ServiceDetails.Of(requiredServiceType, serviceKey, ifUnresolved, null, metadataKey, metadata)); } @@ -5095,7 +5620,7 @@ private class TypedWithDetails : Typed #endregion } - /// Provides for parameter, + /// Provides for parameter, /// by default using parameter name as . /// For parameter default setting is . public class ParameterServiceInfo : IServiceInfo @@ -5112,7 +5637,7 @@ public static ParameterServiceInfo Of(ParameterInfo parameter) var defaultValue = isOptional ? parameter.DefaultValue : null; var hasDefaultValue = defaultValue != null && parameter.ParameterType.IsTypeOf(defaultValue); - return !isOptional + return !isOptional ? new ParameterServiceInfo(parameter) : new WithDetails(parameter, !hasDefaultValue ? ServiceDetails.IfUnresolvedReturnDefault @@ -5293,19 +5818,79 @@ public TypeWithDetails(FieldInfo field, Type serviceType, ServiceDetails details #endregion } + /// Memoized checks and conditions of two kinds: inherited down dependency chain and not. + [Flags] + public enum RequestFlags + { + /// Not inherited + TracksTransientDisposable = 1 << 1, + /// Not inherited + IsServiceCollection = 1 << 2, + + /// Inherited + IsSingletonOrDependencyOfSingleton = 1 << 3, + /// Inherited + IsWrappedInFunc = 1 << 4, + /// Inherited + IsWrappedInFuncWithArgs = 1 << 5, + } + /// Contains resolution stack with information about resolved service and factory for it, /// Additionally request contain weak reference to . That the all required information for resolving services. /// Request implements interface on top of provided container, which could be use by delegate factories. public sealed class Request { - /// Creates empty request associated with provided . - /// Every resolution will start from this request by pushing service information into, and then resolving it. - /// Reference to container issued the request. Could be changed later with method. - /// New empty request. - public static Request CreateEmpty(ContainerWeakRef container) + /// Not inherited down dependency chain. + public static readonly RequestFlags NotInheritedFlags + = RequestFlags.TracksTransientDisposable + | RequestFlags.IsServiceCollection; + + // todo: v3: remove + /// Obsolete: replaced with /. + public static Request CreateEmpty(Container container) { - var resolverContext = new ResolverContext(container, container, null, RequestInfo.Empty); - return new Request(resolverContext, parent: null, requestInfo: RequestInfo.Empty, made: null, funcArgs: null); + var resolverContext = new RequestContext(container, container, null, RequestInfo.Empty); + return new Request(resolverContext, null, ServiceInfo.Empty, null, null, null, default(RequestFlags)); + } + + private static readonly Request _empty = new Request(null, null, ServiceInfo.Empty, null, null, null, default(RequestFlags)); + + /// Creates empty request associated wit container. + /// The shared part of request is stored in request context. Pre-request info is also store once in shared context. + /// Associated container - part of request context. + /// Service type to resolve. + /// (optional) Service key to resolve. + /// (optional) How to handle unresolved service. + /// (optional) Actual registered or unwrapped service type to look for. + /// (optional) Pre-request info: resolution scope. // todo: v3: remove + /// (optional) Request info preceding Resolve call. + /// New request with provided info. + public static Request Create(IContainer container, Type serviceType, + object serviceKey = null, IfUnresolved ifUnresolved = IfUnresolved.Throw, Type requiredServiceType = null, + IScope scope = null, RequestInfo preResolveParent = null) + { + serviceType.ThrowIfNull() + .ThrowIf(serviceType.IsOpenGeneric(), Error.ResolvingOpenGenericServiceTypeIsNotPossible); + + if (preResolveParent == null) + preResolveParent = RequestInfo.Empty; + + var resolverContext = new RequestContext(container, (IScopeAccess)container, scope, preResolveParent); + + IServiceInfo serviceInfo = ServiceInfo.Of(serviceType, requiredServiceType, ifUnresolved, serviceKey); + + // inherit some flags and service details from parent (if any) + var flags = default(RequestFlags); + if (!preResolveParent.IsEmpty) + { + serviceInfo = serviceInfo.InheritInfoFromDependencyOwner(preResolveParent.ServiceInfo, + ownerType: preResolveParent.FactoryType, container: container); + + // filter out not propagated flags + flags = preResolveParent.Flags & ~NotInheritedFlags; + } + + return new Request(resolverContext, _empty, serviceInfo, null, null, null, flags); } /// Indicates that request is empty initial request: there is no in such a request. @@ -5321,132 +5906,140 @@ public static Request CreateEmpty(ContainerWeakRef container) public bool IsResolutionRoot { get { return IsResolutionCall && PreResolveParent.IsEmpty; } } /// Request prior to Resolve call. - public RequestInfo PreResolveParent { get { return _resolverContext.PreResolveParent; } } - - /// Returns true for the First Service in resolve call. - public bool IsFirstNonWrapperInResolutionCall() - { - if (IsResolutionCall && RawParent.FactoryType != FactoryType.Wrapper) - return true; - var p = RawParent; - while (!p.IsEmpty && p.FactoryType == FactoryType.Wrapper) - p = p.RawParent; - return p.IsEmpty; - } + public RequestInfo PreResolveParent { get { return _requestContext.PreResolveParent; } } /// Checks if request is wrapped in Func, /// where Func is one of request immediate wrappers. /// True if has Func ancestor. public bool IsWrappedInFunc() { - return ParentOrWrapper.Enumerate() - .Any(r => r.FactoryType == FactoryType.Wrapper && r.GetActualServiceType().IsFunc()); + return (_flags & RequestFlags.IsWrappedInFunc) != 0; } - /// Checks if request has parent with service type of Func with arguments. - /// If set indicate to check for immediate parent only, + /// Checks if request has parent with service type of Func with arguments. + /// If set indicate to check for immediate parent only, /// otherwise will check whole parent chain. /// True if has Func with arguments ancestor. public bool IsWrappedInFuncWithArgs(bool immediateParent = false) { - var parent = ParentOrWrapper; - return immediateParent - ? parent.FactoryType == FactoryType.Wrapper && parent.GetActualServiceType().IsFuncWithArgs() - : !parent.FirstOrEmpty(p => - p.FactoryType == FactoryType.Wrapper && p.GetActualServiceType().IsFuncWithArgs()).IsEmpty; + if ((_flags & RequestFlags.IsWrappedInFuncWithArgs) != 0) + { + if (!immediateParent) + return true; // skip other checks + + // check if parent is not wrapped itself + + // first run-time parent + if (!RawParent.IsEmpty) + return (RawParent._flags & RequestFlags.IsWrappedInFuncWithArgs) == 0; + + // and if run-time parent does not exist then check the preresolve parent + if (!PreResolveParent.IsEmpty) + return (PreResolveParent.Flags & RequestFlags.IsWrappedInFuncWithArgs) != 0; + } + + return false; + } + + /// Indicates that requested service is transient disposable that should be tracked. + public bool TracksTransientDisposable + { + get { return (_flags & RequestFlags.TracksTransientDisposable) != 0; } } - /// Returns service parent of request, skipping intermediate wrappers if any. - public RequestInfo Parent + /// Indicates the request is singleton or has singleton upper in dependency chain. + public bool IsSingletonOrDependencyOfSingleton { - get - { - return IsEmpty ? RequestInfo.Empty - : RawParent.IsEmpty ? PreResolveParent.FirstOrEmpty(p => p.FactoryType != FactoryType.Wrapper) - : RawParent.FactoryType != FactoryType.Wrapper ? RawParent.RequestInfo - : RawParent.Parent; - } + get { return (_flags & RequestFlags.IsSingletonOrDependencyOfSingleton) != 0; } } - /// Returns direct parent either it service or not (wrapper). - /// In comparison with logical which returns first service parent skipping wrapper if any. - public RequestInfo ParentOrWrapper + /// Gathers the info from resolved dependency graph. + /// If dependency injected asResolutionCall the whole graph is not cacheable (issue #416). + /// True if contains, false - otherwise or if not known. + public bool ContainsNestedResolutionCall { - get { return IsEmpty ? RequestInfo.Empty - : RawParent.IsEmpty ? PreResolveParent - : RawParent.RequestInfo; } + get { return _requestContext.ContainsNestedResolutionCall; } + set { if (value) _requestContext.ContainsNestedResolutionCall = true; } } - /// Gets first ancestor request which satisfies the condition, - /// or empty if no ancestor is found. - /// (optional) Condition to stop on. - /// Request info of found parent. - public RequestInfo Ancestor(Func condition = null) + /// Provides approximate nummber of dependencies in resolution graph (starting from Resolve method), + /// excluding registered delegates, instances, and wrappers. + public int DependencyCount { get { return _requestContext.DependencyCount; } } + + /// Returns true if object grapth should be split due setting. + /// True if should be split, and false otherwise. + public bool ShouldSplitObjectGraph() { - var parent = ParentOrWrapper; - return condition == null ? parent : parent.FirstOrEmpty(condition); + if (FactoryType != FactoryType.Service) + return false; + var maxObjectGraphSize = Rules.MaxObjectGraphSize; + return maxObjectGraphSize != -1 && DependencyCount > maxObjectGraphSize; } - /// Weak reference to container. May be replaced in request flowed from parent to child container. - public ContainerWeakRef ContainerWeakRef { get { return _resolverContext.ContainerWeakRef; } } + /// Returns service parent of request, skipping intermediate wrappers if any. + public RequestInfo Parent { get { return RequestInfo.Parent; } } + + /// Returns direct parent either it service or not (wrapper). + /// In comparison with logical which returns first service parent skipping wrapper if any. + public RequestInfo ParentOrWrapper { get { return RequestInfo.ParentOrWrapper; } } - /// Provides access to container currently bound to request. + /// Provides access to container currently bound to request. /// By default it is container initiated request by calling resolve method, /// but could be changed along the way: for instance when resolving from parent container. - public IContainer Container { get { return _resolverContext.ContainerWeakRef.Container; } } + public IContainer Container { get { return _requestContext.Container; } } + + /// Shortcut to issued container rules. + public Rules Rules { get { return _requestContext.Container.Rules; } } /// Separate from container because while container may be switched from parent to child, scopes should be from child/facade. - public IScopeAccess Scopes { get { return _resolverContext.ScopesWeakRef.Scopes; } } + public IScopeAccess Scopes { get { return _requestContext.Scopes; } } + + /// Singletons + public IScope SingletonScope { get { return ((Container)_requestContext.Container).SingletonScope; } } + + /// Weak reference to container. May be replaced in request flowed from parent to child container. + public ContainerWeakRef ContainerWeakRef { get { return _requestContext.Container.ContainerWeakRef; } } - /// Optionally associated resolution scope. - public IScope Scope { get { return _resolverContext.Scope; } } + /// Resolution scope. + public IScope Scope { get { return _requestContext.Scope; } } /// (optional) Made spec used for resolving request. - public readonly Made Made; + public Made Made { get { return _factory == null ? null : _factory.Made; } } /// User provided arguments: key tracks what args are still unused. /// Mutable: tracks used arguments public readonly KV FuncArgs; - /// Counting nested levels. May be used to split object graph if level is too deep. - public readonly int Level; - /// Requested service type. - public Type ServiceType { get { return RequestInfo.ServiceType; } } + public Type ServiceType { get { return _serviceInfo.ServiceType; } } /// Optional service key to identify service of the same type. - public object ServiceKey { get { return RequestInfo.ServiceKey; } } + public object ServiceKey { get { return _serviceInfo.Details.ServiceKey; } } /// Metadata key to find in metadata dictionary in resolved service. - public string MetadataKey - { - get { return RequestInfo == null ? null : RequestInfo.MetadataKey; } - } + public string MetadataKey { get { return _serviceInfo.Details.MetadataKey; } } /// Metadata or the value (if key specified) to find in resolved service. - public object Metadata - { - get { return RequestInfo == null ? null : RequestInfo.Metadata; } - } + public object Metadata { get { return _serviceInfo.Details.Metadata; } } /// Policy to deal with unresolved service. - public IfUnresolved IfUnresolved { get { return RequestInfo.IfUnresolved; } } + public IfUnresolved IfUnresolved { get { return _serviceInfo.Details.IfUnresolved; } } /// Required service type if specified. - public Type RequiredServiceType { get { return RequestInfo.RequiredServiceType; } } + public Type RequiredServiceType { get { return _serviceInfo.Details.RequiredServiceType; } } /// Implementation FactoryID. /// The default unassigned value of ID is 0. - public int FactoryID { get { return RequestInfo.FactoryID; } } + public int FactoryID { get { return _factory.ThrowIfNull().FactoryID; } } /// Type of factory: Service, Wrapper, or Decorator. - public FactoryType FactoryType { get { return RequestInfo.FactoryType; } } + public FactoryType FactoryType { get { return _factory.ThrowIfNull().FactoryType; } } /// Service implementation type if known. - public Type ImplementationType { get { return RequestInfo.ImplementationType; } } + public Type ImplementationType { get { return _factory.ThrowIfNull().ImplementationType; } } /// Service reuse. - public IReuse Reuse { get { return RequestInfo.Reuse; } } + public IReuse Reuse { get { return _reuse; } } /// Relative number representing reuse lifespan. public int ReuseLifespan { get { return Reuse == null ? 0 : Reuse.Lifespan; } } @@ -5455,63 +6048,47 @@ public object Metadata /// The type to be used for lookup in registry. public Type GetActualServiceType() { - return RequestInfo.GetActualServiceType(); + return _serviceInfo.GetActualServiceType(); } /// Creates new request with provided info, and attaches current request as new request parent. - /// Info about service to resolve. (optional) Resolution scope. - /// (optional) Request info beyond/preceding Resolve call. + /// Info about service to resolve. + /// (optional) Pushed flags. /// New request for provided info. /// Existing/parent request should be resolved to factory (), before pushing info into it. - public Request Push(IServiceInfo info, IScope scope = null, RequestInfo preResolveParent = null) + public Request Push(IServiceInfo info, RequestFlags flags = default(RequestFlags)) { info.ThrowIfNull(); - if (IsEmpty) - { - preResolveParent = preResolveParent ?? RequestInfo.Empty; - var resolverContext = _resolverContext.With(scope).With(preResolveParent); + if (_factory == null) + Throw.It(Error.PushingToRequestWithoutFactory, info, this); - var requestInfo = Push(preResolveParent, info, Container); + var parentInfo = ChangeIfUnresolvedForCollectionServiceDependency(); - return new Request(resolverContext, this, requestInfo, made: null, funcArgs: null, level: 1); - } + var inheritedInfo = info.InheritInfoFromDependencyOwner(parentInfo, ownerType: FactoryType, container: Container); + var inheritedFlags = _flags & ~NotInheritedFlags | flags; - Throw.If(RequestInfo.FactoryID == 0, Error.PushingToRequestWithoutFactory, info.ThrowIfNull(), this); - - var inheritedRequestInfo = Push(RequestInfo, info, Container); - - return new Request(_resolverContext, this, inheritedRequestInfo, null, FuncArgs, Level + 1); + return new Request(_requestContext, this, inheritedInfo, null, null, FuncArgs, inheritedFlags); } - private static RequestInfo Push(RequestInfo parent, IServiceInfo serviceInfo, IContainer container) + // todo: v3: review and remove if possible + // if service info is dependency of service wrapped in collection, + // then change policy to collection policy + private IServiceInfo ChangeIfUnresolvedForCollectionServiceDependency() { - if (parent == null || parent.IsEmpty) - return RequestInfo.Empty.Push(serviceInfo); - - // todo: v3: review and remove if possible - // if service info is dependency of service wrapped in collection, - // then change policy to collection policy - var parentInfo = parent.ServiceInfo; - if (parent.IfUnresolved == IfUnresolved.ReturnDefault) + if (IfUnresolved == IfUnresolved.ReturnDefault && FactoryType == FactoryType.Service) { - if (parent.FactoryType == FactoryType.Service) - { - var collectionParent = parent.Enumerate().FirstOrDefault(p => - p.FactoryType == FactoryType.Wrapper && - p.GetActualServiceType().IsSupportedCollectionType()); - if (collectionParent != null && - collectionParent.IfUnresolved == IfUnresolved.Throw) - { - parentInfo = ServiceInfo.Of(parentInfo.ServiceType, - parentInfo.Details.RequiredServiceType, IfUnresolved.Throw, parentInfo.Details.ServiceKey); - } - } + var p = RequestInfo; + while (!p.IsEmpty && + !(p.FactoryType == FactoryType.Wrapper && p.GetActualServiceType().IsSupportedCollectionType())) + p = p.ParentOrWrapper; + + if (!p.IsEmpty && p.IfUnresolved == IfUnresolved.Throw) + return ServiceInfo.Of(ServiceType, RequiredServiceType, IfUnresolved.Throw, ServiceKey, + MetadataKey, Metadata); } - var newInfo = serviceInfo.InheritInfoFromDependencyOwner(parentInfo, - ownerType: parent.FactoryType, container: container); - return parent.Push(newInfo); + return _serviceInfo; } /// Composes service description into and calls Push. @@ -5521,14 +6098,17 @@ private static RequestInfo Push(RequestInfo parent, IServiceInfo serviceInfo, IC /// (optional) Registered/unwrapped service type to find. /// (optional) Resolution scope. /// (optional) Request info preceding Resolve call. + /// (optional) Sets some flags. /// New request with provided info. public Request Push(Type serviceType, object serviceKey = null, IfUnresolved ifUnresolved = IfUnresolved.Throw, Type requiredServiceType = null, - IScope scope = null, RequestInfo preResolveParent = null) + IScope scope = null, RequestInfo preResolveParent = null, RequestFlags flags = default(RequestFlags)) { - serviceType.ThrowIfNull().ThrowIf(serviceType.IsOpenGeneric(), Error.ResolvingOpenGenericServiceTypeIsNotPossible); - var details = ServiceDetails.Of(requiredServiceType, serviceKey, ifUnresolved); - return Push(ServiceInfo.Of(serviceType).WithDetails(details, this), scope ?? Scope, preResolveParent); + serviceType.ThrowIfNull() + .ThrowIf(serviceType.IsOpenGeneric(), Error.ResolvingOpenGenericServiceTypeIsNotPossible); + + var serviceInfo = ServiceInfo.Of(serviceType, requiredServiceType, ifUnresolved, serviceKey); + return Push(serviceInfo, flags); } /// Allow to switch current service info to new one: for instance it is used be decorators. @@ -5536,119 +6116,232 @@ private static RequestInfo Push(RequestInfo parent, IServiceInfo serviceInfo, IC /// New request with new service info but the same implementation and context. public Request WithChangedServiceInfo(Func getInfo) { - var newRequestInfo = RequestInfo.With(getInfo); - return new Request(_resolverContext, RawParent, newRequestInfo, Made, FuncArgs, Level); + return new Request(_requestContext, RawParent, getInfo(_serviceInfo), _factory, _reuse, FuncArgs, _flags); } /// Sets service key to passed value. Required for multiple default services to change null key to /// actual /// Key to set. - public void ChangeServiceKey(object serviceKey) // NOTE: May be removed in future versions. + public void ChangeServiceKey(object serviceKey) // NOTE: May be removed in future versions. { - RequestInfo = RequestInfo.With(i => - { - var details = i.Details; - var newDetails = ServiceDetails.Of(details.RequiredServiceType, serviceKey, details.IfUnresolved, details.DefaultValue); - return i.Create(i.ServiceType, newDetails); - }); + var i = _serviceInfo; + var d = i.Details; + var newDetails = ServiceDetails.Of(d.RequiredServiceType, serviceKey, d.IfUnresolved, d.DefaultValue); + _serviceInfo = i.Create(i.ServiceType, newDetails); + } + + /// Adds input argument expression list to request. + /// The arguments are provided by Func and Action wrappers. + /// Argument parameter expressions. New request. + public Request WithArgs(ParameterExpression[] argExpressions) + { + var argsUsed = new bool[argExpressions.Length]; + var argsInfo = new KV(argsUsed, argExpressions); + return new Request(_requestContext, RawParent, _serviceInfo, _factory, _reuse, argsInfo, _flags); } - /// Returns new request with parameter expressions created for input arguments. - /// The expression is set to request field to use for - /// resolution. - /// Func type to get input arguments from. - /// New request with field set. + // todo: v3: remove + /// Obsolete: replaced with . public Request WithFuncArgs(Type funcType) { - var funcArgs = funcType.ThrowIf(!funcType.IsFuncWithArgs()).GetGenericParamsAndArgs(); - var funcArgExprs = new ParameterExpression[funcArgs.Length - 1]; + var openGenType = funcType.GetGenericDefinitionOrNull().ThrowIfNull(); + + var funcIndex = WrappersSupport.FuncTypes.IndexOf(openGenType); + var actionIndex = funcIndex != -1 ? -1 : WrappersSupport.ActionTypes.IndexOf(openGenType); + Throw.If(funcIndex < 1 && actionIndex < 1); - for (var i = 0; i < funcArgExprs.Length; ++i) + var argTypes = funcType.GetGenericParamsAndArgs(); + var argCount = funcIndex > 0 ? argTypes.Length - 1 : argTypes.Length; + + var argExprs = new ParameterExpression[argCount]; + for (var i = 0; i < argCount; ++i) { - var funcArg = funcArgs[i]; - var funcArgName = "_" + funcArg.Name + i; // Valid non conflicting argument names for code generation - funcArgExprs[i] = Expression.Parameter(funcArg, funcArgName); + var argType = argTypes[i]; + var argName = "_" + argType.Name + i; // Valid unique argument names for code generation + argExprs[i] = Expression.Parameter(argType, argName); } - var funcArgsUsage = new bool[funcArgExprs.Length]; - var funcArgsUsageAndExpr = new KV(funcArgsUsage, funcArgExprs); - return new Request(_resolverContext, RawParent, RequestInfo, Made, funcArgsUsageAndExpr, Level); + var argsUsed = new bool[argExprs.Length]; + var argsInfo = new KV(argsUsed, argExprs); + return new Request(_requestContext, RawParent, _serviceInfo, _factory, _reuse, argsInfo, _flags); } - /// Changes container to passed one. Could be used by child container, + /// Changes container to passed one. Could be used by child container, /// to switch child container to parent preserving the rest of request state. /// Reference to container to switch to. /// Request with replaced container. public Request WithNewContainer(ContainerWeakRef newContainer) { - var newContext = _resolverContext.With(newContainer); - return new Request(newContext, RawParent, RequestInfo, Made, FuncArgs, Level); + var newContext = _requestContext.With(newContainer.Container); + return new Request(newContext, RawParent, _serviceInfo, _factory, _reuse, FuncArgs, _flags); } /// Returns new request with set implementation details. /// Factory to which request is resolved. + /// (optional) does not check for recursive dependency. + /// Use with caution. Make sense for Resolution expression. + /// (optional) allows to skip captive dependency check. /// New request with set factory. - public Request WithResolvedFactory(Factory factory) + public Request WithResolvedFactory(Factory factory, + bool skipRecursiveDependencyCheck = false, + bool skipCaptiveDependencyCheck = false) { - var newFactoryID = factory.FactoryID; - if (IsEmpty || FactoryID == newFactoryID) + if (IsEmpty || _factory != null && _factory.FactoryID == factory.FactoryID) return this; // resolving only once, no need to check recursion again. - if (factory.FactoryType == FactoryType.Service) + if (factory.FactoryType == FactoryType.Service && !skipRecursiveDependencyCheck) for (var p = RawParent; !p.IsEmpty; p = p.RawParent) - Throw.If(p.FactoryID == newFactoryID, Error.RecursiveDependencyDetected, Print(newFactoryID)); + if (p.FactoryID == factory.FactoryID) + Throw.It(Error.RecursiveDependencyDetected, Print(factory.FactoryID)); + + var reuse = factory.Reuse; + if (reuse == null) + reuse = GetDefaultReuse(factory); + + var flags = _flags; + + if (!skipCaptiveDependencyCheck && reuse.Lifespan != 0 && + Rules.ThrowIfDependencyHasShorterReuseLifespan) + ThrowIfReuseHasShorterLifespanThanParent(reuse); - var reuse = factory.Reuse ?? - (factory.Setup.UseParentReuse - ? GetParentOrFuncOrEmpty().Reuse - : FactoryID != 0 && factory.Setup is Setup.DecoratorSetup && - ((Setup.DecoratorSetup)factory.Setup).UseDecorateeReuse - ? Reuse - : null); + if (reuse == DryIoc.Reuse.Transient) + { + reuse = GetTransientDisposableTrackingReuse(factory); + if (reuse != DryIoc.Reuse.Transient) + flags |= RequestFlags.TracksTransientDisposable; + } - var newInfo = RequestInfo.With(newFactoryID, factory.FactoryType, factory.ImplementationType, reuse); - var made = factory is ReflectionFactory ? ((ReflectionFactory)factory).Made : null; - return new Request(_resolverContext, RawParent, newInfo, made, FuncArgs, Level); + if (reuse == DryIoc.Reuse.Singleton) + flags |= RequestFlags.IsSingletonOrDependencyOfSingleton; + + _requestContext.IncrementDependencyCount(); + return new Request(_requestContext, RawParent, _serviceInfo, factory, reuse, FuncArgs, flags); } - /// Returns non-wrapper parent of Func wrapper if any. - /// (optional) When set specifies to search for first not transient parent. - /// Found parent or Func parent or empty. - public RequestInfo GetParentOrFuncOrEmpty(bool firstNonTransientParent = false) + private IReuse GetDefaultReuse(Factory factory) { - var parent = ParentOrWrapper; - if (!parent.IsEmpty) + IReuse reuse = null; + + if (factory.Setup.UseParentReuse) + reuse = GetFirstParentNonTransientReuseUntilFunc(); + + else if (factory.Setup.FactoryType == FactoryType.Decorator + && ((Setup.DecoratorSetup)factory.Setup).UseDecorateeReuse) + reuse = Reuse; // use reuse of resolved service factory for decorator + else + // if no specified the wrapper reuse is always Transient, + // other container-wide default reuse is applied + reuse = factory.FactoryType != FactoryType.Wrapper + ? Container.Rules.DefaultReuseInsteadOfTransient + : DryIoc.Reuse.Transient; + + return reuse; + } + + 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(typeof(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 reused parent, then track in current open scope, or if not opened in singleton + return Scopes.GetCurrentScope() != null ? DryIoc.Reuse.InCurrentScope : DryIoc.Reuse.Singleton; + } + + private void ThrowIfReuseHasShorterLifespanThanParent(IReuse reuse) + { + if (!RawParent.IsEmpty) + for (var p = RawParent; !p.IsEmpty; p = p.RawParent) + { + if (p.FactoryType == FactoryType.Wrapper && p.GetActualServiceType().IsFunc()) + break; + + if (p.FactoryType == FactoryType.Service && p.ReuseLifespan > reuse.Lifespan) + Throw.It(Error.DependencyHasShorterReuseLifespan, PrintCurrent(), reuse, p); + } + + if (!PreResolveParent.IsEmpty) { - foreach (var p in parent.Enumerate()) + for (var p = PreResolveParent; !p.IsEmpty; p = p.ParentOrWrapper) { - if (p.FactoryType == FactoryType.Wrapper) - { - if (p.GetActualServiceType().IsFunc()) - return p; - } - else - { - if (!firstNonTransientParent || p.Reuse != null) - return p; - } + if (p.FactoryType == FactoryType.Wrapper && p.GetActualServiceType().IsFunc()) + break; + + if (p.FactoryType == FactoryType.Service && p.ReuseLifespan > reuse.Lifespan) + Throw.It(Error.DependencyHasShorterReuseLifespan, PrintCurrent(), reuse, p); } } + } + + private IReuse GetFirstParentNonTransientReuseUntilFunc(bool firstNonTransientParent = false) + { + if (!RawParent.IsEmpty) + for (var p = RawParent; !p.IsEmpty; p = p.RawParent) + { + if (p.FactoryType == FactoryType.Wrapper && p.GetActualServiceType().IsFunc()) + return DryIoc.Reuse.Transient; + + if (p.FactoryType != FactoryType.Wrapper && p.Reuse != DryIoc.Reuse.Transient) + return p.Reuse; + } - return RequestInfo.Empty; + if (!PreResolveParent.IsEmpty) + for (var p = PreResolveParent; !p.IsEmpty; p = p.ParentOrWrapper) + { + if (p.FactoryType == FactoryType.Wrapper && p.GetActualServiceType().IsFunc()) + return DryIoc.Reuse.Transient; + + if (p.FactoryType != FactoryType.Wrapper && p.Reuse != DryIoc.Reuse.Transient) + return p.Reuse; + } + + return DryIoc.Reuse.Transient; } - /// Serializable request info stripped from run-time info. - public RequestInfo RequestInfo { get; private set; } // note: mutable to change key in place + /// Serializable request info stripped off run-time info. + public RequestInfo RequestInfo + { + get + { + if (IsEmpty) + return PreResolveParent; + + RequestInfo parentRequestInfo; + if (RawParent.IsEmpty) + parentRequestInfo = PreResolveParent; + else + parentRequestInfo = RawParent.RequestInfo; + + if (_factory == null) + return parentRequestInfo.Push(_serviceInfo); + + var f = _factory; + return parentRequestInfo.Push(_serviceInfo, + f.FactoryID, f.FactoryType, f.ImplementationType, _reuse); + } + } // todo: v3: remove /// Obsolete: use instead. - /// Mirrored request info. public RequestInfo ToRequestInfo() { return RequestInfo; } - /// If request corresponds to dependency injected into parameter, + /// If request corresponds to dependency injected into parameter, /// then method calls 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 handler. @@ -5664,7 +6357,7 @@ public RequestInfo ToRequestInfo() Func property = null, Func field = null) { - var serviceInfo = RequestInfo.ServiceInfo; + var serviceInfo = _serviceInfo; if (serviceInfo is ParameterServiceInfo) { if (parameter != null) @@ -5680,7 +6373,7 @@ public RequestInfo ToRequestInfo() return property(propertyInfo); } else if (field != null) - return field((FieldInfo)propertyOrFieldServiceInfo.Member); + return field((FieldInfo)propertyOrFieldServiceInfo.Member); } else if (root != null) return root(); @@ -5688,7 +6381,7 @@ public RequestInfo ToRequestInfo() return default(TResult); } - /// Enumerates all request stack parents. + /// Enumerates all request stack parents. /// Last returned will empty parent. /// Unfolding parents. public IEnumerable Enumerate() @@ -5703,21 +6396,42 @@ public IEnumerable Enumerate() public StringBuilder PrintCurrent(StringBuilder s = null) { s = s ?? new StringBuilder(); - s = RequestInfo.PrintCurrent(s); + + if (IsEmpty) + return s.Append("{empty}"); + + if (_factory != null) + { + if (_reuse != DryIoc.Reuse.Transient) + s.Append(Reuse is SingletonReuse ? "singleton" : "scoped").Append(' '); + + var factoryType = _factory.FactoryType; + if (factoryType != FactoryType.Service) + s.Append(factoryType.ToString().ToLower()).Append(' '); + + var implType = _factory.ImplementationType; + if (implType != null && implType != ServiceType) + s.Print(implType).Append(": "); + } + + s.Append(_serviceInfo); + if (FuncArgs != null) s.AppendFormat(" with {0} arg(s) ", FuncArgs.Key.Count(k => k == false)); + return s; } /// Prints full stack of requests starting from current one using . - /// Flag specifying that in case of found recursion/repetition of requests, + /// Flag specifying that in case of found recursion/repetition of requests, /// mark repeated requests. /// Builder with appended request stack info. public StringBuilder Print(int recursiveFactoryID = -1) { - var s = PrintCurrent(new StringBuilder()); if (IsEmpty) - return s; + return new StringBuilder(""); + + var s = PrintCurrent(new StringBuilder()); s = recursiveFactoryID == -1 ? s : s.Append(" <--recursive"); foreach (var r in RawParent.Enumerate()) @@ -5742,50 +6456,54 @@ public override string ToString() #region Implementation - internal Request(ResolverContext resolverContext, - Request parent, RequestInfo requestInfo, Made made, KV funcArgs, - int level = 0) + private Request(RequestContext requestContext, Request parent, IServiceInfo serviceInfo, + Factory factory, IReuse reuse, + KV funcArgs, RequestFlags flags) { - _resolverContext = resolverContext; + _requestContext = requestContext; RawParent = parent; - RequestInfo = requestInfo; - Made = made; + _serviceInfo = serviceInfo; + _factory = factory; + _reuse = reuse; FuncArgs = funcArgs; - Level = level; + _flags = flags; } - private readonly ResolverContext _resolverContext; + private IServiceInfo _serviceInfo; + private readonly Factory _factory; + private readonly IReuse _reuse; + + private readonly RequestFlags _flags; - internal sealed class ResolverContext + private readonly RequestContext _requestContext; + + internal sealed class RequestContext { - public readonly ContainerWeakRef ContainerWeakRef; - public readonly ContainerWeakRef ScopesWeakRef; + public readonly IContainer Container; + public readonly IScopeAccess Scopes; public readonly IScope Scope; public readonly RequestInfo PreResolveParent; - public ResolverContext(ContainerWeakRef container, ContainerWeakRef scopes, IScope scope, RequestInfo preResolveParent) + // Mutable updatable part + public bool ContainsNestedResolutionCall; + public int DependencyCount; + + public RequestContext(IContainer container, IScopeAccess scopes, IScope scope, RequestInfo preResolveParent) { - ContainerWeakRef = container; - ScopesWeakRef = scopes; + Container = container; + Scopes = scopes; Scope = scope; PreResolveParent = preResolveParent; } - public ResolverContext With(ContainerWeakRef newContainer) + public RequestContext With(IContainer newContainer) { - return new ResolverContext(newContainer, ScopesWeakRef, Scope, PreResolveParent); + return new RequestContext(newContainer, Scopes, Scope, PreResolveParent); } - public ResolverContext With(IScope scope) + public void IncrementDependencyCount() { - return scope == null ? this - : new ResolverContext(ContainerWeakRef, ScopesWeakRef, scope, PreResolveParent); - } - - public ResolverContext With(RequestInfo preResolveParent) - { - return preResolveParent == null || preResolveParent.IsEmpty ? this - : new ResolverContext(ContainerWeakRef, ScopesWeakRef, Scope, preResolveParent); + Interlocked.Increment(ref DependencyCount); } } @@ -5806,7 +6524,7 @@ public enum FactoryType /// Base class to store optional settings. public abstract class Setup { - /// Factory type is required to be specified by concrete setups as in + /// Factory type is required to be specified by concrete setups as in /// , , . public abstract FactoryType FactoryType { get; } @@ -5816,14 +6534,14 @@ public abstract class Setup /// Arbitrary metadata object associated with Factory/Implementation. public virtual object Metadata { get { return null; } } - /// Indicates that injected expression should be: + /// Indicates that injected expression should be: /// (...)]]> /// instead of: public bool AsResolutionCall { get { return (_settings & Settings.AsResolutionCall) != 0; } } /// Marks service (not a wrapper or decorator) registration that is expected to be resolved via Resolve call. public bool AsResolutionRoot { get { return (_settings & Settings.AsResolutionRoot) != 0; } } - + /// In addition to opens scope. public bool OpenResolutionScope { get { return (_settings & Settings.OpenResolutionScope) != 0; } } @@ -5860,16 +6578,20 @@ public bool MatchesMetadata(string metadataKey, object metadata) /// Default setup for service factories. public static readonly Setup Default = new ServiceSetup(); - /// Sets the basic settings. + /// Sets the base settings. + /// /// /// /// /// - private Setup(bool openResolutionScope = false, bool asResolutionCall = false, + private Setup(Func 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) { + Condition = condition; + if (asResolutionCall) _settings |= Settings.AsResolutionCall; if (openResolutionScope) @@ -5910,7 +6632,8 @@ private enum Settings private readonly Settings _settings; /// Constructs setup object out of specified settings. If all settings are default then setup will be returned. - /// (optional) Metadata object or Func returning metadata object. (optional) + /// (optional) Metadata object or Func returning metadata object. + /// (optional) /// (optional) Same as but in addition opens new scope. /// (optional) If true dependency expression will be "r.Resolve(...)" instead of inline expression. /// (optional) Marks service (not a wrapper or decorator) registration that is expected to be resolved via Resolve call. @@ -5944,17 +6667,24 @@ private enum Settings public static readonly Setup Wrapper = new WrapperSetup(); /// Returns generic wrapper setup. - /// Default is -1 for generic wrapper with single type argument. Need to be set for multiple type arguments. + /// Default is -1 for generic wrapper with single type argument. Need to be set for multiple type arguments. /// Need to be set when generic wrapper type arguments should be ignored. + /// (optional) Delegate returning wrapped type from wrapper type. Overwrites other options. /// (optional) Opens the new scope. + /// (optional) Injects decorator as resolution call. /// (optional) Prevents disposal of reused instance if it is disposable. + /// (optional) /// New setup or default . - public static Setup WrapperWith(int wrappedServiceTypeArgIndex = -1, bool alwaysWrapsRequiredServiceType = false, - bool openResolutionScope = false, bool preventDisposal = false) + public static Setup WrapperWith(int wrappedServiceTypeArgIndex = -1, + bool alwaysWrapsRequiredServiceType = false, Func unwrap = null, + bool openResolutionScope = false, bool asResolutionCall = false, bool preventDisposal = false, + Func condition = null) { - return wrappedServiceTypeArgIndex == -1 && !alwaysWrapsRequiredServiceType - && !openResolutionScope && !preventDisposal ? Wrapper - : new WrapperSetup(wrappedServiceTypeArgIndex, alwaysWrapsRequiredServiceType, openResolutionScope, preventDisposal); + return wrappedServiceTypeArgIndex == -1 && !alwaysWrapsRequiredServiceType && unwrap == null + && !openResolutionScope && !preventDisposal && condition == null + ? Wrapper + : new WrapperSetup(wrappedServiceTypeArgIndex, alwaysWrapsRequiredServiceType, unwrap, + condition, openResolutionScope, asResolutionCall, preventDisposal); } /// Default decorator setup: decorator is applied to service type it registered with. @@ -5965,13 +6695,13 @@ private enum Settings /// (optional) If provided specifies relative decorator position in decorators chain. /// 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 + /// Decorators without order (Order is 0) or with equal order are applied in registration order /// - first registered are closer decoratee. /// New setup with condition or . - public static Setup DecoratorWith(Func condition = null, int order = 0, + public static Setup DecoratorWith(Func condition = null, int order = 0, bool useDecorateeReuse = false) { - return condition == null && order == 0 && !useDecorateeReuse ? Decorator + return condition == null && order == 0 && !useDecorateeReuse ? Decorator : new DecoratorSetup(condition, order, useDecorateeReuse); } @@ -5992,15 +6722,17 @@ public override object Metadata } } - public ServiceSetup(Func 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) : base(openResolutionScope, asResolutionCall, asResolutionRoot, + public ServiceSetup() { } + + public ServiceSetup(Func condition, object metadataOrFuncOfMetadata, + bool openResolutionScope, bool asResolutionCall, bool asResolutionRoot, + bool preventDisposal, bool weaklyReferenced, + bool allowDisposableTransient, bool trackDisposableTransient, + bool useParentReuse) + : base(condition, openResolutionScope, asResolutionCall, asResolutionRoot, preventDisposal, weaklyReferenced, allowDisposableTransient, trackDisposableTransient, useParentReuse) { - Condition = condition; _metadataOrFuncOfMetadata = metadataOrFuncOfMetadata; } @@ -6008,36 +6740,52 @@ public override object Metadata } /// Setup for factory. - public sealed class WrapperSetup : Setup + internal sealed class WrapperSetup : Setup { /// Returns type. public override FactoryType FactoryType { get { return FactoryType.Wrapper; } } - /// Delegate to get wrapped type from provided wrapper type. + /// Delegate to get wrapped type from provided wrapper type. /// If wrapper is generic, then wrapped type is usually a generic parameter. public readonly int WrappedServiceTypeArgIndex; /// Per name. public readonly bool AlwaysWrapsRequiredServiceType; + /// Delegate returning wrapped type from wrapper type. Overwrites other options. + public readonly Func Unwrap; + + /// Default setup + /// Default is -1 for generic wrapper with single type argument. + /// Need to be set for multiple type arguments. + public WrapperSetup(int wrappedServiceTypeArgIndex = -1) + { + WrappedServiceTypeArgIndex = wrappedServiceTypeArgIndex; + } + /// Constructs wrapper setup from optional wrapped type selector and reuse wrapper factory. - /// Default is -1 for generic wrapper with single type argument. Need to be set for multiple type arguments. + /// Default is -1 for generic wrapper with single type argument. Need to be set for multiple type arguments. /// Need to be set when generic wrapper type arguments should be ignored. - /// (optional) Opens the new scope. - /// (optional) Prevents disposal of reused instance if it is disposable. - public WrapperSetup(int wrappedServiceTypeArgIndex = -1, bool alwaysWrapsRequiredServiceType = false, - bool openResolutionScope = false, bool asResolutionCall = false, bool preventDisposal = false) - : base(openResolutionScope: openResolutionScope, asResolutionCall: asResolutionCall, - preventDisposal: preventDisposal) + /// Delegate returning wrapped type from wrapper type. Overwrites other options. + /// Opens the new scope. + /// Prevents disposal of reused instance if it is disposable. + /// Predicate to check if factory could be used for resolved request. + public WrapperSetup(int wrappedServiceTypeArgIndex, bool alwaysWrapsRequiredServiceType, Func unwrap, + Func condition, bool openResolutionScope, bool asResolutionCall, bool preventDisposal) + : base(condition, openResolutionScope: openResolutionScope, asResolutionCall: asResolutionCall, preventDisposal: preventDisposal) { WrappedServiceTypeArgIndex = wrappedServiceTypeArgIndex; AlwaysWrapsRequiredServiceType = alwaysWrapsRequiredServiceType; + Unwrap = unwrap; } /// Unwraps service type or returns its. /// Wrapped type or self. public Type GetWrappedTypeOrNullIfWrapsRequired(Type serviceType) { + if (Unwrap != null) + return Unwrap(serviceType); + if (AlwaysWrapsRequiredServiceType || !serviceType.IsGeneric()) return null; @@ -6055,38 +6803,41 @@ public Type GetWrappedTypeOrNullIfWrapsRequired(Type serviceType) } /// Setup applied to decorators. - public sealed class DecoratorSetup : Setup + internal sealed class DecoratorSetup : Setup { /// Returns Decorator factory type. public override FactoryType FactoryType { get { return FactoryType.Decorator; } } /// 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 + /// Decorators without order (Order is 0) or with equal order are applied in registration order /// - first registered are closer decoratee. public readonly int Order; /// Instructs to use decorated service reuse. Decorated service may be decorator itself. public readonly bool UseDecorateeReuse; + /// Default setup. + public DecoratorSetup() { } + /// Creates decorator setup with optional condition. /// (optional) Applied to decorated service to find that service is the decorator target. /// (optional) 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 + /// Decorators without order (Order is 0) or with equal order are applied in registration order /// - first registered are closer decoratee. /// (optional) Instructs to use decorated service reuse. /// Decorated service may be decorator itself. - public DecoratorSetup(Func condition = null, int order = 0, bool useDecorateeReuse = false) + public DecoratorSetup(Func condition, int order, bool useDecorateeReuse) + : base(condition) { - Condition = condition; Order = order; UseDecorateeReuse = useDecorateeReuse; } } } - /// Facility for creating concrete factories from some template/prototype. Example: + /// Facility for creating concrete factories from some template/prototype. Example: /// creating closed-generic type reflection factory from registered open-generic prototype factory. public interface IConcreteFactoryGenerator { @@ -6094,11 +6845,14 @@ public interface IConcreteFactoryGenerator ImTreeMap, ReflectionFactory> GeneratedFactories { get; } /// Returns factory per request. May track already generated factories and return one without regenerating. - /// Request to resolve. Returns new factory per request. - Factory GetGeneratedFactoryOrDefault(Request request); + /// Request to resolve. + /// If set to true - returns null if unable to generate, + /// otherwise error result depends on . + /// Returns new factory per request. + Factory GetGeneratedFactory(Request request, bool ifErrorReturnDefault = false); } - /// Base class for different ways to instantiate service: + /// Base class for different ways to instantiate service: /// /// Through reflection - /// Using custom delegate - @@ -6109,20 +6863,26 @@ public interface IConcreteFactoryGenerator /// Each created factory has an unique ID set in . public abstract class Factory { + /// Get next factory ID in a atomic way.The ID. + public static int GetNextID() + { + return Interlocked.Increment(ref _lastFactoryID); + } + /// Unique factory id generated from static seed. public int FactoryID { get; internal set; } - /// Reuse policy for factory created services. - public readonly IReuse Reuse; + /// Reuse policy for created services. + public virtual IReuse Reuse { get { return _reuse; } } /// Setup may contain different/non-default factory settings. - public Setup Setup + public virtual Setup Setup { get { return _setup; } protected internal set { _setup = value ?? Setup.Default; } } - /// Checks that condition is met for request or there is no condition setup. + /// Checks that condition is met for request or there is no condition setup. /// Additionally check for reuse scope availability. /// Request to check against. /// True if condition met or no condition setup. @@ -6141,41 +6901,30 @@ public FactoryType FactoryType /// Non-abstract closed implementation type. May be null if not known beforehand, e.g. in . public virtual Type ImplementationType { get { return null; } } - /// Indicates that Factory is factory provider and - /// consumer should call to get concrete factory. + /// Indicates that Factory is factory provider and + /// consumer should call to get concrete factory. public virtual IConcreteFactoryGenerator FactoryGenerator { get { return null; } } - /// Get next factory ID in a atomic way.The ID. - public static int GetNextID() - { - return Interlocked.Increment(ref _lastFactoryID); - } + /// Settings (if any) to select Constructor/FactoryMethod, Parameters, Properties and Fields. + public virtual Made Made { get { return Made.Default; } } /// Initializes reuse and setup. Sets the - /// (optional) - /// (optional) + /// (optional) (optional) protected Factory(IReuse reuse = null, Setup setup = null) { FactoryID = GetNextID(); - Reuse = reuse; - Setup = setup ?? Setup.Default; + _reuse = reuse; + _setup = setup ?? Setup.Default; } /// Returns true if for factory Reuse exists matching resolution or current Scope. /// True if matching Scope exists. public bool HasMatchingReuseScope(Request request) { - if (Reuse == null || !request.Container.Rules.ImplicitCheckForReuseMatchingScope) + if (!request.Rules.ImplicitCheckForReuseMatchingScope) return true; - - if (Reuse is ResolutionScopeReuse) - return Reuse.GetScopeOrDefault(request) != null; - - if (Reuse is CurrentScopeReuse) - return request.IsWrappedInFunc() - || Reuse.GetScopeOrDefault(request) != null; - - return true; + var reuse = Reuse as IReuseV3; + return reuse == null || reuse.CanApply(request); } /// The main factory method to create service expression, e.g. "new Client(new Service())". @@ -6189,18 +6938,31 @@ public bool HasMatchingReuseScope(Request request) /// Context. True if factory expression could be cached. protected virtual bool IsFactoryExpressionCacheable(Request request) { - return request.FuncArgs == null - && Setup.FactoryType == FactoryType.Service - // the settings below specify context based resolution so that expression will be different on - // different resolution paths, which prevents its caching and reuse. - && Setup.Condition == null - && !Setup.AsResolutionCall - && !Setup.UseParentReuse - && !(request.Reuse is ResolutionScopeReuse) - // tracking disposable transient - && !(request.Reuse == null - && (Setup.TrackDisposableTransient || request.Container.Rules.TrackingDisposableTransients) - && IsDisposableService(request)); + return Setup.FactoryType == FactoryType.Service + && !request.TracksTransientDisposable + && request.FuncArgs == null + && !Setup.AsResolutionCall + && !request.IsResolutionRoot + && Setup.Condition == null && + !IsScopeDependent(request); + } + + private bool IsScopeDependent(Request request) + { + return Setup.UseParentReuse + || request.Reuse is ResolutionScopeReuse + || (request.Reuse is CurrentScopeReuse && ((CurrentScopeReuse)request.Reuse).Name != null); + } + + private bool ShouldBeInjectedAsResolutionCall(Request request) + { + return !request.IsResolutionCall && // prevents recursion on already split graph + // explicit aka user requested split + (Setup.AsResolutionCall || + // implicit split only when not inside func with args, cause until v3 args are not propagated through resolve call. + (request.ShouldSplitObjectGraph() || IsScopeDependent(request)) && + !request.IsWrappedInFuncWithArgs()) && + request.GetActualServiceType() != typeof(void); } /// Returns service expression: either by creating it with or taking expression from cache. @@ -6210,93 +6972,82 @@ public virtual Expression GetExpressionOrDefault(Request request) { request = request.WithResolvedFactory(this); - var container = request.Container; - - // Returns "r.Resolver.Resolve(...)" instead of "new Dependency()". - if (Setup.AsResolutionCall && !request.IsFirstNonWrapperInResolutionCall() - || request.Level >= container.Rules.LevelToSplitObjectGraphIntoResolveCalls - // note: Split only if not wrapped in Func with args - Propagation of args across Resolve boundaries is not supported at the moment - && !request.IsWrappedInFuncWithArgs()) + if (ShouldBeInjectedAsResolutionCall(request)) return Resolver.CreateResolutionExpression(request, Setup.OpenResolutionScope); - // Here's lookup for decorators - var decoratorExpr = FactoryType != FactoryType.Decorator - ? container.GetDecoratorExpressionOrDefault(request) - : null; + var container = request.Container; - var isDecorated = decoratorExpr != null; - var cacheable = IsFactoryExpressionCacheable(request) && !isDecorated; - if (cacheable) + // First look for decorators + if (FactoryType != FactoryType.Decorator) { - var cachedServiceExpr = container.GetCachedFactoryExpressionOrDefault(FactoryID); - if (cachedServiceExpr != null) - return cachedServiceExpr; + var decoratorExpr = container.GetDecoratorExpressionOrDefault(request); + if (decoratorExpr != null) + return decoratorExpr; } - var serviceExpr = decoratorExpr ?? CreateExpressionOrDefault(request); - if (serviceExpr != null && !isDecorated) + // Then optimize for already resolved singleton object, otherwise goes normal ApplyReuse route + if (request.Rules.EagerCachingSingletonForFasterAccess && + request.Reuse is SingletonReuse && + !Setup.PreventDisposal && !Setup.WeaklyReferenced) { - // Getting reuse from Request to take useParentReuse or useDecorateeReuse into account - var reuse = request.Reuse; - - // Track transient disposable in parent scope (if any), or open scope (if any) - var tracksTransientDisposable = false; - if (reuse == null && !Setup.PreventDisposal && - (Setup.TrackDisposableTransient || - !Setup.AllowDisposableTransient && container.Rules.TrackingDisposableTransients) && - IsDisposableService(request)) + var singletons = (SingletonScope)request.SingletonScope; + var singletonID = singletons.IndexOf(FactoryID); + if (singletonID > 0) { - reuse = GetTransientDisposableTrackingReuse(request); - tracksTransientDisposable = true; + var value = singletons.GetOrDefault(singletonID); + if (value != null) + return Expression.Constant(value, request.ServiceType); } + } - // As a last resort, apply per-container default reuse - if (reuse == null) - reuse = container.Rules.DefaultReuseInsteadOfTransient; - - ThrowIfReuseHasShorterLifespanThanParent(reuse, request); - - if (reuse != null) - serviceExpr = ApplyReuse(serviceExpr, reuse, tracksTransientDisposable, request); + // Then check the expression cache + var isCacheable = IsFactoryExpressionCacheable(request); + if (isCacheable) + { + var cachedExpr = container.GetCachedFactoryExpressionOrDefault(FactoryID); + if (cachedExpr != null) + return cachedExpr; } - if (cacheable && serviceExpr != null) - container.CacheFactoryExpression(FactoryID, serviceExpr); + // Then create new expression + var serviceExpr = CreateExpressionOrDefault(request); + if (serviceExpr != null) + { + // can be checked only after expression is created + if (request.ContainsNestedResolutionCall) + isCacheable = false; - if (serviceExpr == null && request.IfUnresolved == IfUnresolved.Throw) - Container.ThrowUnableToResolve(request); + if (request.Reuse != DryIoc.Reuse.Transient && + request.GetActualServiceType() != typeof(void)) + { + var originalServiceExprType = serviceExpr.Type; - return serviceExpr; - } + serviceExpr = ApplyReuse(serviceExpr, request.Reuse, request.TracksTransientDisposable, request); - private static IReuse GetTransientDisposableTrackingReuse(Request request) - { - // Track in parent's scope - var parent = request.GetParentOrFuncOrEmpty(firstNonTransientParent: true); - if (parent.FactoryType == FactoryType.Wrapper) - return null; + if (serviceExpr.NodeType == ExpressionType.Constant) + isCacheable = false; - // If no reused parent, then track in current open scope if any - // NOTE: No tracking in singleton scope cause it is most likely a memory leak. - var reuse = parent.Reuse; - if (reuse == null) + if (serviceExpr.Type != originalServiceExprType) + serviceExpr = Expression.Convert(serviceExpr, originalServiceExprType); + } + + if (isCacheable) + { + container.CacheFactoryExpression(FactoryID, serviceExpr); + } + } + // Otherwise throw + else if (request.IfUnresolved == IfUnresolved.Throw) { - var currentScope = request.Scopes.GetCurrentScope(); - if (currentScope != null) - reuse = DryIoc.Reuse.InCurrentScope; + Container.ThrowUnableToResolve(request); } - return reuse; - } - - private bool IsDisposableService(Request request) - { - return request.GetActualServiceType().IsAssignableTo(typeof(IDisposable)) - || ImplementationType.IsAssignableTo(typeof(IDisposable)); + return serviceExpr; } - /// Applies reuse to created expression. - /// Actually wraps passed expression in scoped access and produces another expression. + // todo: remove trackTransientDisposable param as it is available from Request param. + /// Applies reuse to created expression. Actually wraps passed expression in scoped access + /// and produces another expression. /// Raw service creation (or receiving) expression. /// Reuse - may be different from if set . /// Specifies that reuse is to track transient disposable. @@ -6304,36 +7055,42 @@ private bool IsDisposableService(Request request) /// Scoped expression or originally passed expression. protected virtual Expression ApplyReuse(Expression serviceExpr, IReuse reuse, bool tracksTransientDisposable, Request request) { + // optimization for already activated singleton + if (serviceExpr.NodeType == ExpressionType.Constant && + reuse is SingletonReuse && request.Rules.EagerCachingSingletonForFasterAccess && + !Setup.PreventDisposal && !Setup.WeaklyReferenced) + return serviceExpr; + var serviceType = serviceExpr.Type; - // The singleton optimization: eagerly create singleton during the construction of object graph. + // Optimize: eagerly create singleton during the construction of object graph, + // but only for root singleton and not for singleton dependency inside singleton, because of double compilation work if (reuse is SingletonReuse && - !tracksTransientDisposable && + request.Rules.EagerCachingSingletonForFasterAccess && + // except: For decorators and wrappers, when tracking tansient disposable and for lazy consumption in Func FactoryType == FactoryType.Service && - !request.IsWrappedInFunc() && - request.Container.Rules.EagerCachingSingletonForFasterAccess) + !tracksTransientDisposable && + !request.IsWrappedInFunc()) { - var factoryDelegate = serviceExpr.CompileToDelegate(request.Container); - + var factoryDelegate = serviceExpr.CompileToDelegate(); if (Setup.PreventDisposal) { var factory = factoryDelegate; - factoryDelegate = (st, cs, rs) => new[] { factory(st, cs, rs) }; + factoryDelegate = (_, cs, rs) => new[] {factory(null, cs, rs)}; } if (Setup.WeaklyReferenced) { var factory = factoryDelegate; - factoryDelegate = (st, cs, rs) => new WeakReference(factory(st, cs, rs)); + factoryDelegate = (_, cs, rs) => new WeakReference(factory(null, cs, rs)); } - var singletonScope = request.Scopes.SingletonScope; - + var singletonScope = request.SingletonScope; var singletonId = singletonScope.GetScopedItemIdOrSelf(FactoryID); - singletonScope.GetOrAdd(singletonId, () => - factoryDelegate(request.Container.ResolutionStateCache, request.ContainerWeakRef, request.Scope)); + var singleton = singletonScope.GetOrAdd(singletonId, () => + factoryDelegate(null, request.ContainerWeakRef, request.Scope)); - serviceExpr = Container.GetStateItemExpression(singletonId); + serviceExpr = Expression.Constant(singleton); } else { @@ -6343,13 +7100,21 @@ protected virtual Expression ApplyReuse(Expression serviceExpr, IReuse reuse, bo if (Setup.WeaklyReferenced) serviceExpr = Expression.New(typeof(WeakReference).GetConstructorOrNull(args: typeof(object)), serviceExpr); - var scopeExpr = reuse.GetScopeExpression(request); + var reuseV3 = reuse as IReuseV3; + if (reuseV3 != null) + { + serviceExpr = reuseV3.Apply(request, tracksTransientDisposable, serviceExpr); + } + else + { + var scopeExpr = reuse.GetScopeExpression(request); - // For transient disposable we does not care to bind to specific ID, because it should be created each time. - var scopedId = tracksTransientDisposable ? -1 : reuse.GetScopedItemIdOrSelf(FactoryID, request); - serviceExpr = Expression.Call(scopeExpr, "GetOrAdd", ArrayTools.Empty(), - Expression.Constant(scopedId), - Expression.Lambda(serviceExpr, ArrayTools.Empty())); + // For transient disposable we don't care to bind to specific ID, because it should be created each time. + var scopedId = tracksTransientDisposable ? -1 : reuse.GetScopedItemIdOrSelf(FactoryID, request); + serviceExpr = Expression.Call(scopeExpr, "GetOrAdd", ArrayTools.Empty(), + Expression.Constant(scopedId), + Expression.Lambda(serviceExpr, ArrayTools.Empty())); + } } // Unwrap WeakReference and/or array preventing disposal @@ -6364,31 +7129,7 @@ protected virtual Expression ApplyReuse(Expression serviceExpr, IReuse reuse, bo Expression.Convert(serviceExpr, typeof(object[])), Expression.Constant(0, typeof(int))); - return Expression.Convert(serviceExpr, serviceType); - } - - /// Throws if request direct or further ancestor has longer reuse lifespan, - /// and throws if that is true. Until there is a Func wrapper in between. - /// Reuse to check. Request to resolve. - protected static void ThrowIfReuseHasShorterLifespanThanParent(IReuse reuse, Request request) - { - // Fast check: if reuse is not applied or the rule set then skip the check. - if (reuse == null || reuse.Lifespan == 0 || - !request.Container.Rules.ThrowIfDependencyHasShorterReuseLifespan) - return; - - var parent = request.ParentOrWrapper; - if (parent.IsEmpty) - return; - - var parentWithLongerLifespan = parent.Enumerate() - .TakeWhile(r => r.FactoryType != FactoryType.Wrapper || !r.GetActualServiceType().IsFunc()) - .FirstOrDefault(r => r.FactoryType == FactoryType.Service - && r.ReuseLifespan > reuse.Lifespan); - - if (parentWithLongerLifespan != null) - Throw.It(Error.DependencyHasShorterReuseLifespan, - request.PrintCurrent(), reuse, parentWithLongerLifespan); + return serviceExpr; } /// Creates factory delegate from service expression and returns it. @@ -6398,7 +7139,7 @@ protected static void ThrowIfReuseHasShorterLifespanThanParent(IReuse reuse, Req public virtual FactoryDelegate GetDelegateOrDefault(Request request) { var expression = GetExpressionOrDefault(request); - return expression == null ? null : expression.CompileToDelegate(request.Container); + return expression == null ? null : expression.CompileToDelegate(); } /// Returns nice string representation of factory. @@ -6416,14 +7157,19 @@ public override string ToString() 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(", AsResolutionScope"); + return s.Append("}").ToString(); } #region Implementation private static int _lastFactoryID; + private IReuse _reuse; private Setup _setup; #endregion @@ -6452,7 +7198,7 @@ public static class Parameters public static ParameterSelector Of = request => ParameterServiceInfo.Of; /// Returns service info which considers each parameter as optional. - public static ParameterSelector IfUnresolvedReturnDefault = + public static ParameterSelector IfUnresolvedReturnDefault = request => pi => ParameterServiceInfo.Of(pi).WithDetails(ServiceDetails.IfUnresolvedReturnDefault, request); /// Combines source selector with other. Other is used as fallback when source returns null. @@ -6475,7 +7221,7 @@ public static ParameterSelector And(this ParameterSelector source, ParameterSele } /// Overrides source parameter rules with specific parameter details. If it is not your parameter just return null. - /// Original parameters rules + /// Original parameters rules /// Should return specific details or null. /// New parameters rules. public static ParameterSelector Details(this ParameterSelector source, Func getDetailsOrNull) @@ -6497,7 +7243,7 @@ public static ParameterSelector Details(this ParameterSelector source, Func(optional) Required metadata key Required metadata or value. /// New parameters rules. public static ParameterSelector Name(this ParameterSelector source, string name, - Type requiredServiceType = null, object serviceKey = null, + Type requiredServiceType = null, object serviceKey = null, IfUnresolved ifUnresolved = IfUnresolved.Throw, object defaultValue = null, string metadataKey = null, object metadata = null) { @@ -6515,14 +7261,14 @@ public static ParameterSelector Name(this ParameterSelector source, string name, } /// Adds to selector service info for parameter identified by type . - /// Type of parameter. Source selector. + /// Type of parameter. Source selector. /// (optional) (optional) /// (optional) By default throws exception if unresolved. /// (optional) Specifies default value to use when unresolved. /// (optional) Required metadata key Required metadata or value. /// Combined selector. public static ParameterSelector Type(this ParameterSelector source, - Type requiredServiceType = null, object serviceKey = null, + Type requiredServiceType = null, object serviceKey = null, IfUnresolved ifUnresolved = IfUnresolved.Throw, object defaultValue = null, string metadataKey = null, object metadata = null) { @@ -6532,7 +7278,7 @@ public static ParameterSelector Name(this ParameterSelector source, string name, /// Specify parameter by type and set custom value to it. /// Parameter type. - /// Original parameters rules. + /// Original parameters rules. /// Custom value provider. /// New parameters rules. public static ParameterSelector Type(this ParameterSelector source, Func getCustomValue) @@ -6573,11 +7319,11 @@ public static partial class PropertiesAndFields PropertyOrFieldServiceInfo.Of(m).WithDetails(ServiceDetails.Of(ifUnresolved: ifUnresolved), r); return r => { - var properties = r.ImplementationType.GetDeclaredAndBase(_ => _.DeclaredProperties) + var properties = r.ImplementationType.GetMembers(_ => _.DeclaredProperties, includeBase: true) .Where(p => p.IsInjectable(withNonPublic, withPrimitive)) .Select(m => getInfo(m, r)); return !withFields ? properties : - properties.Concat(r.ImplementationType.GetDeclaredAndBase(_ => _.DeclaredFields) + properties.Concat(r.ImplementationType.GetMembers(_ => _.DeclaredFields, includeBase: true) .Where(f => f.IsInjectable(withNonPublic, withPrimitive)) .Select(m => getInfo(m, r))); }; @@ -6657,10 +7403,9 @@ public static PropertiesAndFieldsSelector Details(this PropertiesAndFieldsSelect /// (optional) By default returns default value if unresolved. /// (optional) Specifies default value to use when unresolved. /// (optional) Required metadata key Required metadata or value. - /// /// Combined selector. public static PropertiesAndFieldsSelector Name(this PropertiesAndFieldsSelector source, string name, - Type requiredServiceType = null, object serviceKey = null, + Type requiredServiceType = null, object serviceKey = null, IfUnresolved ifUnresolved = IfUnresolved.ReturnDefault, object defaultValue = null, string metadataKey = null, object metadata = null) { @@ -6707,13 +7452,30 @@ public static bool IsInjectable(this FieldInfo field, bool withNonPublic = false public sealed class ReflectionFactory : Factory { /// Non-abstract service implementation type. May be open generic. - public override Type ImplementationType { get { return _implementationType; } } + public override Type ImplementationType + { + get + { + if (_implementationType == null && _implementationTypeProvider != null) + SetKnownImplementationType(_implementationTypeProvider(), Made); + return _implementationType; + } + } /// Provides closed-generic factory for registered open-generic variant. public override IConcreteFactoryGenerator FactoryGenerator { get { return _factoryGenerator; } } /// Injection rules set for Constructor/FactoryMethod, Parameters, Properties and Fields. - public readonly Made Made; + public override Made Made { get { return _made; } } + + /// Sets single ctor in case there are no special rules or factory method. To don;t do this twice. + /// + public void SetKnownSingleCtor(ConstructorInfo ctor) + { + _knownSingleCtor = ctor; + } + + private ConstructorInfo _knownSingleCtor; /// Creates factory providing implementation type, optional reuse and setup. /// (optional) Optional if Made.FactoryMethod is present Non-abstract close or open generic type. @@ -6721,21 +7483,25 @@ public sealed class ReflectionFactory : Factory public ReflectionFactory(Type implementationType = null, IReuse reuse = null, Made made = null, Setup setup = null) : base(reuse, setup) { - Made = made ?? Made.Default; - _implementationType = GetValidImplementationTypeOrDefault(implementationType); + _made = made ?? Made.Default; + SetKnownImplementationType(implementationType, _made); + } - var originalImplementationType = _implementationType ?? implementationType; - if (originalImplementationType == typeof(object) || // for open-generic T implementation - originalImplementationType != null && ( // for open-generic X implementation - originalImplementationType.IsGenericDefinition() || - originalImplementationType.IsGenericParameter)) - _factoryGenerator = new ClosedGenericFactoryGenerator(this); + /// Creates factory providing implementation type, optional reuse and setup. + /// Provider of non-abstract close or open generic type. + /// (optional) (optional) (optional) + public ReflectionFactory(Func implementationTypeProvider, IReuse reuse = null, Made made = null, Setup setup = null) + : base(reuse, setup) + { + _made = made ?? Made.Default; + _implementationTypeProvider = implementationTypeProvider.ThrowIfNull(); } /// Add to base rules: do not cache if Made is context based. /// Context. True if factory expression could be cached. protected override bool IsFactoryExpressionCacheable(Request request) { + // todo: review Made and may be move to IsContextDependent return base.IsFactoryExpressionCacheable(request) && (Made == Made.Default // Property injection. @@ -6749,7 +7515,7 @@ protected override bool IsFactoryExpressionCacheable(Request request) || (Made.FactoryMethodKnownResultType != null && !Made.HasCustomDependencyValue)); } - /// Creates service expression, so for registered implementation type "Service", + /// Creates service expression, so for registered implementation type "Service", /// you will get "new Service()". If there is specified, then expression will /// contain call to returned by reuse. /// Request for service to resolve. Created expression. @@ -6757,32 +7523,37 @@ public override Expression CreateExpressionOrDefault(Request request) { var factoryMethod = GetFactoryMethod(request); + var container = request.Container; + // If factory method is instance method, then resolve factory instance first. Expression factoryExpr = null; if (factoryMethod.FactoryServiceInfo != null) { var factoryRequest = request.Push(factoryMethod.FactoryServiceInfo); - var factoryFactory = factoryRequest.Container.ResolveFactory(factoryRequest); - factoryExpr = factoryFactory == null ? null : factoryFactory.GetExpressionOrDefault(factoryRequest); + var factoryFactory = container.ResolveFactory(factoryRequest); + if (factoryFactory == null) + return null; + factoryExpr = factoryFactory.GetExpressionOrDefault(factoryRequest); if (factoryExpr == null) return null; } - var containerRules = request.Container.Rules; + var containerRules = container.Rules; + var allParamsAreConstants = true; Expression[] paramExprs = null; - var constructorOrMethod = factoryMethod.ConstructorOrMethodOrMember as MethodBase; - if (constructorOrMethod != null) + var ctorOrMethod = factoryMethod.ConstructorOrMethodOrMember as MethodBase; + if (ctorOrMethod != null) { - var parameters = constructorOrMethod.GetParameters(); + var parameters = ctorOrMethod.GetParameters(); if (parameters.Length != 0) { paramExprs = new Expression[parameters.Length]; - var selector = + var selector = containerRules.OverrideRegistrationMade - ? containerRules.Parameters.OverrideWith(Made.Parameters) - : Made.Parameters.OverrideWith(containerRules.Parameters); + ? Made.Parameters.OverrideWith(containerRules.Parameters) + : containerRules.Parameters.OverrideWith(Made.Parameters); var parameterSelector = selector(request); @@ -6820,42 +7591,49 @@ public override Expression CreateExpressionOrDefault(Request request) var customValue = paramInfo.Details.CustomValue; if (customValue != null) customValue.ThrowIfNotOf(paramRequest.ServiceType, Error.InjectedCustomValueIsOfDifferentType, paramRequest); - paramExpr = paramRequest.Container - .GetOrAddStateItemExpression(customValue, paramRequest.ServiceType); + paramExpr = container.GetOrAddStateItemExpression(customValue, paramRequest.ServiceType); } else { - var paramFactory = paramRequest.Container.ResolveFactory(paramRequest); + var paramFactory = container.ResolveFactory(paramRequest); paramExpr = paramFactory == null ? null : paramFactory.GetExpressionOrDefault(paramRequest); - // Meant that parent Or parameter itself allows default value, + // Meant that parent Or parameter itself allows default value, // otherwise we did not get null but exception if (paramExpr == null) { - // Check if parameter itself (without propagated parent details) + // Check if parameter itself (without propagated parent details) // does not allow default, then stop checking the rest of parameters. if (paramInfo.Details.IfUnresolved == IfUnresolved.Throw) return null; var defaultValue = paramInfo.Details.DefaultValue; paramExpr = defaultValue != null - ? paramRequest.Container.GetOrAddStateItemExpression(defaultValue) + ? container.GetOrAddStateItemExpression(defaultValue) : paramRequest.ServiceType.GetDefaultValueExpression(); } } } + if (paramExpr.NodeType != ExpressionType.Constant && + !(paramExpr.NodeType == ExpressionType.Convert && + ((UnaryExpression)paramExpr).Operand.NodeType == ExpressionType.Constant)) + allParamsAreConstants = false; + paramExprs[i] = paramExpr; } } } - return CreateServiceExpression(factoryMethod.ConstructorOrMethodOrMember, factoryExpr, paramExprs, request); + return CreateServiceExpression(factoryMethod.ConstructorOrMethodOrMember, factoryExpr, paramExprs, request, + allParamsAreConstants); } #region Implementation - private readonly Type _implementationType; - private readonly ClosedGenericFactoryGenerator _factoryGenerator; + private Type _implementationType; // non-readonly to be set by provider + private readonly Func _implementationTypeProvider; + private readonly Made _made; + private ClosedGenericFactoryGenerator _factoryGenerator; private sealed class ClosedGenericFactoryGenerator : IConcreteFactoryGenerator { @@ -6869,7 +7647,7 @@ public ClosedGenericFactoryGenerator(ReflectionFactory openGenericFactory) _openGenericFactory = openGenericFactory; } - public Factory GetGeneratedFactoryOrDefault(Request request) + public Factory GetGeneratedFactory(Request request, bool ifErrorReturnDefault = false) { var serviceType = request.GetActualServiceType(); @@ -6883,103 +7661,153 @@ public Factory GetGeneratedFactoryOrDefault(Request request) return generatedFactory; } - request = request.WithResolvedFactory(_openGenericFactory); + var openFactory = _openGenericFactory; + request = request.WithResolvedFactory(openFactory, + skipRecursiveDependencyCheck: ifErrorReturnDefault, skipCaptiveDependencyCheck: ifErrorReturnDefault); - var implementationType = _openGenericFactory._implementationType; + var implType = openFactory._implementationType; - var closedTypeArgs = - implementationType == null ? serviceType.GetGenericParamsAndArgs() - : implementationType == serviceType.GetGenericDefinitionOrNull() ? serviceType.GetGenericParamsAndArgs() - : implementationType.IsGenericParameter ? new[] { serviceType } - : GetClosedTypeArgsOrNullForOpenGenericType(implementationType, serviceType, request); + var closedTypeArgs = implType == null || implType == serviceType.GetGenericDefinitionOrNull() + ? serviceType.GetGenericParamsAndArgs() + : implType.IsGenericParameter ? new[] { serviceType } + : GetClosedTypeArgsOrNullForOpenGenericType(implType, serviceType, request, ifErrorReturnDefault); if (closedTypeArgs == null) return null; - var closedMade = _openGenericFactory.Made; - if (closedMade.FactoryMethod != null) + var made = openFactory.Made; + if (made.FactoryMethod != null) { - var factoryMethod = closedMade.FactoryMethod(request) - .ThrowIfNull(Error.GotNullFactoryWhenResolvingService, request); + var factoryMethod = made.FactoryMethod(request); + if (factoryMethod == null) + return ifErrorReturnDefault ? null + : Throw.For(Error.GotNullFactoryWhenResolvingService, request); - var checkMatchingType = implementationType != null && implementationType.IsGenericParameter; + var checkMatchingType = implType != null && implType.IsGenericParameter; var closedFactoryMethod = GetClosedFactoryMethodOrDefault( factoryMethod, closedTypeArgs, request, checkMatchingType); // may be null only for IfUnresolved.ReturnDefault or check for matching type is failed - if (closedFactoryMethod == null) + if (closedFactoryMethod == null) return null; - closedMade = Made.Of(closedFactoryMethod, closedMade.Parameters, closedMade.PropertiesAndFields); + made = Made.Of(closedFactoryMethod, made.Parameters, made.PropertiesAndFields); } - Type closedImplementationType = null; - if (implementationType != null) + Type closedImplType = null; + if (implType != null) { - if (implementationType.IsGenericParameter) - closedImplementationType = closedTypeArgs[0]; + if (implType.IsGenericParameter) + closedImplType = closedTypeArgs[0]; else - closedImplementationType = Throw.IfThrows( - () => implementationType.MakeGenericType(closedTypeArgs), - request.IfUnresolved == IfUnresolved.Throw, - Error.NoMatchedGenericParamConstraints, implementationType, request); + closedImplType = Throw.IfThrows( + () => implType.MakeGenericType(closedTypeArgs), + !ifErrorReturnDefault && request.IfUnresolved == IfUnresolved.Throw, + Error.NoMatchedGenericParamConstraints, implType, request); - if (closedImplementationType == null) + if (closedImplType == null) return null; } - var closedGenericFactory = new ReflectionFactory( - closedImplementationType, _openGenericFactory.Reuse, closedMade, _openGenericFactory.Setup); + var closedGenericFactory = new ReflectionFactory(closedImplType, openFactory.Reuse, made, openFactory.Setup); - // Storing generated factory ID to service type/key mapping + // Storing generated factory ID to service type/key mapping // to find/remove generated factories when needed _generatedFactories.Swap(_ => _.AddOrUpdate(generatedFactoryKey, closedGenericFactory)); return closedGenericFactory; } private readonly ReflectionFactory _openGenericFactory; - private readonly Ref, ReflectionFactory>> + private readonly Ref, ReflectionFactory>> _generatedFactories = Ref.Of(ImTreeMap, ReflectionFactory>.Empty); } - private Type GetValidImplementationTypeOrDefault(Type implementationType) + private void SetKnownImplementationType(Type implType, Made made) { + var knownImplType = implType; + var factoryMethodResultType = Made.FactoryMethodKnownResultType; - if (implementationType == null || - implementationType == typeof(object) || - implementationType.IsAbstract()) + if (implType == null || + implType == typeof(object) || + implType.IsAbstract()) { - if (Made.FactoryMethod == null) + if (made.FactoryMethod == null) { - if (implementationType == null) + if (implType == null) Throw.It(Error.RegisteringNullImplementationTypeAndNoFactoryMethod); - if (implementationType.IsAbstract()) - Throw.It(Error.RegisteringAbstractImplementationTypeAndNoFactoryMethod, implementationType); + if (implType.IsAbstract()) + Throw.It(Error.RegisteringAbstractImplementationTypeAndNoFactoryMethod, implType); } - implementationType = null; // Ensure that we do not have abstract implementation type + 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 && + if (factoryMethodResultType != null && factoryMethodResultType != typeof(object) && !factoryMethodResultType.IsAbstract()) - implementationType = factoryMethodResultType; - - return implementationType; + knownImplType = factoryMethodResultType; } - - if (factoryMethodResultType != null && factoryMethodResultType != implementationType) - implementationType.ThrowIfNotImplementedBy(factoryMethodResultType, + else if (factoryMethodResultType != null + && factoryMethodResultType != implType) + { + implType.ThrowIfNotImplementedBy(factoryMethodResultType, Error.RegisteredFactoryMethodResultTypesIsNotAssignableToImplementationType); + } + + var openGenericImplType = knownImplType ?? implType; + if (openGenericImplType == typeof(object) || // for open-generic T implementation + openGenericImplType != null && ( // for open-generic X implementation + openGenericImplType.IsGenericDefinition() || + openGenericImplType.IsGenericParameter)) + _factoryGenerator = new ClosedGenericFactoryGenerator(this); - return implementationType; + _implementationType = knownImplType; } - private Expression CreateServiceExpression(MemberInfo ctorOrMethodOrMember, Expression factoryExpr, Expression[] paramExprs, Request request) + private Expression CreateServiceExpression(MemberInfo ctorOrMethodOrMember, Expression factoryExpr, Expression[] paramExprs, + Request request, bool allParamsAreConstants) { + var rules = request.Rules; + var ctor = ctorOrMethodOrMember as ConstructorInfo; if (ctor != null) - return InitPropertiesAndFields(Expression.New(ctor, paramExprs), request); + { + // optimize singleton creation bypassing Expression.New + if (allParamsAreConstants && ctor.IsPublic && + rules.PropertiesAndFields == null && Made.PropertiesAndFields == null) + { + if (request.Reuse is SingletonReuse && + request.Rules.EagerCachingSingletonForFasterAccess && + FactoryType == FactoryType.Service && + !Setup.PreventDisposal && !Setup.WeaklyReferenced && + !request.TracksTransientDisposable && + !request.IsWrappedInFunc()) + { + var activateSingleton = ActivateSingleton(ctor.DeclaringType, paramExprs); + + var singletonScope = request.SingletonScope; + var singletonId = singletonScope.GetScopedItemIdOrSelf(FactoryID); + var singleton = singletonScope.GetOrAdd(singletonId, activateSingleton); + + return Expression.Constant(singleton); + } + } + + var newServiceExpr = Expression.New(ctor, paramExprs); + + if (rules.PropertiesAndFields == null && Made.PropertiesAndFields == null) + return newServiceExpr; + + var selector = rules.OverrideRegistrationMade + ? Made.PropertiesAndFields.OverrideWith(rules.PropertiesAndFields) + : rules.PropertiesAndFields.OverrideWith(Made.PropertiesAndFields); + + var propertiesAndFields = selector(request); + if (propertiesAndFields == null) + return newServiceExpr; + + return InitPropertiesAndFields(newServiceExpr, request, propertiesAndFields); + } var method = ctorOrMethodOrMember as MethodInfo; var serviceExpr = method != null @@ -6998,14 +7826,58 @@ private Expression CreateServiceExpression(MemberInfo ctorOrMethodOrMember, Expr return serviceExpr; } + private static CreateScopedValue ActivateSingleton(Type singletonType, IList argExprs) + { + object[] args = null; + if (argExprs == null || argExprs.Count == 0) + { + args = ArrayTools.Empty(); + } + else if (argExprs.Count == 1) + { + var argExpr = argExprs[0]; + if (argExpr.NodeType == ExpressionType.Convert) + argExpr = ((UnaryExpression)argExpr).Operand; + + var constExpr = argExpr as ConstantExpression; + if (constExpr != null) + args = new[] { constExpr.Value }; + } + else + { + var constantArgs = new object[argExprs.Count]; + int i = constantArgs.Length - 1; + for (; i >= 0; --i) + { + var argExpr = argExprs[i]; + if (argExpr.NodeType == ExpressionType.Convert) + argExpr = ((UnaryExpression)argExpr).Operand; + + var constExpr = argExpr as ConstantExpression; + if (constExpr == null) + break; + constantArgs[i] = constExpr.Value; + } + + if (i == -1) // all args are constants + args = constantArgs; + } + + if (args == null) + return null; + + return () => Activator.CreateInstance(singletonType, args); + } + private FactoryMethod GetFactoryMethod(Request request) { - var implType = _implementationType; - var factoryMethodSelector = Made.FactoryMethod ?? request.Container.Rules.FactoryMethod; + var implType = request.ImplementationType; + var factoryMethodSelector = Made.FactoryMethod ?? request.Rules.FactoryMethod; if (factoryMethodSelector == null) { - var ctors = implType.GetPublicInstanceConstructors().ToArrayOrSelf(); - return FactoryMethod.Of(ctors[0]); + // there is a guarantee of single constructor, which was checked on factory registration + var ctor = _knownSingleCtor ?? implType.GetPublicInstanceConstructors().First(); + return FactoryMethod.Of(ctor); } var factoryMethod = factoryMethodSelector(request); @@ -7021,20 +7893,13 @@ private FactoryMethod GetFactoryMethod(Request request) Error.FactoryObjIsNullInFactoryMethod, factoryMethod, request); } - return factoryMethod.ThrowIfNull(Error.UnableToGetConstructorFromSelector, implType); + return factoryMethod.ThrowIfNull(Error.UnableToGetConstructorFromSelector, implType, request); } - private Expression InitPropertiesAndFields(NewExpression newServiceExpr, Request request) + private Expression InitPropertiesAndFields(NewExpression newServiceExpr, + Request request, IEnumerable members) { - var containerRules = request.Container.Rules; - var selector = containerRules.OverrideRegistrationMade - ? Made.PropertiesAndFields.OverrideWith(containerRules.PropertiesAndFields) - : containerRules.PropertiesAndFields.OverrideWith(Made.PropertiesAndFields); - - var members = selector(request); - if (members == null) - return newServiceExpr; - + var container = request.Container; var bindings = new List(); foreach (var member in members) if (member != null) @@ -7046,11 +7911,11 @@ private Expression InitPropertiesAndFields(NewExpression newServiceExpr, Request var customValue = member.Details.CustomValue; if (customValue != null) customValue.ThrowIfNotOf(memberRequest.ServiceType, Error.InjectedCustomValueIsOfDifferentType, memberRequest); - memberExpr = memberRequest.Container.GetOrAddStateItemExpression(customValue, memberRequest.ServiceType); + memberExpr = container.GetOrAddStateItemExpression(customValue, memberRequest.ServiceType); } else { - var memberFactory = memberRequest.Container.ResolveFactory(memberRequest); + var memberFactory = container.ResolveFactory(memberRequest); memberExpr = memberFactory == null ? null : memberFactory.GetExpressionOrDefault(memberRequest); if (memberExpr == null && request.IfUnresolved == IfUnresolved.ReturnDefault) return null; @@ -7063,7 +7928,8 @@ private Expression InitPropertiesAndFields(NewExpression newServiceExpr, Request return bindings.Count == 0 ? (Expression)newServiceExpr : Expression.MemberInit(newServiceExpr, bindings); } - private static Type[] GetClosedTypeArgsOrNullForOpenGenericType(Type openImplType, Type closedServiceType, Request request) + private static Type[] GetClosedTypeArgsOrNullForOpenGenericType( + Type openImplType, Type closedServiceType, Request request, bool ifErrorReturnDefault) { var serviceTypeArgs = closedServiceType.GetGenericParamsAndArgs(); var serviceTypeGenericDef = closedServiceType.GetGenericTypeDefinition(); @@ -7086,7 +7952,7 @@ private static Type[] GetClosedTypeArgsOrNullForOpenGenericType(Type openImplTyp } if (!matchFound) - return request.IfUnresolved == IfUnresolved.ReturnDefault ? null + return ifErrorReturnDefault || request.IfUnresolved == IfUnresolved.ReturnDefault ? null : Throw.For(Error.NoMatchedImplementedTypesWithServiceType, openImplType, implementedTypes, request); @@ -7094,7 +7960,7 @@ private static Type[] GetClosedTypeArgsOrNullForOpenGenericType(Type openImplTyp var notMatchedIndex = Array.IndexOf(implTypeArgs, null); if (notMatchedIndex != -1) - return request.IfUnresolved == IfUnresolved.ReturnDefault ? null + return ifErrorReturnDefault || request.IfUnresolved == IfUnresolved.ReturnDefault ? null : Throw.For(Error.NotFoundOpenGenericImplTypeArgInService, openImplType, implTypeParams[notMatchedIndex], request); @@ -7116,7 +7982,7 @@ private static void MatchOpenGenericConstraints(Type[] implTypeParams, Type[] im for (var j = 0; !constraintMatchFound && j < implTypeParamConstraints.Length; ++j) { var implTypeParamConstraint = implTypeParamConstraints[j]; - if (implTypeParamConstraint != implTypeArg && + if (implTypeParamConstraint != implTypeArg && implTypeParamConstraint.IsOpenGeneric()) { // match type parameters inside constraint @@ -7133,7 +7999,7 @@ private static void MatchOpenGenericConstraints(Type[] implTypeParams, Type[] im } private static bool MatchServiceWithImplementedTypeParams( - Type[] resultImplArgs, Type[] implParams, Type[] serviceParams, Type[] serviceArgs, + Type[] resultImplArgs, Type[] implParams, Type[] serviceParams, Type[] serviceArgs, int resultCount = 0) { for (var i = 0; i < serviceParams.Length; i++) @@ -7180,8 +8046,8 @@ private static void MatchOpenGenericConstraints(Type[] implTypeParams, Type[] im var factoryInfo = factoryMethod.FactoryServiceInfo; var factoryResultType = factoryMember.GetReturnTypeOrDefault(); - var implTypeParams = factoryResultType.IsGenericParameter - ? new[] { factoryResultType } + var implTypeParams = factoryResultType.IsGenericParameter + ? new[] { factoryResultType } : factoryResultType.GetGenericParamsAndArgs(); // Get method declaring type, and if its open-generic, @@ -7242,7 +8108,7 @@ private static void MatchOpenGenericConstraints(Type[] implTypeParams, Type[] im MatchOpenGenericConstraints(factoryImplTypeParams, resultFactoryImplTypeArgs); - // Close the factory type implementation + // Close the factory type implementation // and get factory member to use from it. var closedFactoryImplType = Throw.IfThrows( () => factoryImplType.MakeGenericType(resultFactoryImplTypeArgs), @@ -7257,7 +8123,7 @@ private static void MatchOpenGenericConstraints(Type[] implTypeParams, Type[] im if (factoryMethodBase != null) { var factoryMethodParameters = factoryMethodBase.GetParameters(); - var targetMethods = closedFactoryImplType.GetDeclaredAndBase(t => t.DeclaredMethods) + var targetMethods = closedFactoryImplType.GetMembers(t => t.DeclaredMethods, includeBase: true) .Where(m => m.Name == factoryMember.Name && m.GetParameters().Length == factoryMethodParameters.Length) .ToArray(); @@ -7275,17 +8141,17 @@ private static void MatchOpenGenericConstraints(Type[] implTypeParams, Type[] im } else if (factoryMember is FieldInfo) { - factoryMember = closedFactoryImplType.GetDeclaredAndBase(t => t.DeclaredFields) + factoryMember = closedFactoryImplType.GetMembers(t => t.DeclaredFields, includeBase: true) .Single(f => f.Name == factoryMember.Name); } else if (factoryMember is PropertyInfo) { - factoryMember = closedFactoryImplType.GetDeclaredAndBase(t => t.DeclaredProperties) + 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, + // 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) @@ -7339,7 +8205,8 @@ public override Expression CreateExpressionOrDefault(Request request) private readonly Func _getServiceExpression; } - /// Factory representing external service object registered with . + // todo: v3: Remove + /// Obsolete: replaced with UsedInstanceFactory. public sealed class InstanceFactory : Factory { private object _instance; @@ -7368,13 +8235,17 @@ public void ReplaceInstance(object newInstance) /// Context Expression throwing exception. public override Expression CreateExpressionOrDefault(Request request) { - return Expression.Constant(_instance); + return Expression.Constant(_instance); } /// Puts instance directly to available scope. protected override Expression ApplyReuse(Expression _, IReuse reuse, bool tracksTransientDisposableIgnored, Request request) { - var scope = Reuse.GetScopeOrDefault(request).ThrowIfNull(Error.NoCurrentScope); + var scopedReuse = reuse as CurrentScopeReuse; + var scope = scopedReuse != null + ? request.Scopes.GetCurrentNamedScope(scopedReuse.Name, throwIfNotFound: true) + : request.SingletonScope; + var scopedId = scope.GetScopedItemIdOrSelf(FactoryID); var instance = _instance; scope.GetOrAdd(scopedId, () => instance); @@ -7382,10 +8253,8 @@ protected override Expression ApplyReuse(Expression _, IReuse reuse, bool tracks var instanceType = instance == null || instance.GetType().IsValueType() ? typeof(object) : instance.GetType(); var instanceExpr = Expression.Constant(instance, instanceType); - var scopeExpr = Reuse.GetScopeExpression(request); - Expression serviceExpr = Expression.Call(scopeExpr, "GetOrAdd", ArrayTools.Empty(), - Expression.Constant(scopedId), - Expression.Lambda(instanceExpr, ArrayTools.Empty())); + var reuseV3 = (reuse as IReuseV3).ThrowIfNull(); + var serviceExpr = reuseV3.Apply(request, tracksTransientDisposableIgnored, instanceExpr); // Unwrap WeakReference and/or array preventing disposal if (Setup.WeaklyReferenced) @@ -7403,7 +8272,7 @@ protected override Expression ApplyReuse(Expression _, IReuse reuse, bool tracks } } - /// This factory is the thin wrapper for user provided delegate + /// This factory is the thin wrapper for user provided delegate /// and where possible it uses delegate directly: without converting it to expression. public sealed class DelegateFactory : Factory { @@ -7429,12 +8298,13 @@ public sealed class DelegateFactory : Factory public override Expression CreateExpressionOrDefault(Request request) { var factoryDelegateExpr = request.Container.GetOrAddStateItemExpression(_factoryDelegate); - return Expression.Convert(Expression.Invoke(factoryDelegateExpr, Container.ResolverExpr), request.ServiceType); + var resolverExpr = Container.GetResolverExpr(request); + return Expression.Convert(Expression.Invoke(factoryDelegateExpr, resolverExpr), request.ServiceType); } /// If possible returns delegate directly, without creating expression trees, just wrapped in . /// If decorator found for request then factory fall-backs to expression creation. - /// Request to resolve. + /// Request to resolve. /// Factory delegate directly calling wrapped delegate, or invoking expression if decorated. public override FactoryDelegate GetDelegateOrDefault(Request request) { @@ -7442,11 +8312,9 @@ public override FactoryDelegate GetDelegateOrDefault(Request request) if (FactoryType == FactoryType.Service && request.Container.GetDecoratorExpressionOrDefault(request) != null) - return base.GetDelegateOrDefault(request); // via expression creation - - ThrowIfReuseHasShorterLifespanThanParent(Reuse, request); + return base.GetDelegateOrDefault(request); // use expression creation - if (Reuse != null) + if (request.Reuse != DryIoc.Reuse.Transient) return base.GetDelegateOrDefault(request); // use expression creation return (state, r, scope) => _factoryDelegate(r.Resolver); @@ -7459,7 +8327,7 @@ public override FactoryDelegate GetDelegateOrDefault(Request request) /// Should return value stored in scope. public delegate object CreateScopedValue(); - /// Lazy object storage that will create object with provided factory on first access, + /// Lazy object storage that will create object with provided factory on first access, /// then will be returning the same object for subsequent access. public interface IScope : IDisposable { @@ -7473,7 +8341,7 @@ public interface IScope : IDisposable /// Unique ID to find created object in subsequent calls. /// Delegate to create object. It will be used immediately, and reference to delegate will not be stored. /// Created and stored object. - /// Scope does not store (no memory leak here), + /// Scope does not store (no memory leak here), /// it stores only result of call. object GetOrAdd(int id, CreateScopedValue createValue); @@ -7481,9 +8349,9 @@ public interface IScope : IDisposable /// To set value at. Value to set. void SetOrAdd(int id, object item); - /// Creates id/index for new item to be stored in scope. + /// Creates id/index for new item to be stored in scope. /// If separate index is not supported then just returns back passed . - /// Id to be mapped to new item id/index + /// Id to be mapped to new item id/index /// New it/index or just passed int GetScopedItemIdOrSelf(int externalId); } @@ -7564,7 +8432,7 @@ public void SetOrAdd(int id, object item) } /// Disposes all stored objects and nullifies object storage. - /// If item disposal throws exception, then it won't be propagated outside, + /// If item disposal throws exception, then it won't be propagated outside, /// so the rest of the items could be disposed. public void Dispose() { @@ -7580,7 +8448,7 @@ public void Dispose() _items = ImTreeMapIntToObj.Empty; } - /// Prints scope info (name and parent) to string for debug purposes. + /// Prints scope info (name and parent) to string for debug purposes. /// String representation. public override string ToString() { @@ -7662,6 +8530,9 @@ public sealed class SingletonScope : IScope /// Amount of items in item array. public static readonly int BucketSize = 32; + /// Returns true if scope was disposed. + public bool IsDisposed { get { return _disposed == 1; } } + /// Creates scope. /// Parent in scope stack. Associated name object. public SingletonScope(IScope parent = null, object name = null) @@ -7673,6 +8544,32 @@ public SingletonScope(IScope parent = null, object name = null) _lastItemIndex = 0; } + internal int IndexOf(int factoryId) + { + var indexObj = _factoryIdToIndexMap.GetValueOrDefault(factoryId); + return indexObj == null ? -1 : (int)indexObj; + } + + internal object GetOrDefault(int index) + { + if (index < BucketSize) + return Items[index]; + + var buckets = Items[0] as object[][]; + if (buckets == null) + return null; + + var bucketIndex = (index / BucketSize) - 1; // bucket indeces start with 0 + if (bucketIndex >= buckets.Length) + return null; + + var bucket = buckets[bucketIndex]; + if (bucket == null) + return null; + + return bucket[index % BucketSize]; + } + /// Adds mapping between provide id and index for new stored item. Returns index. /// External id mapped to internal index. /// Already mapped index, or newly created. @@ -7722,21 +8619,6 @@ public void SetOrAdd(int id, object item) TrackDisposable(item); } - /// Adds external non-service item into singleton collection. - /// The item may not have corresponding external item ID. - /// External item to add, this may be metadata, service key, etc. - /// Index of added or already added item. - internal int GetOrAdd(object item) - { - var index = IndexOf(item); - if (index == -1) - { - index = Interlocked.Increment(ref _lastItemIndex); - SetOrAdd(index, item); - } - return index; - } - /// Disposes all stored objects and nullifies object storage. /// If item disposal throws exception, then it won't be propagated outside, so the rest of the items could be disposed. public void Dispose() @@ -7756,11 +8638,7 @@ public void Dispose() #region Implementation - private static readonly object[] _lockers = - { - new object(), new object(), new object(), new object(), - new object(), new object(), new object(), new object() - }; + private readonly object _syncRoot = new object(); private ImTreeMapIntToObj _factoryIdToIndexMap; private int _lastItemIndex; @@ -7803,8 +8681,7 @@ private object GetOrAddItem(object[] bucket, int index, CreateScopedValue create if (value != null) return value; - var locker = _lockers[index % _lockers.Length]; - lock (locker) + lock (_syncRoot) { value = bucket[index]; if (value == null) @@ -7822,7 +8699,7 @@ private object GetOrAddItem(object[] bucket, int index, CreateScopedValue create // if not - create new buckets array and copy old buckets into it private object[] GetOrAddBucket(int index) { - var bucketIndex = index / BucketSize - 1; + var bucketIndex = (index / BucketSize) - 1; var buckets = Items[0] as object[][]; if (buckets == null || buckets.Length < bucketIndex + 1 || @@ -7857,32 +8734,6 @@ private object[] GetOrAddBucket(int index) return bucket; } - private int IndexOf(object item) - { - var index = Items.IndexOf(item); - if (index != -1) - return index; - - // look in other buckets - var bucketsObj = Items[0]; - if (bucketsObj != null) - { - var buckets = (object[][])bucketsObj; - for (var i = 0; i < buckets.Length; i++) - { - var b = buckets[i]; - if (b != null) - { - index = b.IndexOf(item); - if (index != -1) - return (i + 1) * BucketSize + index; - } - } - } - - return -1; - } - #endregion } @@ -7891,7 +8742,7 @@ private int IndexOf(object item) /// New scope or old if do not want to change current scope. public delegate IScope SetCurrentScopeHandler(IScope oldScope); - /// Provides ambient current scope and optionally scope storage for container, + /// Provides ambient current scope and optionally scope storage for container, /// examples are HttpContext storage, Execution context, Thread local. public interface IScopeContext : IDisposable { @@ -7953,9 +8804,8 @@ public void Dispose() private ImTreeMapIntToObj _scopes = ImTreeMapIntToObj.Empty; } - /// Reuse goal is to locate or create scope where reused objects will be stored. - /// implementors supposed to be stateless, and provide scope location behavior only. - /// The reused service instances should be stored in scope(s). + // todo: v3: remove + /// Obsolete: until v3 replaced by . public interface IReuse { /// Relative to other reuses lifespan value. @@ -7966,11 +8816,8 @@ public interface IReuse /// Located scope. IScope GetScopeOrDefault(Request request); - /// Supposed to create in-line expression with the same code as body of method. - /// Request to get context information or for example store something in resolution state. - /// Expression of type . - /// Result expression should be static: should Not create closure on any objects. - /// If you require to reference some item from outside, put it into . + // todo: v3: remove + /// ObsoIReuseV3.ApplyApply"/> instead. Expression GetScopeExpression(Request request); /// Returns special id/index to lookup scoped item, or original passed factory id otherwise. @@ -7979,26 +8826,93 @@ public interface IReuse int GetScopedItemIdOrSelf(int factoryID, Request request); } + // todo: v3: rename to IReuse + // todo: v3: add object[] Names property + /// Simplified scope agnostic reuse abstraction. More easy to implement, + /// and more powerful as can be based on other storage beside reuse. + public interface IReuseV3 : IConvertibleToExpression + { + /// Relative to other reuses lifespan value. + int Lifespan { get; } + + // todo: v3: add. It also mabe be interpreted as object[] Names for matching with multiple scopes + // object Name { get; } + + // todo: v3: remove trackTransientDisposable param as it is available from Request param. + /// Returns composed expression. + /// info + /// Indicates that item should be tracked. + /// Service creation expression + /// Subject + Expression Apply(Request request, bool trackTransientDisposable, Expression createItemExpr); + + /// Returns true if reuse can be applied: may check if scope or other reused item storage is present. + /// Service request. Check result. + bool CanApply(Request request); + } + /// Returns container bound scope for storing singleton objects. - public sealed class SingletonReuse : IReuse + public sealed class SingletonReuse : IReuse, IReuseV3 { /// Relative to other reuses lifespan value. public int Lifespan { get { return 1000; } } + /// Returns item from singleton scope. + /// Singleton scope. + /// Indicates that item should be tracked instead of reused. + /// ID for lookup. + /// Delegate for creating the item. + /// Reused item. + public static object GetOrAddItem(IScope singletonScope, bool trackTransientDisposable, int factoryId, + CreateScopedValue createValue) + { + var scopedItemId = trackTransientDisposable ? -1 : singletonScope.GetScopedItemIdOrSelf(factoryId); + return singletonScope.GetOrAdd(scopedItemId, createValue); + } + + private static readonly MethodInfo _getOrAddItemMethod = + typeof(SingletonReuse).GetSingleMethodOrNull("GetOrAddItem"); + + /// Returns expression call to . + public Expression Apply(Request request, bool trackTransientDisposable, Expression createItemExpr) + { + return Expression.Call(_getOrAddItemMethod, + Container.SingletonScopeExpr, + Expression.Constant(trackTransientDisposable), + Expression.Constant(request.FactoryID), + Expression.Lambda(createItemExpr)); + } + + /// Returns true because singleton is always available. + /// _ True. + public bool CanApply(Request request) + { + return true; + } + + private readonly Lazy _singletonReuseExpr = new Lazy(() => + Expression.Field(null, typeof(Reuse).GetFieldOrNull("Singleton"))); + + /// + public Expression ToExpression(Func fallbackConverter) + { + return _singletonReuseExpr.Value; + } + + #region Obsolete + /// Returns container bound Singleton scope. /// Request to get context information or for example store something in resolution state. /// Container singleton scope. public IScope GetScopeOrDefault(Request request) { - return request.Scopes.SingletonScope; + return request.SingletonScope; } - /// Returns expression directly accessing . - /// Request to get context information or for example store something in resolution state. - /// Singleton scope property expression. + /// public Expression GetScopeExpression(Request request) { - return Expression.Property(Container.ScopesExpr, "SingletonScope"); + return Throw.For(Error.Of("Obsolete")); } /// Returns index of new item in singleton scope. @@ -8007,10 +8921,12 @@ public Expression GetScopeExpression(Request request) /// Index in scope. public int GetScopedItemIdOrSelf(int factoryID, Request request) { - return request.Scopes.SingletonScope.GetScopedItemIdOrSelf(factoryID); + return request.SingletonScope.GetScopedItemIdOrSelf(factoryID); } - /// Pretty prints reuse name and lifespan Printed string. + #endregion + + /// Pretty prints reuse name and lifespan Printed string. public override string ToString() { return GetType().Name + " {Lifespan=" + Lifespan + "}"; @@ -8019,7 +8935,7 @@ public override string ToString() /// Returns container bound current scope created by method. /// It is the same as Singleton scope if container was not created by . - public sealed class CurrentScopeReuse : IReuse + public sealed class CurrentScopeReuse : IReuse, IReuseV3 { /// Name to find current scope or parent with equal name. public readonly object Name; @@ -8027,13 +8943,76 @@ public sealed class CurrentScopeReuse : IReuse /// Relative to other reuses lifespan value. public int Lifespan { get { return 100; } } - /// Creates reuse optionally specifying its name. + /// Creates reuse optionally specifying its name. /// (optional) Used to find matching current scope or parent. public CurrentScopeReuse(object name = null) { Name = name; } + /// Returns item from current scope with specified name. + /// Container scopes to select from. + /// scope name to look up. + /// Specifies to throw if scope with the is not found. + /// + /// ID for lookup. + /// Delegate for creating the item. + /// Reused item. + public static object GetOrAddItemOrDefault( + IScopeAccess containerScopes, object scopeName, bool throwIfNoScopeFound, + bool trackTransientDisposable, int factoryId, CreateScopedValue createValue) + { + var scope = containerScopes.GetCurrentNamedScope(scopeName, throwIfNoScopeFound); + if (scope == null) + return null; + var scopedItemId = trackTransientDisposable ? -1 : scope.GetScopedItemIdOrSelf(factoryId); + return scope.GetOrAdd(scopedItemId, createValue); + } + + private static readonly MethodInfo _getOrAddItemOrDefaultMethod = + typeof(CurrentScopeReuse).GetSingleMethodOrNull("GetOrAddItemOrDefault"); + + /// Returns expression call to . + public Expression Apply(Request request, bool trackTransientDisposable, Expression createItemExpr) + { + var scopeNameExpr = request.Container.GetOrAddStateItemExpression(Name); + if (Name != null && Name.GetType().IsValueType()) + scopeNameExpr = Expression.Convert(scopeNameExpr, typeof(object)); + + var scopesExpr = Container.GetScopesExpr(request); + + return Expression.Call(_getOrAddItemOrDefaultMethod, + scopesExpr, scopeNameExpr, + Expression.Constant(request.IfUnresolved == IfUnresolved.Throw), + Expression.Constant(trackTransientDisposable), + Expression.Constant(request.FactoryID), + Expression.Lambda(createItemExpr)); + } + + /// Returns true if scope is open and the name is matching with reuse . + /// Service request. Check result. + public bool CanApply(Request request) + { + return // first is the special case whith ambient scope context, + // where scope can be switched for already resolved singleton. + // So it may be no valid initially but only afterwars + (request.Container.ScopeContext != null && request.IsWrappedInFunc()) || + request.Scopes.GetCurrentNamedScope(Name, false) != null; + } + + private readonly Lazy _inCurrentScopeReuseExpr = new Lazy(() => + Expression.Field(null, typeof(Reuse).GetFieldOrNull("InCurrentScope"))); + + /// + public Expression ToExpression(Func fallbackConverter) + { + return Name == null ? _inCurrentScopeReuseExpr.Value + : Expression.Call(typeof(Reuse), "InCurrentNamedScope", ArrayTools.Empty(), + fallbackConverter(Name)); + } + + #region Obsolete + /// Returns container current scope or if specified: current scope or its parent with corresponding name. /// Request to get context information or for example store something in resolution state. /// Found current scope or its parent. @@ -8044,17 +9023,10 @@ public IScope GetScopeOrDefault(Request request) return request.Scopes.GetCurrentNamedScope(Name, false); } - /// Returns method call expression. - /// Request to get context information or for example store something in resolution state. - /// Method call expression returning matched current scope. + /// public Expression GetScopeExpression(Request request) { - var nameExpr = request.Container.GetOrAddStateItemExpression(Name); - if (Name != null && Name.GetType().IsValueType()) - nameExpr = Expression.Convert(nameExpr, typeof(object)); - return Expression.Call(Container.ScopesExpr, "GetCurrentNamedScope", ArrayTools.Empty(), - nameExpr, - Expression.Constant(true)); + return Throw.For(Error.Of("Obsolete")); } /// Asks the scope to convert factory ID into internal representation and returns it. @@ -8063,10 +9035,11 @@ public Expression GetScopeExpression(Request request) /// Scope mapping of factory ID or passed factory ID without changes if scope is not available. public int GetScopedItemIdOrSelf(int factoryID, Request request) { - var scope = GetScopeOrDefault(request); - return scope == null ? factoryID : scope.GetScopedItemIdOrSelf(factoryID); + return Throw.For(Error.Of("Obsolete")); } + #endregion + /// Pretty prints reuse to string. Reuse string. public override string ToString() { @@ -8079,7 +9052,7 @@ public override string ToString() /// Represents services created once per resolution root (when some of Resolve methods called). /// Scope is created only if accessed to not waste memory. - public sealed class ResolutionScopeReuse: IReuse + public sealed class ResolutionScopeReuse : IReuse, IReuseV3 { /// Relative to other reuses lifespan value. public int Lifespan { get { return 0; } } @@ -8104,6 +9077,41 @@ public ResolutionScopeReuse(Type assignableFromServiceType = null, object servic Outermost = outermost; } + /// + public Expression Apply(Request request, bool trackTransientDisposable, Expression createItemExpr) + { + var scopeExpr = GetScopeExpression(request); + + // For transient disposable we don't care to bind to specific ID, because it should be created each time. + var scopedId = trackTransientDisposable ? -1 : request.FactoryID; + return Expression.Call(scopeExpr, "GetOrAdd", ArrayTools.Empty(), + Expression.Constant(scopedId), + Expression.Lambda(createItemExpr, ArrayTools.Empty())); + } + + /// + public bool CanApply(Request request) + { + return GetScopeOrDefault(request) != null; + } + + private readonly Lazy _inResolutionScopeReuseExpr = new Lazy(() => + Expression.Field(null, typeof(Reuse).GetFieldOrNull("InCurrentScope"))); + + /// + public Expression ToExpression(Func fallbackConverter) + { + if (AssignableFromServiceType == null && ServiceKey == null && Outermost == false) + return _inResolutionScopeReuseExpr.Value; + + return Expression.Call(typeof(Reuse), "InResolutionScopeOf", ArrayTools.Empty(), + Expression.Constant(AssignableFromServiceType, typeof(Type)), + fallbackConverter(ServiceKey), + Expression.Constant(Outermost, typeof(bool))); + } + + #region Obsolete + /// Creates or returns already created resolution root scope. /// Request to get context information or for example store something in resolution state. /// Created or existing scope. @@ -8113,10 +9121,11 @@ public IScope GetScopeOrDefault(Request request) if (scope == null) { var parent = request.Enumerate().Last(); - request.Scopes.GetOrCreateResolutionScope(ref scope, parent.ServiceType, parent.ServiceKey); + request.Scopes.GetOrCreateResolutionScope(ref scope, parent.GetActualServiceType(), parent.ServiceKey); } - return request.Scopes.GetMatchingResolutionScope(scope, AssignableFromServiceType, ServiceKey, Outermost, false); + return request.Scopes.GetMatchingResolutionScope(scope, + AssignableFromServiceType, ServiceKey, Outermost, throwIfNotFound: false); } /// Returns method call expression. @@ -8124,12 +9133,13 @@ public IScope GetScopeOrDefault(Request request) /// Method call expression returning existing or newly created resolution scope. public Expression GetScopeExpression(Request request) { - return Expression.Call(Container.ScopesExpr, "GetMatchingResolutionScope", ArrayTools.Empty(), + var scopesExpr = Container.GetScopesExpr(request); + return Expression.Call(scopesExpr, "GetMatchingResolutionScope", ArrayTools.Empty(), Container.GetResolutionScopeExpression(request), Expression.Constant(AssignableFromServiceType, typeof(Type)), request.Container.GetOrAddStateItemExpression(ServiceKey, typeof(object)), Expression.Constant(Outermost, typeof(bool)), - Expression.Constant(true, typeof(bool))); + Expression.Constant(request.IfUnresolved == IfUnresolved.Throw, typeof(bool))); } /// Just returns back passed id without changes. @@ -8140,6 +9150,8 @@ public int GetScopedItemIdOrSelf(int factoryID, Request request) return factoryID; } + #endregion + /// Pretty prints reuse name and lifespan Printed string. public override string ToString() { @@ -8151,12 +9163,12 @@ public override string ToString() } } - /// Specifies pre-defined reuse behaviors supported by container: + /// Specifies pre-defined reuse behaviors supported by container: /// used when registering services into container with methods. public static class Reuse { /// Synonym for absence of reuse. - public static readonly IReuse Transient = null; // no reuse. + public static readonly IReuse Transient = new TransientReuse(); /// Specifies to store single service instance per . public static readonly IReuse Singleton = new SingletonReuse(); @@ -8207,6 +9219,55 @@ public static IReuse InResolutionScopeOf(object serv /// Web request is just convention for reuse in with special name . public static readonly IReuse InWebRequest = InCurrentNamedScope(WebRequestScopeName); + + #region Implementation + + /// No-reuse + private sealed class TransientReuse : IReuse, IReuseV3 + { + /// 0 means no reused lifespan + public int Lifespan { get { return 0; } } + + /// returns source expression without modification + public Expression Apply(Request request, bool trackTransientDisposable, Expression createItemExpr) + { + return createItemExpr; + } + + public bool CanApply(Request request) + { + return true; + } + + private readonly Lazy _transientReuseExpr = new Lazy(() => + Expression.Field(null, typeof(Reuse).GetFieldOrNull("Transient"))); + + public Expression ToExpression(Func fallbackConverter) + { + return _transientReuseExpr.Value; + } + + #region Obsolete + + public IScope GetScopeOrDefault(Request request) + { + throw new NotImplementedException(); + } + + public Expression GetScopeExpression(Request request) + { + throw new NotImplementedException(); + } + + public int GetScopedItemIdOrSelf(int factoryID, Request request) + { + throw new NotImplementedException(); + } + + #endregion + } + + #endregion } /// Policy to handle unresolved service. @@ -8222,7 +9283,7 @@ public enum IfUnresolved public sealed class RequestInfo { /// Represents empty info (indicated by null ). - public static readonly RequestInfo Empty = new RequestInfo(null, -1, FactoryType.Service, null, null, null); + public static readonly RequestInfo Empty = new RequestInfo(null, -1, FactoryType.Service, null, null, default(RequestFlags), null); /// Wraps the resolved service lookup details. public readonly IServiceInfo ServiceInfo; @@ -8241,19 +9302,14 @@ public RequestInfo Parent { get { - return IsEmpty ? Empty : ParentOrWrapper.FirstOrEmpty(p => p.FactoryType != FactoryType.Wrapper); - } - } + if (IsEmpty) + return Empty; - /// Gets first request info starting with itself which satisfies the condition, or empty otherwise. - /// Condition to stop on. Should not be null. - /// Request info of found parent. - public RequestInfo FirstOrEmpty(Func condition) - { - var r = this; - while (!r.IsEmpty && !condition(r)) - r = r.ParentOrWrapper; - return r; + var p = ParentOrWrapper; + while (!p.IsEmpty && p.FactoryType == FactoryType.Wrapper) + p = p.ParentOrWrapper; + return p; + } } /// Requested service type. @@ -8270,9 +9326,7 @@ public Type RequiredServiceType /// The type to be used for lookup in registry. public Type GetActualServiceType() { - return RequiredServiceType != null - && RequiredServiceType.IsAssignableTo(ServiceType) - ? RequiredServiceType : ServiceType; + return ServiceInfo.GetActualServiceType(); } /// Returns known implementation, or otherwise actual service type. The subject. @@ -8320,24 +9374,28 @@ public object Metadata /// Relative number representing reuse lifespan. public int ReuseLifespan { get { return Reuse == null ? 0 : Reuse.Lifespan; } } + /// . + public readonly RequestFlags Flags; + /// Simplified version of Push with most common properties. /// /// Created info chain to current (parent) info. public RequestInfo Push(Type serviceType, int factoryID, Type implementationType, IReuse reuse) { - return Push(serviceType, null, null, factoryID, FactoryType.Service, implementationType, reuse); + var serviceInfo = DryIoc.ServiceInfo.Of(serviceType, null, IfUnresolved.Throw, null); + return Push(serviceInfo, factoryID, FactoryType.Service, implementationType, reuse); } /// Creates info by supplying all the properties and chaining it with current (parent) info. /// /// - /// + /// /// Created info chain to current (parent) info. public RequestInfo Push(Type serviceType, Type requiredServiceType, object serviceKey, - int factoryID, FactoryType factoryType, Type implementationType, IReuse reuse) + int factoryID, FactoryType factoryType, Type implementationType, IReuse reuse, RequestFlags flags) { var serviceInfo = DryIoc.ServiceInfo.Of(serviceType, requiredServiceType, IfUnresolved.Throw, serviceKey); - return Push(serviceInfo, factoryID, factoryType, implementationType, reuse); + return Push(serviceInfo, factoryID, factoryType, implementationType, reuse, flags); } /// Creates info by supplying all the properties and chaining it with current (parent) info. @@ -8345,59 +9403,41 @@ public RequestInfo Push(Type serviceType, int factoryID, Type implementationType /// /// /// + /// /// Created info chain to current (parent) info. - public RequestInfo Push(Type serviceType, - Type requiredServiceType, object serviceKey, - IfUnresolved ifUnresolved, - int factoryID, FactoryType factoryType, Type implementationType, IReuse reuse) + public RequestInfo Push(Type serviceType, Type requiredServiceType, object serviceKey, IfUnresolved ifUnresolved, + int factoryID, FactoryType factoryType, Type implementationType, IReuse reuse, RequestFlags flags) { var serviceInfo = DryIoc.ServiceInfo.Of(serviceType, requiredServiceType, ifUnresolved, serviceKey); - return Push(serviceInfo, factoryID, factoryType, implementationType, reuse); + return Push(serviceInfo, factoryID, factoryType, implementationType, reuse, flags); } /// Creates info by supplying all the properties and chaining it with current (parent) info. /// - /// + /// /// /// /// + /// /// Created info chain to current (parent) info. - public RequestInfo Push(Type serviceType, - Type requiredServiceType, object serviceKey, string metadataKey, object metadata, - IfUnresolved ifUnresolved, - int factoryID, FactoryType factoryType, Type implementationType, IReuse reuse) + public RequestInfo Push(Type serviceType, Type requiredServiceType, object serviceKey, string metadataKey, object metadata, IfUnresolved ifUnresolved, + int factoryID, FactoryType factoryType, Type implementationType, IReuse reuse, RequestFlags flags) { var info = DryIoc.ServiceInfo.Of(serviceType, requiredServiceType, ifUnresolved, serviceKey, metadataKey, metadata); - return Push(info, factoryID, factoryType, implementationType, reuse); + return Push(info, factoryID, factoryType, implementationType, reuse, flags); } /// Creates info by supplying all the properties and chaining it with current (parent) info. /// /// (optional) (optional) /// (optional) (optional) + /// (optional) /// Created info chain to current (parent) info. public RequestInfo Push(IServiceInfo serviceInfo, - int factoryID = 0, FactoryType factoryType = FactoryType.Service, - Type implementationType = null, IReuse reuse = null) - { - return new RequestInfo(serviceInfo, factoryID, factoryType, implementationType, reuse, this); - } - - /// Produces new request info with specified service info. - /// Gets new service info from the old one. New request info. - public RequestInfo With(Func getServiceInfo) - { - var newServiceInfo = getServiceInfo(ServiceInfo); - return new RequestInfo(newServiceInfo, FactoryID, FactoryType, ImplementationType, Reuse, ParentOrWrapper); - } - - /// Produces new info adding the implementation (factory) details to the current info. - /// - /// - /// New request info. - public RequestInfo With(int factoryID, FactoryType factoryType, Type implementationType, IReuse reuse) + int factoryID = 0, FactoryType factoryType = FactoryType.Service, Type implementationType = null, IReuse reuse = null, + RequestFlags flags = default(RequestFlags)) { - return new RequestInfo(ServiceInfo, factoryID, factoryType, implementationType, reuse, ParentOrWrapper); + return new RequestInfo(serviceInfo, factoryID, factoryType, implementationType, reuse, flags, this); } /// Returns all request until the root - parent is null. @@ -8415,6 +9455,9 @@ public StringBuilder PrintCurrent(StringBuilder s) if (IsEmpty) return s.Append("{empty}"); + if (Reuse != null && Reuse != DryIoc.Reuse.Transient) + s.Append(Reuse is SingletonReuse ? "singleton" : "scoped").Append(' '); + if (FactoryType != FactoryType.Service) s.Append(FactoryType.ToString().ToLower()).Append(' '); @@ -8452,7 +9495,7 @@ public override bool Equals(object obj) /// public bool Equals(RequestInfo other) { - return other != null && EqualsWithoutParent(other) + return other != null && EqualsWithoutParent(other) && (ParentOrWrapper == null && other.ParentOrWrapper == null || (ParentOrWrapper != null && ParentOrWrapper.EqualsWithoutParent(other.ParentOrWrapper))); } @@ -8472,6 +9515,22 @@ public bool EqualsWithoutParent(RequestInfo other) && other.ImplementationType == ImplementationType && other.ReuseLifespan == ReuseLifespan; } + + /// Compares info's regarding properties but not their parents. + /// Info to compare for equality. + public bool EqualsWithoutParent(Request other) + { + return other.ServiceType == ServiceType + && other.RequiredServiceType == RequiredServiceType + && other.IfUnresolved == IfUnresolved + && Equals(other.ServiceKey, ServiceKey) + && other.MetadataKey == MetadataKey + && Equals(other.Metadata, Metadata) + + && other.FactoryType == FactoryType + && other.ImplementationType == ImplementationType + && other.ReuseLifespan == ReuseLifespan; + } /// Returns hash code combined from info fields plus its parent. /// Combined hash code. @@ -8506,6 +9565,7 @@ public override int GetHashCode() private RequestInfo(IServiceInfo serviceInfo, int factoryID, FactoryType factoryType, Type implementationType, IReuse reuse, + RequestFlags flags, RequestInfo parentOrWrapper) { ParentOrWrapper = parentOrWrapper; @@ -8517,6 +9577,8 @@ public override int GetHashCode() FactoryType = factoryType; ImplementationType = implementationType; Reuse = reuse; + + Flags = flags; } // Inspired by System.Tuple.CombineHashCodes @@ -8534,17 +9596,19 @@ private static int CombineHashCodes(int h1, int h2) /// Resolve default and keyed is separated because of micro optimization for faster resolution. public interface IResolver { + // todo: v3: replace bool @ifUnresolvedReturnDefault with enum type /// Resolves default (non-keyed) service from container and returns created service object. /// Service type to search and to return. /// Says what to do if service is unresolved. /// Created service object or default based on provided. object Resolve(Type serviceType, bool ifUnresolvedReturnDefault); + // todo: v3: remove @scope parameter /// Resolves service from container and returns created service object. /// Service type to search and to return. /// Optional service key used for registering service. /// Says what to do if service is unresolved. - /// Actual registered service type to use instead of , + /// Actual registered service type to use instead of , /// or wrapped type for generic wrappers. The type should be assignable to return . /// Dependency resolution path info. /// Propagated resolution scope. @@ -8553,9 +9617,10 @@ public interface IResolver /// This method covers all possible resolution input parameters comparing to , and /// by specifying the same parameters as for should return the same result. /// - object Resolve(Type serviceType, object serviceKey, bool ifUnresolvedReturnDefault, Type requiredServiceType, + object Resolve(Type serviceType, object serviceKey, bool ifUnresolvedReturnDefault, Type requiredServiceType, RequestInfo preResolveParent, IScope scope); + // todo: v3: remove unused @compositeParentKey and @compositeParentRequiredType /// Resolves all services registered for specified , or if not found returns /// empty enumerable. If specified then returns only (single) service registered with /// this type. Excludes for result composite parent identified by . @@ -8567,8 +9632,8 @@ public interface IResolver /// Dependency resolution path info. /// propagated resolution scope, may be null. /// Enumerable of found services or empty. Does Not throw if no service found. - IEnumerable ResolveMany(Type serviceType, object serviceKey, Type requiredServiceType, - object compositeParentKey, Type compositeParentRequiredType, + IEnumerable ResolveMany(Type serviceType, object serviceKey, Type requiredServiceType, + object compositeParentKey, Type compositeParentRequiredType, RequestInfo preResolveParent, IScope scope); } @@ -8583,7 +9648,7 @@ public enum IfAlreadyRegistered Keep, /// Replaces old registration with new one. Replace, - /// Adds new implementation or null (Made.Of), + /// Adds new implementation or null (Made.Of), /// skips registration if the implementation is already registered. AppendNewImplementation } @@ -8639,20 +9704,21 @@ public interface IRegistrator IEnumerable GetServiceRegistrations(); /// 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 + /// Returns true if factory was added to registry, false otherwise. False may be in case of /// setting and already existing factory /// To register. /// Service type as unique key in registry for lookup. /// Service key as complementary lookup for the same service type. /// Policy how to deal with already registered factory with same service type and key. /// Confirms that service and implementation types are statically checked by compiler. - /// True if factory was added to registry, false otherwise. + /// True if factory was added to registry, false otherwise. /// False may be in case of setting and already existing factory. void Register(Factory factory, Type serviceType, object serviceKey, IfAlreadyRegistered ifAlreadyRegistered, bool isStaticallyChecked); /// Returns true if expected factory is registered with specified service key and type. /// Type to lookup. - /// Key to lookup for the same type. + /// (optional) Identifies registration via service key. + /// Not provided or null service key means to check the alone with any service key. /// Expected factory type. /// Expected factory condition. /// True if expected factory found in registry. @@ -8669,13 +9735,10 @@ public interface IRegistrator /// Provides access to scopes. public interface IScopeAccess { - /// Scope containing container singletons. - IScope SingletonScope { get; } - /// Current scope. IScope GetCurrentScope(); - /// Gets current scope matching the . + /// Gets current scope matching the . /// If name is null then current scope is returned, or if there is no current scope then exception thrown. /// May be null Found scope or throws exception. /// Says to throw if no scope found. @@ -8683,21 +9746,21 @@ public interface IScopeAccess /// Check if scope is not null, then just returns it, otherwise will create and return it. /// May be null scope. - /// Marking scope with resolved service type. + /// Marking scope with resolved service type. /// Marking scope with resolved service key. /// Input ensuring it is not null. IScope GetOrCreateResolutionScope(ref IScope scope, Type serviceType, object serviceKey); /// Check if scope is not null, then just returns it, otherwise will create and return it. /// May be null scope. - /// Marking scope with resolved service type. + /// Marking scope with resolved service type. /// Marking scope with resolved service key. /// Input ensuring it is not null. IScope GetOrNewResolutionScope(IScope scope, Type serviceType, object serviceKey); - /// If both and are null, + /// If both and are null, /// then returns input . - /// Otherwise searches scope hierarchy to find first scope with: Type assignable and + /// Otherwise searches scope hierarchy to find first scope with: Type assignable and /// Key equal to . /// Scope to start matching with Type and Key specified. /// Type to match. Key to match. @@ -8706,7 +9769,7 @@ public interface IScopeAccess IScope GetMatchingResolutionScope(IScope scope, Type assignableFromServiceType, object serviceKey, bool outermost, bool throwIfNotFound); } - /// Exposes operations required for internal registry access. + /// Exposes operations required for internal registry access. /// That's why most of them are implemented explicitly by . public interface IContainer : IRegistrator, IResolver, IDisposable { @@ -8723,7 +9786,7 @@ public interface IContainer : IRegistrator, IResolver, IDisposable object[] ResolutionStateCache { get; } /// Copies all of container state except Cache and specifies new rules. - /// (optional) Configure rules, if not specified then uses Rules from current container. + /// (optional) Configure rules, if not specified then uses Rules from current container. /// (optional) New scope context, if not specified then uses context from current container. /// New container. IContainer With(Func configure = null, IScopeContext scopeContext = null); @@ -8757,7 +9820,7 @@ public interface IContainer : IRegistrator, IResolver, IDisposable /// In case of previous open scope, new open scope references old one as a parent. /// /// (optional) Name for opened scope to allow reuse to identify the scope. - /// (optional) Configure rules, if not specified then uses Rules from current container. + /// (optional) Configure rules, if not specified then uses Rules from current container. /// New container with different current scope. /// configure = null); /// Creates container (facade) that fallbacks to this container for unresolved services. - /// Facade shares rules with this container, everything else is its own. + /// Facade shares rules with this container, everything else is its own. /// It could be used for instance to create Test facade over original container with replacing some services with test ones. /// Singletons from container are not reused by facade, to achieve that rather use with . /// New facade container. @@ -8801,7 +9864,7 @@ public interface IContainer : IRegistrator, IResolver, IDisposable /// Returns all decorators registered for the service type. Decorator factories. Factory[] GetDecoratorFactoriesOrDefault(Type serviceType); - /// Creates decorator expression: it could be either Func{TService,TService}, + /// Creates decorator expression: it could be either Func{TService,TService}, /// or service expression for replacing decorators. /// Decorated service request. /// Decorator expression. @@ -8811,7 +9874,7 @@ public interface IContainer : IRegistrator, IResolver, IDisposable /// Service instance with properties to resolve and initialize. /// (optional) Function to select properties and fields, overrides all other rules if specified. /// Instance with assigned properties and fields. - /// Different Rules could be combined together using method. + /// Different Rules could be combined together using method. object InjectPropertiesAndFields(object instance, PropertiesAndFieldsSelector propertiesAndFields); /// If is generic type then this method checks if the type registered as generic wrapper, @@ -8831,20 +9894,20 @@ public interface IContainer : IRegistrator, IResolver, IDisposable /// Factory ID to lookup by. Found expression or null. Expression GetCachedFactoryExpressionOrDefault(int factoryID); - /// If possible wraps added item in (possible for primitive type, Type, strings), - /// otherwise invokes and wraps access to added item (by returned index) into expression: state => state.Get(index). - /// Item to wrap or to add. (optional) Specific type of item, otherwise item . - /// (optional) Enable filtering of stateful items. + /// Converts known items into custom expression or wraps in . + /// Item to convert. + /// (optional) Type of item, otherwise item . + /// (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. /// Returns constant or state access expression for added items. Expression GetOrAddStateItemExpression(object item, Type itemType = null, bool throwIfStateRequired = false); - /// Adds item if it is not already added to state, returns added or existing item index. - /// Item to find in existing items with or add if not found. - /// Index of found or added item. + // todo: v3: remove with implementation + /// Obsolete: Please don't use. Will be removed in V3. int GetOrAddStateItem(object item); } - /// Resolves all registered services of type on demand, + /// Resolves all registered services of type on demand, /// when enumerator called. If service type is not found, empty returned. /// Service type to resolve. public sealed class LazyEnumerable : IEnumerable @@ -8991,11 +10054,9 @@ public static readonly int "Registering without implementation type and without FactoryMethod to use instead."), RegisteringAbstractImplementationTypeAndNoFactoryMethod = Of( "Registering abstract implementation type {0} when it is should be concrete. Also there is not FactoryMethod to use instead."), - NoPublicConstructorDefined = Of( - "There is no public constructor defined for {0}."), NoDefinedMethodToSelectFromMultipleConstructors = Of( - "Unspecified how to select single constructor from type {0} with multiple public constructors:" + - Environment.NewLine + + "Unspecified how to select single constructor from type {0} with multiple public constructors:" + + Environment.NewLine + "{1}"), NoMatchedImplementedTypesWithServiceType = Of( "Unable to match service with open-generic {0} implementing {1} when resolving {2}."), @@ -9007,13 +10068,9 @@ public static readonly int "[Specific to this .NET version] Unable to match method or constructor {0} from open-generic declaring type {1} to closed-generic type {2}, " + Environment.NewLine + "Please give the method an unique name to distinguish it from other overloads."), - CtorIsMissingSomeParameters = Of( - "Constructor [{0}] of {1} misses some arguments required for {2} dependency."), UnableToSelectConstructor = Of( "Unable to select single constructor from {0} available in {1}." + Environment.NewLine + "Please provide constructor selector when registering service."), - ExpectedFuncWithMultipleArgs = Of( - "Expecting Func with one or more arguments but found {0}."), ResolvingOpenGenericServiceTypeIsNotPossible = Of( "Resolving open-generic service type is not possible for type: {0}."), RecursiveDependencyDetected = Of( @@ -9023,7 +10080,7 @@ public static readonly int NotFoundOpenGenericImplTypeArgInService = Of( "Unable to find for open-generic implementation {0} the type argument {1} when resolving {2}."), UnableToGetConstructorFromSelector = Of( - "Unable to get constructor of {0} using provided constructor selector."), + "Unable to get constructor of {0} using provided constructor selector when resolving {1}."), UnableToFindCtorWithAllResolvableArgs = Of( "Unable to find constructor with all resolvable parameters when resolving {0}."), UnableToFindMatchingCtorForFuncWithArgs = Of( @@ -9035,8 +10092,6 @@ public static readonly int "Unable to find writable property or field \"{0}\" when resolving: {1}."), PushingToRequestWithoutFactory = Of( "Pushing next info {0} to request not yet resolved to factory: {1}"), - TargetWasAlreadyDisposed = Of( - "Target {0} was already disposed in {1} wrapper."), NoMatchedGenericParamConstraints = Of( "Open-generic service does not match with registered open-generic implementation constraints {0} when resolving: {1}."), GenericWrapperWithMultipleTypeArgsShouldSpecifyArgIndex = Of( @@ -9050,20 +10105,14 @@ public static readonly int "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}."), - ServiceIsNotAssignableFromOpenGenericRequiredServiceType = Of( - "Service of {0} is not assignable from open-generic required service type {1} when resolving: {2}."), FactoryObjIsNullInFactoryMethod = Of( "Unable to use null factory object with *instance* factory method {0} when resolving: {1}."), FactoryObjProvidedButMethodIsStatic = Of( "Factory instance provided {0} But factory method is static {1} when resolving: {2}."), GotNullConstructorFromFactoryMethod = Of( "Got null constructor when resolving {0}"), - NoOpenThreadScope = Of( - "Unable to find open thread scope in {0}. Please OpenScope with {0} to make sure thread reuse work."), ContainerIsGarbageCollected = Of( "Container is no longer available (has been garbage-collected)."), - UnableToResolveDecorator = Of( - "Unable to resolve decorator {0}."), UnableToRegisterDuplicateDefault = Of( "Service {0} without key is already registered as {1}."), UnableToRegisterDuplicateKey = Of( @@ -9072,15 +10121,13 @@ public static readonly int NoCurrentScope = Of( "No current scope available: probably you are registering to, or resolving from outside of scope."), ContainerIsDisposed = Of( - "Container is disposed and its operations are no longer available."), + "Container is disposed and cannot be used anymore."), NotDirectScopeParent = Of( "Unable to OpenScope [{0}] because parent scope [{1}] is not current context scope [{2}]." + Environment.NewLine + "It is probably other scope was opened in between OR you forgot to Dispose some other scope!"), - WrappedNotAssignableFromRequiredType = Of( - "Service (wrapped) type {0} is not assignable from required service type {1} when resolving {2}."), NoMatchedScopeFound = Of( - "Unable to find scope with matching name: {0}."), + "Unable to find scope starting from current opened {0} with name: {1}."), NoMatchingScopeWhenRegisteringInstance = Of( "No matching scope when registering instance [{0}] with {1}." + Environment.NewLine + "You could register delegate returning instance instead. That will succeed as long as scope is available at resolution."), @@ -9097,9 +10144,9 @@ public static readonly int InjectedCustomValueIsOfDifferentType = Of( "Injected value {0} is not assignable to {2}."), StateIsRequiredToUseItem = Of( - "Runtime state is required to inject (or use) the value {0}. " + Environment.NewLine + - "The reason is using RegisterDelegate, RegisterInstance, RegisterInitializer/Disposer, or registering with non-primitive service key, or metadata." + Environment.NewLine + - "To allow the value you can use container.With(rules => rules.WithItemToExpressionConverter(YOUR_ITEM_TO_EXPRESSION_CONVERTER))."), + "Runtime state is required to inject (or use) the: {0}. " + Environment.NewLine + + "The reason is using RegisterDelegate, UseInstance, RegisterInitializer/Disposer, or registering with non-primitive service key, or metadata." + Environment.NewLine + + "You can convert run-time value to expression via container.With(rules => rules.WithItemToExpressionConverter(YOUR_ITEM_TO_EXPRESSION_CONVERTER))."), ArgOfValueIsProvidedButNoArgValues = Of( "Arg.OfValue index is provided but no arg values specified."), ArgOfValueIndesIsOutOfProvidedArgValues = Of( @@ -9110,9 +10157,6 @@ public static readonly int "When registering mapping unable to find factory of registered service type {0} and key {1}."), RegisteringInstanceNotAssignableToServiceType = Of( "Registered instance {0} is not assignable to serviceType {1}."), - RegisteredInstanceIsNotAvailableInCurrentContext = Of( - "Registered instance of {0} is not available in a given context." + Environment.NewLine + - "It may mean that instance is requested from fallback container which is not supported at the moment."), RegisteringWithNotSupportedDepedendencyCustomValueType = Of( "Registering {0} dependency with not supported custom value type {1}." + Environment.NewLine + "Only DryIoc.DefaultValue, System.Type, .NET primitives types, or array of those are supported."), @@ -9120,8 +10164,8 @@ public static readonly int "Container does not allow further registrations." + Environment.NewLine + "Attempting to register {0}{1} with implementation factory {2}."), NoMoreUnregistrationsAllowed = Of( - "Container does not allow further (un)registrations." + Environment.NewLine + - "Attempting to unregister {0}{1} with factory type {2}."), + "Container does not allow further registry modification." + Environment.NewLine + + "Attempting to Unregister {0}{1} with factory type {2}."), GotNullFactoryWhenResolvingService = Of( "Got null factory method when resolving {0}"), RegisteredDisposableTransientWontBeDisposedByContainer = Of( @@ -9136,7 +10180,11 @@ public static readonly int " Lazy boundaries: {0}"), NotPossibleToResolveLazyEnumerableInsideFuncWithArgs = Of( "Unable to resolve LazyEnumerable service inside Func because arguments can't be passed through" + - " lazy boundaries: {0}"); + " lazy boundaries: {0}"), + UnableToUseInstanceForExistingNonInstanceFactory = Of( + "Unable to use the keyed instance {0} because of existing non-instance keyed registration: {1}"), + NotFoundMetaCtorWithTwoArgs = Of( + "Expecting Meta wrapper public constructor with two args {0} but not found when resolving: {1}"); #pragma warning restore 1591 // "Missing XML-comment" @@ -9267,7 +10315,7 @@ public static T ThrowIfNotOf(this T arg0, Type arg1, int error = -1, object a } /// Throws if is not assignable from . - /// + /// /// Error code /// /// if no exception. @@ -9310,7 +10358,7 @@ public static object It(int error, object arg0 = null, object arg1 = null, objec throw GetMatchedException(ErrorCheck.Unspecified, error, arg0, arg1, arg2, arg3, null); } - /// Throws instead of returning value of . + /// Throws instead of returning value of . /// Supposed to be used in expression that require some return value. /// /// @@ -9350,7 +10398,7 @@ public enum AsImplementedType } /// Returns all interfaces and all base types (in that order) implemented by . - /// Specify to include itself as first item and + /// Specify to include itself as first item and /// type as the last item. /// Source type for discovery. /// Additional types to include into result collection. @@ -9406,7 +10454,7 @@ public static Type[] GetImplementedInterfaces(this Type type) } /// Gets all declared and base members. - /// Type to get members from. + /// Type to get members from. /// (optional) When set looks into base members. /// All members. public static IEnumerable GetAllMembers(this Type type, bool includeBase = false) @@ -9418,17 +10466,17 @@ public static IEnumerable GetAllMembers(this Type type, bool include includeBase); } - /// Returns true if contains all generic parameters + /// Returns true if contains all generic parameters /// from . - /// Expected to be open-generic type. + /// Expected to be open-generic type, throws otherwise. /// Generic parameters. - /// Returns true if contains and false otherwise. + /// Returns true if contains, and false otherwise. public static bool ContainsAllGenericTypeParameters(this Type openGenericType, Type[] genericParameters) { if (!openGenericType.IsOpenGeneric()) return false; - // NOTE: may be replaced with more lightweight Bits flags. + // todo: may be replaced with more lightweight Bits flags. var matchedParams = new Type[genericParameters.Length]; Array.Copy(genericParameters, matchedParams, genericParameters.Length); @@ -9512,7 +10560,7 @@ public static Type GetArrayElementTypeOrNull(this Type type) return typeInfo.IsArray ? typeInfo.GetElementType() : null; } - /// Return base type or null, if not exist (the case for only for object type). + /// Return base type or null, if not exist (the case for only for object type). /// Source type. Base type or null for object. public static Type GetBaseType(this Type type) { @@ -9571,7 +10619,7 @@ public static bool IsEnum(this Type type) } /// Returns true if instance of type is assignable to instance of type. - /// Type to check, could be null. + /// Type to check, could be null. /// Other type to check, could be null. /// Check result. public static bool IsAssignableTo(this Type type, Type other) @@ -9589,7 +10637,7 @@ public static bool IsTypeOf(this Type type, object obj) /// Returns true if provided type IsPitmitive in .Net terms, or enum, or string /// , or array of primitives if is true. - /// Type to check. + /// Type to check. /// Says to return true for array or primitives recursively. /// Check result. public static bool IsPrimitive(this Type type, bool orArrayOfPrimitives = false) @@ -9618,8 +10666,8 @@ public static Attribute[] GetAttributes(this Type type, Type attributeType = nul /// Input type. Get declared type details. /// (optional) When set looks into base members. /// Enumerated details info objects. - public static IEnumerable GetMembers(this Type type, - Func> getMembers, + public static IEnumerable GetMembers(this Type type, + Func> getMembers, bool includeBase = false) { var typeInfo = type.GetTypeInfo(); @@ -9627,16 +10675,13 @@ public static Attribute[] GetAttributes(this Type type, Type attributeType = nul if (!includeBase) return members; var baseType = typeInfo.BaseType; - return baseType == null || baseType == typeof(object) ? members - : members.Concat(baseType.GetMembers(getMembers)); + return baseType == null || baseType == typeof(object) + ? members + : members.Concat(baseType.GetMembers(getMembers, true)); } - // todo: V3: Obsolete: Replace with GetMembers. - /// Recursive method to enumerate all input type and its base types for specific details. - /// Details are returned by delegate. - /// Details type: properties, fields, methods, etc. - /// Input type. Get declared type details. - /// Enumerated details info objects. + // todo: V3: remove. + /// Obsolete: replaced with . public static IEnumerable GetDeclaredAndBase(this Type type, Func> getDeclared) { var typeInfo = type.GetTypeInfo(); @@ -9646,7 +10691,7 @@ public static IEnumerable GetDeclaredAndBase(this Type type, FuncReturns all public instance constructors for the type + /// Returns all public instance constructors for the type /// public static IEnumerable GetPublicInstanceConstructors(this Type type) { @@ -9702,11 +10747,40 @@ public static MethodInfo GetSingleMethodOrNull(this Type type, string name, bool /// Returns declared (not inherited) method by name and argument types, or null if not found. /// Input type Method name to look for. - /// Argument types Found method or null. - public static MethodInfo GetMethodOrNull(this Type type, string name, params Type[] args) + /// Argument types Found method or null. + public static MethodInfo GetMethodOrNull(this Type type, string name, params Type[] paramTypes) { - return type.GetTypeInfo().DeclaredMethods.FirstOrDefault(m => - m.Name == name && args.SequenceEqual(m.GetParameters().Select(p => p.ParameterType))); + var typeInfo = type.GetTypeInfo(); + var paramCount = paramTypes.Length; + foreach (var method in typeInfo.DeclaredMethods) + { + if (method.Name == name) + { + var methodParams = method.GetParameters(); + if (paramCount == methodParams.Length) + { + if (paramCount == 0) + return method; + + if (paramCount == 1) + { + if (paramTypes[0] == methodParams[0].ParameterType) + return method; + } + else + { + var i = 0; + for (; i < paramCount; ++i) + if (paramTypes[i] != methodParams[i].ParameterType) + break; + if (i == paramCount) + return method; + } + } + } + } + + return null; } /// Returns property by name, including inherited. Or null if not found. @@ -9714,7 +10788,7 @@ public static MethodInfo GetMethodOrNull(this Type type, string name, params Typ /// Found property or null. public static PropertyInfo GetPropertyOrNull(this Type type, string name) { - return type.GetDeclaredAndBase(_ => _.DeclaredProperties).FirstOrDefault(p => p.Name == name); + return type.GetMembers(_ => _.DeclaredProperties, includeBase: true).FirstOrDefault(p => p.Name == name); } /// Returns field by name, including inherited. Or null if not found. @@ -9722,7 +10796,7 @@ public static PropertyInfo GetPropertyOrNull(this Type type, string name) /// Found field or null. public static FieldInfo GetFieldOrNull(this Type type, string name) { - return type.GetDeclaredAndBase(_ => _.DeclaredFields).FirstOrDefault(p => p.Name == name); + return type.GetMembers(_ => _.DeclaredFields, includeBase: true).FirstOrDefault(p => p.Name == name); } /// Returns type assembly. Input type Type assembly. @@ -9784,7 +10858,7 @@ public static IEnumerable GetAttributes(this MemberInfo member, Type } /// Returns attributes defined for parameter. - /// Target parameter. + /// Target parameter. /// (optional) Specific attribute type to return, any attribute otherwise. /// Check for inherited attributes. Found attributes or empty. public static IEnumerable GetAttributes(this ParameterInfo parameter, Type attributeType = null, bool inherit = false) @@ -9792,7 +10866,7 @@ public static IEnumerable GetAttributes(this ParameterInfo parameter, return parameter.GetCustomAttributes(attributeType ?? typeof(Attribute), inherit).Cast(); } - /// Get types from assembly that are loaded successfully. + /// Get types from assembly that are loaded successfully. /// Hacks to for loaded types. /// Assembly to get types from. /// Array of loaded types. @@ -9813,15 +10887,7 @@ public static Type[] GetLoadedTypes(this Assembly assembly) /// Default value expression. public static Expression GetDefaultValueExpression(this Type type) { - return Expression.Call(_getDefaultMethod.MakeGenericMethod(type), ArrayTools.Empty()); - } - - /// Utility to convert passed func/delegate to expression. - /// Type of expression result. Delegate to convert. - /// Delegate expression. - public static Expression ToExpression(Expression> func) - { - return func.Body; + return Expression.Call(_getDefaultMethod.Value.MakeGenericMethod(type), ArrayTools.Empty()); } #region Implementation @@ -9872,8 +10938,9 @@ private static void SetToNullMatchesFoundInGenericParameters(Type[] matchedParam } } - private static readonly MethodInfo _getDefaultMethod = - typeof(ReflectionTools).GetMethodOrNull("GetDefault", ArrayTools.Empty()); + private static readonly Lazy _getDefaultMethod = new Lazy(() => + typeof(ReflectionTools).GetMethodOrNull("GetDefault", ArrayTools.Empty())); + internal static T GetDefault() { return default(T); } #endregion @@ -9883,7 +10950,7 @@ private static void SetToNullMatchesFoundInGenericParameters(Type[] matchedParam public static class PrintTools { /// Default separator used for printing enumerable. - public readonly static string DefaultItemSeparator = ";" + Environment.NewLine; + public static string DefaultItemSeparator = ";" + Environment.NewLine; /// Prints input object by using corresponding Print methods for know types. /// Builder to append output to. @@ -9933,7 +11000,7 @@ public static StringBuilder Print(this StringBuilder s, string str, string quote /// Default delegate to print Type details: by default prints Type FullName and /// skips namespace if it start with "System." - public static readonly Func GetTypeNameDefault = t => + public static Func GetTypeNameDefault = t => t.FullName != null && t.Namespace != null && !t.Namespace.StartsWith("System") ? t.FullName : t.Name; /// Appends type details to string builder. @@ -9983,12 +11050,31 @@ public static partial class Portable { var assemblyParamExpr = Expression.Parameter(typeof(Assembly), "a"); + Expression typesExpr; + var definedTypeInfosProperty = typeof(Assembly).GetPropertyOrNull("DefinedTypes"); - var getTypesExpr = definedTypeInfosProperty != null - ? (Expression)Expression.Property(assemblyParamExpr, definedTypeInfosProperty) - : Expression.Call(assemblyParamExpr, "GetTypes", ArrayTools.Empty(), ArrayTools.Empty()); + if (definedTypeInfosProperty == null) + { + typesExpr = Expression.Call(assemblyParamExpr, "GetTypes", ArrayTools.Empty(), + ArrayTools.Empty()); + } + else + { + typesExpr = Expression.Property(assemblyParamExpr, definedTypeInfosProperty); + if (typesExpr.Type == typeof(IEnumerable)) + { + var typeInfoParamExpr = Expression.Parameter(typeof(TypeInfo), "typeInfo"); + typesExpr = Expression.Call(typeof(Enumerable), + "Select", new[] { typeof(TypeInfo), typeof(Type) }, + typesExpr, + Expression.Lambda>( + Expression.Call(typeInfoParamExpr, "AsType", + ArrayTools.Empty(), ArrayTools.Empty()), + typeInfoParamExpr)); + } + } - var resultFunc = Expression.Lambda>>(getTypesExpr, assemblyParamExpr); + var resultFunc = Expression.Lambda>>(typesExpr, assemblyParamExpr); return resultFunc.Compile(); } @@ -10010,6 +11096,17 @@ public static MethodInfo GetSetMethodOrNull(this PropertyInfo p, bool includeNon return p.DeclaringType.GetSingleMethodOrNull("set_" + p.Name, includeNonPublic); } + private static readonly Lazy> _getEnvCurrentManagedThreadId = new Lazy>(() => + { + var method = typeof(Environment).GetMethodOrNull("get_CurrentManagedThreadId", ArrayTools.Empty()); + if (method == null) + return null; + + return Expression.Lambda>( + Expression.Call(method, ArrayTools.Empty()), + ArrayTools.Empty()).Compile(); + }); + /// Returns managed Thread ID either from Environment or Thread.CurrentThread whichever is available. /// Managed Thread ID. public static int GetCurrentManagedThreadID() @@ -10017,20 +11114,11 @@ public static int GetCurrentManagedThreadID() var resultID = -1; GetCurrentManagedThreadID(ref resultID); if (resultID == -1) - resultID = _getEnvCurrentManagedThreadId(); + resultID = _getEnvCurrentManagedThreadId.Value(); return resultID; } static partial void GetCurrentManagedThreadID(ref int threadID); - - private static readonly MethodInfo _getEnvCurrentManagedThreadIdMethod = - typeof(Environment).GetMethodOrNull("get_CurrentManagedThreadId", ArrayTools.Empty()); - - private static readonly Func _getEnvCurrentManagedThreadId = - _getEnvCurrentManagedThreadIdMethod == null ? null : - Expression.Lambda>( - Expression.Call(_getEnvCurrentManagedThreadIdMethod, ArrayTools.Empty()), - ArrayTools.Empty()).Compile(); } } @@ -10038,6 +11126,7 @@ namespace DryIoc.Experimental { using System; using System.Reflection; + using ImTools; /// Succinct convention-based, LINQ like API to resolve resolution root at the end. public static class DI diff --git a/Src/Commons.InversionOfControl.DryIoC/DryIoc/FactoryCompiler.cs b/Src/Commons.InversionOfControl.DryIoC/DryIoc/FactoryCompiler.cs deleted file mode 100644 index 0381f1f..0000000 --- a/Src/Commons.InversionOfControl.DryIoC/DryIoc/FactoryCompiler.cs +++ /dev/null @@ -1,296 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq.Expressions; -using System.Reflection; -using System.Reflection.Emit; - -namespace DryIoc -{ - public static partial class FactoryCompiler - { - static partial void CompileToDelegate(Expression expression, ref FactoryDelegate result) - { - var method = new DynamicMethod(string.Empty, - typeof(object), _factoryDelegateArgTypes, - typeof(Container).Module, skipVisibility: true); - - var il = method.GetILGenerator(); - - var emitted = EmittingVisitor.TryVisit(expression, il); - if (emitted) - { - il.Emit(OpCodes.Ret); - result = (FactoryDelegate)method.CreateDelegate(typeof(FactoryDelegate)); - } - } - - private static readonly Type[] _factoryDelegateArgTypes = { typeof(object[]), typeof(IResolverContext), typeof(IScope) }; - - /// Supports emitting of selected expressions, e.g. lambda are not supported yet. - /// When emitter find not supported expression it will return false from , so I could fallback - /// to normal and slow Expression.Compile. - private static class EmittingVisitor - { - public static bool TryVisit(Expression expr, ILGenerator il) - { - switch (expr.NodeType) - { - case ExpressionType.Convert: - return VisitConvert((UnaryExpression)expr, il); - case ExpressionType.ArrayIndex: - return VisitArrayIndex((BinaryExpression)expr, il); - case ExpressionType.Constant: - return VisitConstant((ConstantExpression)expr, il); - case ExpressionType.Parameter: - return VisitFactoryDelegateParameters(expr, il); - case ExpressionType.New: - return VisitNew((NewExpression)expr, il); - case ExpressionType.NewArrayInit: - return VisitNewArray((NewArrayExpression)expr, il); - case ExpressionType.MemberInit: - return VisitMemberInit((MemberInitExpression)expr, il); - case ExpressionType.Call: - return VisitMethodCall((MethodCallExpression)expr, il); - case ExpressionType.MemberAccess: - return VisitMemberAccess((MemberExpression)expr, il); - default: - // Not supported yet: nested lambdas (Invoke) - return false; - } - } - - private static bool VisitFactoryDelegateParameters(Expression expr, ILGenerator il) - { - var paramExpr = (ParameterExpression)expr; - if (paramExpr == Container.StateParamExpr) - il.Emit(OpCodes.Ldarg_0); - else if (paramExpr == Container.ResolverContextParamExpr) - il.Emit(OpCodes.Ldarg_1); - else if (paramExpr == Container.ResolutionScopeParamExpr) - il.Emit(OpCodes.Ldarg_2); - return true; - } - - private static bool VisitBinary(BinaryExpression b, ILGenerator il) - { - var ok = TryVisit(b.Left, il); - if (ok) - ok = TryVisit(b.Right, il); - // skips TryVisit(b.Conversion) for NodeType.Coalesce (?? operation) - return ok; - } - - private static bool VisitExpressionList(IList eList, ILGenerator state) - { - var ok = true; - for (int i = 0, n = eList.Count; i < n && ok; i++) - ok = TryVisit(eList[i], state); - return ok; - } - - private static bool VisitConvert(UnaryExpression node, ILGenerator il) - { - var ok = TryVisit(node.Operand, il); - if (ok) - { - var convertTargetType = node.Type; - if (convertTargetType == typeof(object)) // not supported, probably required for converting ValueType - return false; - il.Emit(OpCodes.Castclass, convertTargetType); - } - return ok; - } - - private static bool VisitConstant(ConstantExpression node, ILGenerator il) - { - var value = node.Value; - if (value == null) - il.Emit(OpCodes.Ldnull); - else if (value is int || value.GetType().IsEnum()) - EmitLoadConstantInt(il, (int)value); - else if (value is string) - il.Emit(OpCodes.Ldstr, (string)value); - else - return false; - return true; - } - - private static bool VisitNew(NewExpression node, ILGenerator il) - { - var ok = VisitExpressionList(node.Arguments, il); - if (ok) - il.Emit(OpCodes.Newobj, node.Constructor); - return ok; - } - - private static bool VisitNewArray(NewArrayExpression node, ILGenerator il) - { - var elems = node.Expressions; - var arrType = node.Type; - var elemType = arrType.GetArrayElementTypeOrNull(); - var isElemOfValueType = elemType.IsValueType(); - - var arrVar = il.DeclareLocal(arrType); - - EmitLoadConstantInt(il, elems.Count); - il.Emit(OpCodes.Newarr, elemType); - il.Emit(OpCodes.Stloc, arrVar); - - var ok = true; - for (int i = 0, n = elems.Count; i < n && ok; i++) - { - il.Emit(OpCodes.Ldloc, arrVar); - EmitLoadConstantInt(il, i); - - // loading element address for later copying of value into it. - if (isElemOfValueType) - il.Emit(OpCodes.Ldelema, elemType); - - ok = TryVisit(elems[i], il); - if (ok) - { - if (isElemOfValueType) - il.Emit(OpCodes.Stobj, elemType); // store element of value type by array element address - else - il.Emit(OpCodes.Stelem_Ref); - } - } - - il.Emit(OpCodes.Ldloc, arrVar); - return ok; - } - - private static bool VisitArrayIndex(BinaryExpression node, ILGenerator il) - { - var ok = VisitBinary(node, il); - if (ok) - il.Emit(OpCodes.Ldelem_Ref); - return ok; - } - - private static bool VisitMemberInit(MemberInitExpression mi, ILGenerator il) - { - var ok = VisitNew(mi.NewExpression, il); - if (!ok) return false; - - var obj = il.DeclareLocal(mi.Type); - il.Emit(OpCodes.Stloc, obj); - - var bindings = mi.Bindings; - for (int i = 0, n = bindings.Count; i < n; i++) - { - var binding = bindings[i]; - if (binding.BindingType != MemberBindingType.Assignment) - return false; - il.Emit(OpCodes.Ldloc, obj); - - ok = TryVisit(((MemberAssignment)binding).Expression, il); - if (!ok) return false; - - var prop = binding.Member as PropertyInfo; - if (prop != null) - { - var setMethod = prop.GetSetMethodOrNull(); - if (setMethod == null) - return false; - EmitMethodCall(setMethod, il); - } - else - { - var field = binding.Member as FieldInfo; - if (field == null) - return false; - il.Emit(OpCodes.Stfld, field); - } - } - - il.Emit(OpCodes.Ldloc, obj); - return true; - } - - private static bool VisitMethodCall(MethodCallExpression expr, ILGenerator il) - { - var ok = true; - if (expr.Object != null) - ok = TryVisit(expr.Object, il); - - if (ok && expr.Arguments.Count != 0) - ok = VisitExpressionList(expr.Arguments, il); - - if (ok) - EmitMethodCall(expr.Method, il); - - return ok; - } - - private static bool VisitMemberAccess(MemberExpression expr, ILGenerator il) - { - if (expr.Expression != null) - { - var ok = TryVisit(expr.Expression, il); - if (!ok) return false; - } - - var field = expr.Member as FieldInfo; - if (field != null) - { - il.Emit(field.IsStatic() ? OpCodes.Ldsfld : OpCodes.Ldfld, field); - return true; - } - - var property = expr.Member as PropertyInfo; - if (property != null) - { - var getMethod = property.GetGetMethod(); - if (getMethod == null) - return false; - EmitMethodCall(getMethod, il); - } - - return true; - } - - private static void EmitMethodCall(MethodInfo method, ILGenerator il) - { - il.Emit(method.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, method); - } - - private static void EmitLoadConstantInt(ILGenerator il, int i) - { - switch (i) - { - case 0: - il.Emit(OpCodes.Ldc_I4_0); - break; - case 1: - il.Emit(OpCodes.Ldc_I4_1); - break; - case 2: - il.Emit(OpCodes.Ldc_I4_2); - break; - case 3: - il.Emit(OpCodes.Ldc_I4_3); - break; - case 4: - il.Emit(OpCodes.Ldc_I4_4); - break; - case 5: - il.Emit(OpCodes.Ldc_I4_5); - break; - case 6: - il.Emit(OpCodes.Ldc_I4_6); - break; - case 7: - il.Emit(OpCodes.Ldc_I4_7); - break; - case 8: - il.Emit(OpCodes.Ldc_I4_8); - break; - default: - il.Emit(OpCodes.Ldc_I4, i); - break; - } - } - } - } -} diff --git a/Src/Commons.InversionOfControl.DryIoC/DryIoc/FastExpressionCompiler.cs b/Src/Commons.InversionOfControl.DryIoC/DryIoc/FastExpressionCompiler.cs new file mode 100644 index 0000000..b4ddcce --- /dev/null +++ b/Src/Commons.InversionOfControl.DryIoC/DryIoc/FastExpressionCompiler.cs @@ -0,0 +1,891 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 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 AddOrUpdateServiceFactory +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. +*/ + +namespace DryIoc +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + using System.Reflection.Emit; + + /// Compiles expression to delegate by emitting the IL directly. + /// The emitter is ~10 times faster than Expression.Compile. + public static partial class FastExpressionCompiler + { + // ReSharper disable once RedundantAssignment + static partial void TryCompile(ref TDelegate compileDelegate, + Expression bodyExpr, + ParameterExpression[] paramExprs, + Type[] paramTypes, + Type returnType) where TDelegate : class + { + compileDelegate = TryCompile(bodyExpr, paramExprs, paramTypes, returnType); + } + + /// Compiles expression to delegate by emitting the IL. + /// If sub-expressions are not supported by emitter, then the method returns null. + /// The usage should be calling the method, if result is null then calling the Expression.Compile. + /// Lambda body. + /// Lambda parameter expressions. + /// The types of parameters. + /// The return type. + /// Result delegate or null, if unable to compile. + public static TDelegate TryCompile( + Expression bodyExpr, + ParameterExpression[] paramExprs, + Type[] paramTypes, + Type returnType) where TDelegate : class + { + var constantExprs = new List(); + + if (!TryCollectBoundConstants(bodyExpr, constantExprs)) + return null; + + object closure = null; + ClosureInfo closureInfo = null; + + DynamicMethod method; + if (constantExprs.Count == 0) + { + method = new DynamicMethod(string.Empty, returnType, paramTypes, + typeof(FastExpressionCompiler).Module, skipVisibility: true); + } + else + { + var constants = new object[constantExprs.Count]; + var constantCount = constants.Length; + + for (var i = constantCount - 1; i >= 0; i--) + constants[i] = constantExprs[i].Value; + + if (constantCount <= Closure.CreateMethods.Length) + { + var createClosureMethod = Closure.CreateMethods[constantCount - 1]; + + var constantTypes = new Type[constantCount]; + for (var i = 0; i < constantCount; i++) + constantTypes[i] = constantExprs[i].Type; + + var createClosure = createClosureMethod.MakeGenericMethod(constantTypes); + + closure = createClosure.Invoke(null, constants); + + var fields = closure.GetType().GetTypeInfo().DeclaredFields; + var fieldsArray = fields as FieldInfo[] ?? fields.ToArray(); + + closureInfo = new ClosureInfo(constantExprs, fieldsArray); + } + else + { + var arrayClosure = new ArrayClosure(constants); + closure = arrayClosure; + closureInfo = new ClosureInfo(constantExprs); + } + + var closureType = closure.GetType(); + var closureAndParamTypes = GetClosureAndParamTypes(paramTypes, closureType); + + method = new DynamicMethod(string.Empty, returnType, closureAndParamTypes, closureType, skipVisibility: true); + } + + var il = method.GetILGenerator(); + var emitted = EmittingVisitor.TryEmit(bodyExpr, paramExprs, il, closureInfo); + if (emitted) + { + il.Emit(OpCodes.Ret); + return (TDelegate)(object)method.CreateDelegate(typeof(TDelegate), closure); + } + + return null; + } + + private static Type[] GetClosureAndParamTypes(Type[] paramTypes, Type closureType) + { + var paramCount = paramTypes.Length; + var closureAndParamTypes = new Type[paramCount + 1]; + closureAndParamTypes[0] = closureType; + if (paramCount == 1) + closureAndParamTypes[1] = paramTypes[0]; + else if (paramCount > 1) + Array.Copy(paramTypes, 0, closureAndParamTypes, 1, paramCount); + return closureAndParamTypes; + } + + private sealed class ClosureInfo + { + public readonly IList ConstantExpressions; + + public readonly FieldInfo[] ConstantClosureFields; + public readonly bool IsClosureArray; + + public ClosureInfo(IList constantExpressions, FieldInfo[] constantClosureFields = null) + { + ConstantExpressions = constantExpressions; + ConstantClosureFields = constantClosureFields; + IsClosureArray = constantClosureFields == null; + } + } + + #region Closures + + // todo: perf: test if Activator.CreateInstance is faster than method Invoke. + internal static class Closure + { + public static readonly MethodInfo[] CreateMethods = + typeof(Closure).GetTypeInfo().DeclaredMethods.ToArray(); + + public static Closure CreateClosure(T1 v1) + { + return new Closure(v1); + } + + public static Closure CreateClosure(T1 v1, T2 v2) + { + return new Closure(v1, v2); + } + + public static Closure CreateClosure(T1 v1, T2 v2, T3 v3) + { + return new Closure(v1, v2, v3); + } + + public static Closure CreateClosure(T1 v1, T2 v2, T3 v3, T4 v4) + { + return new Closure(v1, v2, v3, v4); + } + + public static Closure CreateClosure(T1 v1, T2 v2, T3 v3, T4 v4, + T5 v5) + { + return new Closure(v1, v2, v3, v4, v5); + } + + public static Closure CreateClosure(T1 v1, T2 v2, T3 v3, + T4 v4, T5 v5, T6 v6) + { + return new Closure(v1, v2, v3, v4, v5, v6); + } + + public static Closure CreateClosure(T1 v1, T2 v2, + T3 v3, T4 v4, T5 v5, T6 v6, T7 v7) + { + return new Closure(v1, v2, v3, v4, v5, v6, v7); + } + + public static Closure CreateClosure( + T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8) + { + return new Closure(v1, v2, v3, v4, v5, v6, v7, v8); + } + + public static Closure CreateClosure( + T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9) + { + return new Closure(v1, v2, v3, v4, v5, v6, v7, v8, v9); + } + + public static Closure CreateClosure( + T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10) + { + return new Closure(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10); + } + } + + internal sealed class Closure + { + public T1 V1; + + public Closure(T1 v1) + { + V1 = v1; + } + } + + internal sealed class Closure + { + public T1 V1; + public T2 V2; + + public Closure(T1 v1, T2 v2) + { + V1 = v1; + V2 = v2; + } + } + + internal sealed class Closure + { + public T1 V1; + public T2 V2; + public T3 V3; + + public Closure(T1 v1, T2 v2, T3 v3) + { + V1 = v1; + V2 = v2; + V3 = v3; + } + } + + internal sealed class Closure + { + public T1 V1; + public T2 V2; + public T3 V3; + public T4 V4; + + public Closure(T1 v1, T2 v2, T3 v3, T4 v4) + { + V1 = v1; + V2 = v2; + V3 = v3; + V4 = v4; + } + } + + internal sealed class Closure + { + public T1 V1; + public T2 V2; + public T3 V3; + public T4 V4; + public T5 V5; + + public Closure(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5) + { + V1 = v1; + V2 = v2; + V3 = v3; + V4 = v4; + V5 = v5; + } + } + + internal sealed class Closure + { + public T1 V1; + public T2 V2; + public T3 V3; + public T4 V4; + public T5 V5; + public T6 V6; + + public Closure(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6) + { + V1 = v1; + V2 = v2; + V3 = v3; + V4 = v4; + V5 = v5; + V6 = v6; + } + } + + internal sealed class Closure + { + public T1 V1; + public T2 V2; + public T3 V3; + public T4 V4; + public T5 V5; + public T6 V6; + public T7 V7; + + public Closure(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7) + { + V1 = v1; + V2 = v2; + V3 = v3; + V4 = v4; + V5 = v5; + V6 = v6; + V7 = v7; + } + } + + internal sealed class Closure + { + public T1 V1; + public T2 V2; + public T3 V3; + public T4 V4; + public T5 V5; + public T6 V6; + public T7 V7; + public T8 V8; + + public Closure(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8) + { + V1 = v1; + V2 = v2; + V3 = v3; + V4 = v4; + V5 = v5; + V6 = v6; + V7 = v7; + V8 = v8; + } + } + + internal sealed class Closure + { + public T1 V1; + public T2 V2; + public T3 V3; + public T4 V4; + public T5 V5; + public T6 V6; + public T7 V7; + public T8 V8; + public T9 V9; + + public Closure(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9) + { + V1 = v1; + V2 = v2; + V3 = v3; + V4 = v4; + V5 = v5; + V6 = v6; + V7 = v7; + V8 = v8; + V9 = v9; + } + } + + internal sealed class Closure + { + public T1 V1; + public T2 V2; + public T3 V3; + public T4 V4; + public T5 V5; + public T6 V6; + public T7 V7; + public T8 V8; + public T9 V9; + public T10 V10; + + public Closure(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10) + { + V1 = v1; + V2 = v2; + V3 = v3; + V4 = v4; + V5 = v5; + V6 = v6; + V7 = v7; + V8 = v8; + V9 = v9; + V10 = v10; + } + } + + internal sealed class ArrayClosure + { + public readonly object[] Constants; + public static FieldInfo ArrayField = typeof(ArrayClosure).GetTypeInfo().DeclaredFields.First(f => !f.IsStatic); + + public ArrayClosure(object[] constants) + { + Constants = constants; + } + } + + #endregion + + #region Collect Bound Constants + + private static bool IsBoundConstant(object value) + { + return value != null && + !(value is int || value is double || value is bool || + value is string || value is Type || value.GetType().IsEnum); + } + + private static bool TryCollectBoundConstants(Expression expr, List constants) + { + if (expr == null) + return false; + + switch (expr.NodeType) + { + case ExpressionType.Constant: + var constantExpr = (ConstantExpression)expr; + var constantValue = constantExpr.Value; + if (constantValue is Delegate) // Note: skip Delegate constant as it is required complex il emitting + return false; + if (IsBoundConstant(constantValue)) + constants.Add(constantExpr); + break; + + case ExpressionType.Call: + return TryCollectBoundConstants(((MethodCallExpression)expr).Object, constants) + && TryCollectBoundConstants(((MethodCallExpression)expr).Arguments, constants); + + case ExpressionType.MemberAccess: + return TryCollectBoundConstants(((MemberExpression)expr).Expression, constants); + + case ExpressionType.New: + return TryCollectBoundConstants(((NewExpression)expr).Arguments, constants); + + case ExpressionType.NewArrayInit: + return TryCollectBoundConstants(((NewArrayExpression)expr).Expressions, constants); + + // property initializer + case ExpressionType.MemberInit: + var memberInitExpr = (MemberInitExpression)expr; + if (!TryCollectBoundConstants(memberInitExpr.NewExpression, constants)) + return false; + + var memberBindings = memberInitExpr.Bindings; + for (var i = 0; i < memberBindings.Count; ++i) + { + var memberBinding = memberBindings[i]; + if (memberBinding.BindingType == MemberBindingType.Assignment && + !TryCollectBoundConstants(((MemberAssignment)memberBinding).Expression, constants)) + return false; + } + break; + + default: + var unaryExpr = expr as UnaryExpression; + if (unaryExpr != null) + return TryCollectBoundConstants(unaryExpr.Operand, constants); + + var binaryExpr = expr as BinaryExpression; + if (binaryExpr != null) + return TryCollectBoundConstants(binaryExpr.Left, constants) + && TryCollectBoundConstants(binaryExpr.Right, constants); + break; + } + + return true; + } + + private static bool TryCollectBoundConstants(IList exprs, List constants) + { + var count = exprs.Count; + for (var i = 0; i < count; i++) + if (!TryCollectBoundConstants(exprs[i], constants)) + return false; + return true; + } + + #endregion + + /// Supports emitting of selected expressions, e.g. lambdaExpr are not supported yet. + /// When emitter find not supported expression it will return false from , so I could fallback + /// to normal and slow Expression.Compile. + private static class EmittingVisitor + { + public static bool TryEmit(Expression expr, IList paramExprs, ILGenerator il, ClosureInfo closure) + { + switch (expr.NodeType) + { + case ExpressionType.Parameter: + return EmitParameter((ParameterExpression)expr, paramExprs, il, closure); + case ExpressionType.Convert: + return EmitConvert((UnaryExpression)expr, paramExprs, il, closure); + case ExpressionType.ArrayIndex: + return EmitArrayIndex((BinaryExpression)expr, paramExprs, il, closure); + case ExpressionType.Constant: + return EmitConstant((ConstantExpression)expr, il, closure); + case ExpressionType.New: + return EmitNew((NewExpression)expr, paramExprs, il, closure); + case ExpressionType.NewArrayInit: + return EmitNewArray((NewArrayExpression)expr, paramExprs, il, closure); + case ExpressionType.MemberInit: + return EmitMemberInit((MemberInitExpression)expr, paramExprs, il, closure); + case ExpressionType.Call: + return EmitMethodCall((MethodCallExpression)expr, paramExprs, il, closure); + case ExpressionType.MemberAccess: + return EmitMemberAccess((MemberExpression)expr, paramExprs, il, closure); + case ExpressionType.GreaterThan: + case ExpressionType.GreaterThanOrEqual: + case ExpressionType.LessThan: + case ExpressionType.LessThanOrEqual: + case ExpressionType.Equal: + case ExpressionType.NotEqual: + return EmitComparison((BinaryExpression)expr, paramExprs, il, closure); + default: + // Not supported yet: nested lambdas (Invoke) + return false; + } + } + + private static bool EmitParameter(ParameterExpression p, IList ps, ILGenerator il, ClosureInfo closure) + { + var pIndex = ps.IndexOf(p); + if (pIndex == -1) + return false; + + if (closure != null) + pIndex += 1; + + switch (pIndex) + { + case 0: + il.Emit(OpCodes.Ldarg_0); + break; + case 1: + il.Emit(OpCodes.Ldarg_1); + break; + case 2: + il.Emit(OpCodes.Ldarg_2); + break; + case 3: + il.Emit(OpCodes.Ldarg_3); + break; + default: + if (pIndex <= byte.MaxValue) + il.Emit(OpCodes.Ldarg_S, (byte)pIndex); + else + il.Emit(OpCodes.Ldarg, pIndex); + break; + } + + return true; + } + + private static bool EmitBinary(BinaryExpression b, IList ps, ILGenerator il, ClosureInfo closure) + { + var ok = TryEmit(b.Left, ps, il, closure); + if (ok) + ok = TryEmit(b.Right, ps, il, closure); + // skips TryEmit(b.Conversion) for NodeType.Coalesce (?? operation) + return ok; + } + + private static bool EmitMany(IList es, IList ps, ILGenerator il, ClosureInfo closure) + { + for (int i = 0, n = es.Count; i < n; i++) + if (!TryEmit(es[i], ps, il, closure)) + return false; + return true; + } + + private static bool EmitConvert(UnaryExpression node, IList ps, ILGenerator il, ClosureInfo closure) + { + var ok = TryEmit(node.Operand, ps, il, closure); + if (ok) + { + var convertTargetType = node.Type; + if (convertTargetType == typeof(object)) + return false; + il.Emit(OpCodes.Castclass, convertTargetType); + } + return ok; + } + + private static bool EmitConstant(ConstantExpression constantExpr, ILGenerator il, ClosureInfo closure) + { + var constant = constantExpr.Value; + if (constant == null) + { + il.Emit(OpCodes.Ldnull); + } + else if (constant is int || constant.GetType().IsEnum) + { + EmitLoadConstantInt(il, (int)constant); + } + else if (constant is double) + { + il.Emit(OpCodes.Ldc_R8, (double)constant); + } + else if (constant is bool) + { + il.Emit((bool)constant ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); + } + else if (constant is string) + { + il.Emit(OpCodes.Ldstr, (string)constant); + } + else if (constant is Type) + { + il.Emit(OpCodes.Ldtoken, (Type)constant); + var getTypeFromHandle = typeof(Type).GetMethod("GetTypeFromHandle"); + il.Emit(OpCodes.Call, getTypeFromHandle); + } + else if (closure != null) + { + var constantIndex = closure.ConstantExpressions.IndexOf(constantExpr); + if (constantIndex == -1) + return false; + + // load closure argument: Closure or Closure Array + il.Emit(OpCodes.Ldarg_0); + + if (!closure.IsClosureArray) + { + // load closure field + il.Emit(OpCodes.Ldfld, closure.ConstantClosureFields[constantIndex]); + } + else + { + // load array field + il.Emit(OpCodes.Ldfld, ArrayClosure.ArrayField); + + // load array item index + EmitLoadConstantInt(il, constantIndex); + + // load item from index + il.Emit(OpCodes.Ldelem_Ref); + + // case if needed + var castType = constantExpr.Type; + if (castType != typeof(object)) + il.Emit(OpCodes.Castclass, castType); + } + } + else + { + return false; + } + + // boxing the value type, otherwise we can get a strange result when 0 is treated as Null. + if (constantExpr.Type == typeof(object) && + constant != null && constant.GetType().IsValueType()) + il.Emit(OpCodes.Box, constant.GetType()); + + return true; + } + + private static bool EmitNew(NewExpression n, IList ps, ILGenerator il, ClosureInfo closure) + { + var ok = EmitMany(n.Arguments, ps, il, closure); + if (ok) + il.Emit(OpCodes.Newobj, n.Constructor); + return ok; + } + + private static bool EmitNewArray(NewArrayExpression na, IList ps, ILGenerator il, ClosureInfo closure) + { + var elems = na.Expressions; + var arrType = na.Type; + var elemType = arrType.GetElementType(); + var isElemOfValueType = elemType.IsValueType; + + var arrVar = il.DeclareLocal(arrType); + + EmitLoadConstantInt(il, elems.Count); + il.Emit(OpCodes.Newarr, elemType); + il.Emit(OpCodes.Stloc, arrVar); + + var ok = true; + for (int i = 0, n = elems.Count; i < n && ok; i++) + { + il.Emit(OpCodes.Ldloc, arrVar); + EmitLoadConstantInt(il, i); + + // loading element address for later copying of value into it. + if (isElemOfValueType) + il.Emit(OpCodes.Ldelema, elemType); + + ok = TryEmit(elems[i], ps, il, closure); + if (ok) + { + if (isElemOfValueType) + il.Emit(OpCodes.Stobj, elemType); // store element of value type by array element address + else + il.Emit(OpCodes.Stelem_Ref); + } + } + + il.Emit(OpCodes.Ldloc, arrVar); + return ok; + } + + private static bool EmitArrayIndex(BinaryExpression ai, IList ps, ILGenerator il, ClosureInfo closure) + { + var ok = EmitBinary(ai, ps, il, closure); + if (ok) + il.Emit(OpCodes.Ldelem_Ref); + return ok; + } + + private static bool EmitMemberInit(MemberInitExpression mi, IList ps, ILGenerator il, ClosureInfo closure) + { + var ok = EmitNew(mi.NewExpression, ps, il, closure); + if (!ok) return false; + + var obj = il.DeclareLocal(mi.Type); + il.Emit(OpCodes.Stloc, obj); + + var bindings = mi.Bindings; + for (int i = 0, n = bindings.Count; i < n; i++) + { + var binding = bindings[i]; + if (binding.BindingType != MemberBindingType.Assignment) + return false; + il.Emit(OpCodes.Ldloc, obj); + + ok = TryEmit(((MemberAssignment)binding).Expression, ps, il, closure); + if (!ok) return false; + + var prop = binding.Member as PropertyInfo; + if (prop != null) + { + var setMethod = prop.GetSetMethod(); + if (setMethod == null) + return false; + EmitMethodCall(setMethod, il); + } + else + { + var field = binding.Member as FieldInfo; + if (field == null) + return false; + il.Emit(OpCodes.Stfld, field); + } + } + + il.Emit(OpCodes.Ldloc, obj); + return true; + } + + private static bool EmitMethodCall(MethodCallExpression m, IList ps, ILGenerator il, ClosureInfo closure) + { + var ok = true; + if (m.Object != null) + ok = TryEmit(m.Object, ps, il, closure); + + if (ok && m.Arguments.Count != 0) + ok = EmitMany(m.Arguments, ps, il, closure); + + if (ok) + EmitMethodCall(m.Method, il); + + return ok; + } + + private static bool EmitMemberAccess(MemberExpression m, IList ps, ILGenerator il, ClosureInfo closure) + { + if (m.Expression != null) + { + var ok = TryEmit(m.Expression, ps, il, closure); + if (!ok) return false; + } + + var field = m.Member as FieldInfo; + if (field != null) + { + il.Emit(field.IsStatic ? OpCodes.Ldsfld : OpCodes.Ldfld, field); + return true; + } + + var property = m.Member as PropertyInfo; + if (property != null) + { + var getMethod = property.GetGetMethod(); + if (getMethod == null) + return false; + EmitMethodCall(getMethod, il); + } + + return true; + } + + private static bool EmitComparison(BinaryExpression c, IList ps, ILGenerator il, ClosureInfo closure) + { + var ok = EmitBinary(c, ps, il, closure); + if (ok) + { + switch (c.NodeType) + { + case ExpressionType.Equal: + il.Emit(OpCodes.Ceq); + break; + case ExpressionType.LessThan: + il.Emit(OpCodes.Clt); + break; + case ExpressionType.GreaterThan: + il.Emit(OpCodes.Cgt); + break; + case ExpressionType.NotEqual: + il.Emit(OpCodes.Ceq); + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Ceq); + break; + case ExpressionType.LessThanOrEqual: + il.Emit(OpCodes.Cgt); + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Ceq); + break; + case ExpressionType.GreaterThanOrEqual: + il.Emit(OpCodes.Clt); + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Ceq); + break; + } + } + return ok; + } + + private static void EmitMethodCall(MethodInfo method, ILGenerator il) + { + il.Emit(method.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, method); + } + + private static void EmitLoadConstantInt(ILGenerator il, int i) + { + switch (i) + { + case 0: + il.Emit(OpCodes.Ldc_I4_0); + break; + case 1: + il.Emit(OpCodes.Ldc_I4_1); + break; + case 2: + il.Emit(OpCodes.Ldc_I4_2); + break; + case 3: + il.Emit(OpCodes.Ldc_I4_3); + break; + case 4: + il.Emit(OpCodes.Ldc_I4_4); + break; + case 5: + il.Emit(OpCodes.Ldc_I4_5); + break; + case 6: + il.Emit(OpCodes.Ldc_I4_6); + break; + case 7: + il.Emit(OpCodes.Ldc_I4_7); + break; + case 8: + il.Emit(OpCodes.Ldc_I4_8); + break; + default: + il.Emit(OpCodes.Ldc_I4, i); + break; + } + } + } + } +} \ No newline at end of file diff --git a/Src/Commons.InversionOfControl.DryIoC/DryIoc/ImTools.cs b/Src/Commons.InversionOfControl.DryIoC/DryIoc/ImTools.cs index 3be07de..237cd76 100644 --- a/Src/Commons.InversionOfControl.DryIoC/DryIoc/ImTools.cs +++ b/Src/Commons.InversionOfControl.DryIoC/DryIoc/ImTools.cs @@ -22,13 +22,21 @@ THE SOFTWARE. */ -namespace DryIoc +namespace ImTools { using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; + using System.Runtime.CompilerServices; // for aggressive inlining hints + + /// Portable aggressive in-lining option for MethodImpl. + public static class MethodImplHints + { + /// Value of MethodImplOptions.AggressingInlining + public const MethodImplOptions AggressingInlining = (MethodImplOptions)256; + } /// Methods to work with immutable arrays, and general array sugar. public static class ArrayTools @@ -318,6 +326,37 @@ public override int GetHashCode() } } + /// Helpers for . + public static class KV + { + /// Creates the key value pair. + /// Key type Value type + /// Key Value New pair. + [MethodImpl((MethodImplOptions)256)] // AggressiveInlining + public static KV Of(K key, V value) + { + return new KV(key, value); + } + + /// Creates the new pair with new key and old value. + /// Key type Value type + /// Source value New key New pair + [MethodImpl((MethodImplOptions)256)] // AggressiveInlining + public static KV WithKey(this KV source, K key) + { + return new KV(key, source.Value); + } + + /// Creates the new pair with old key and new value. + /// Key type Value type + /// Source value New value. New pair + [MethodImpl((MethodImplOptions)256)] // AggressiveInlining + public static KV WithValue(this KV source, V value) + { + return new KV(source.Key, value); + } + } + /// Delegate for changing value from old one to some new based on provided new value. /// Type of values. /// Existing value. @@ -325,7 +364,7 @@ public override int GetHashCode() /// Changed value. public delegate V Update(V oldValue, V newValue); - // todo: V3: Rename to ImTree + // todo: V3: Rename to ImMap /// Simple immutable AVL tree with integer keys and object values. public sealed class ImTreeMapIntToObj { @@ -466,7 +505,7 @@ private ImTreeMapIntToObj With(ImTreeMapIntToObj left, ImTreeMapIntToObj right) #endregion } - // todo: V3: Rename to ImHashTree + // todo: V3: Rename to ImMap /// Immutable http://en.wikipedia.org/wiki/AVL_tree where actual node key is hash code of . public sealed class ImTreeMap { @@ -581,7 +620,7 @@ private ImTreeMap(int hash, K key, V value, KV[] conficts, ImTreeMap Height = 1 + (left.Height > right.Height ? left.Height : right.Height); } - private ImTreeMap AddOrUpdate(int hash, K key, V value, Update update, bool updateOnly) + internal ImTreeMap AddOrUpdate(int hash, K key, V value, Update update, bool updateOnly) { return Height == 0 ? (updateOnly ? this : new ImTreeMap(hash, key, value, null, Empty, Empty)) : (hash == Hash ? UpdateValueAndResolveConflicts(key, value, update, updateOnly) @@ -616,7 +655,7 @@ private ImTreeMap(int hash, K key, V value, KV[] conficts, ImTreeMap return new ImTreeMap(Hash, Key, Value, conflicts, Left, Right); } - private V GetConflictedValueOrDefault(K key, V defaultValue) + internal V GetConflictedValueOrDefault(K key, V defaultValue) { if (Conflicts != null) for (var i = 0; i < Conflicts.Length; i++) @@ -650,4 +689,103 @@ private V GetConflictedValueOrDefault(K key, V defaultValue) #endregion } + + /// Avl trees forest - uses last hash bits to quickly find target tree, more performant Lookup but no traversal. + /// Key type Value type. + public sealed class ImMap + { + private const int NumberOfTrees = 32; + private const int HashBitsToTree = NumberOfTrees - 1; // get last 4 bits, fast (hash % NumberOfTrees) + + /// Empty tree to start with. + public static readonly ImMap Empty = new ImMap(new ImTreeMap[NumberOfTrees], 0); + + /// Count in items stored. + public readonly int Count; + + /// Truw if contain no items + public bool IsEmpty { get { return Count == 0; } } + + /// Looks for key in a tree and returns the key value if found, or otherwise. + /// Key to look for. (optional) Value to return if key is not found. + /// Found value or . + [MethodImpl(MethodImplHints.AggressingInlining)] + public V GetValueOrDefault(K key, V defaultValue = default(V)) + { + var hash = key.GetHashCode(); + + var t = _trees[hash & HashBitsToTree]; + if (t == null) + return defaultValue; + + while (t.Height != 0 && t.Hash != hash) + t = hash < t.Hash ? t.Left : t.Right; + + if (t.Height != 0 && (ReferenceEquals(key, t.Key) || key.Equals(t.Key))) + return t.Value; + + return t.GetConflictedValueOrDefault(key, defaultValue); + } + + /// Returns new tree with added key-value. + /// If value with the same key is exist then the value is replaced. + /// Key to add.Value to add. + /// New tree with added or updated key-value. + [MethodImpl(MethodImplHints.AggressingInlining)] + public ImMap AddOrUpdate(K key, V value) + { + var hash = key.GetHashCode(); + + var treeIndex = hash & HashBitsToTree; + + var trees = _trees; + var tree = trees[treeIndex]; + if (tree == null) + tree = ImTreeMap.Empty; + + tree = tree.AddOrUpdate(hash, key, value, null, false); + + var newTrees = new ImTreeMap[NumberOfTrees]; + Array.Copy(trees, 0, newTrees, 0, NumberOfTrees); + newTrees[treeIndex] = tree; + + return new ImMap(newTrees, Count + 1); + } + + /// Looks for and replaces its value with new + /// Key to look for. + /// New value to replace key value with. + /// New tree with updated value or the SAME tree if no key found. + [MethodImpl(MethodImplHints.AggressingInlining)] + public ImMap Update(K key, V value) + { + var hash = key.GetHashCode(); + + var treeIndex = hash & HashBitsToTree; + + var trees = _trees; + var tree = trees[treeIndex]; + if (tree == null) + return this; + + var newTree = tree.AddOrUpdate(hash, key, value, null, true); + if (newTree == tree) + return this; + + var newTrees = new ImTreeMap[NumberOfTrees]; + Array.Copy(trees, 0, newTrees, 0, NumberOfTrees); + newTrees[treeIndex] = newTree; + + return new ImMap(newTrees, Count); + } + + private readonly ImTreeMap[] _trees; + + private ImMap(ImTreeMap[] newTrees, int count) + { + _trees = newTrees; + Count = count; + } + } + } \ No newline at end of file diff --git a/Src/Commons.InversionOfControl.DryIoC/Properties/AssemblyInfo.cs b/Src/Commons.InversionOfControl.DryIoC/Properties/AssemblyInfo.cs index 72fa181..a503dbf 100644 --- a/Src/Commons.InversionOfControl.DryIoC/Properties/AssemblyInfo.cs +++ b/Src/Commons.InversionOfControl.DryIoC/Properties/AssemblyInfo.cs @@ -32,7 +32,7 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("4.0.3.2")] -[assembly: AssemblyFileVersion("4.0.3.2")] +[assembly: AssemblyVersion("4.0.6.0")] +[assembly: AssemblyFileVersion("4.0.6.0")] //[assembly: AssemblyKeyFileAttribute("../../BoC.snk")] \ No newline at end of file diff --git a/Src/Commons.InversionOfControl.DryIoC/packages.config b/Src/Commons.InversionOfControl.DryIoC/packages.config index 44a9057..1c9f3af 100644 --- a/Src/Commons.InversionOfControl.DryIoC/packages.config +++ b/Src/Commons.InversionOfControl.DryIoC/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file diff --git a/Src/Commons.InversionOfControl.Ninject/BoC.InversionOfControl.Ninject.csproj b/Src/Commons.InversionOfControl.Ninject/BoC.InversionOfControl.Ninject.csproj index f8fcc2c..df1e5e2 100644 --- a/Src/Commons.InversionOfControl.Ninject/BoC.InversionOfControl.Ninject.csproj +++ b/Src/Commons.InversionOfControl.Ninject/BoC.InversionOfControl.Ninject.csproj @@ -1,94 +1,93 @@ - - - - - Debug - AnyCPU - {8CFC4566-BEE8-42EF-803A-AFEB5629640C} - Library - Properties - BoC.InversionOfControl.Ninject - BoC.InversionOfControl.Ninject - v4.5 - 512 - ..\..\ - true - - - true - full - false - ..\..\build\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - ..\..\build\ - TRACE - prompt - 4 - - - - True - ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - - False - ..\..\packages\Ninject.3.2.2.0\lib\net45-full\Ninject.dll - - - ..\..\packages\Ninject.Web.Common.3.2.3.0\lib\net45-full\Ninject.Web.Common.dll - - - - - - - - - - - - False - ..\..\packages\WebActivatorEx.2.0.6\lib\net40\WebActivatorEx.dll - - - - - - - - - - - {25C350AF-4957-47E8-960A-6C59794C5DEF} - BoC - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - + + + + + Debug + AnyCPU + {8CFC4566-BEE8-42EF-803A-AFEB5629640C} + Library + Properties + BoC.InversionOfControl.Ninject + BoC.InversionOfControl.Ninject + v4.5 + 512 + ..\..\ + true + + + true + full + false + ..\..\build\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + ..\..\build\ + TRACE + prompt + 4 + + + + True + ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + + + False + ..\..\packages\Ninject.3.2.2.0\lib\net45-full\Ninject.dll + + + ..\..\packages\Ninject.Web.Common.3.2.3.0\lib\net45-full\Ninject.Web.Common.dll + + + + + + + + + + + + ..\..\packages\WebActivatorEx.2.2.0\lib\net40\WebActivatorEx.dll + + + + + + + + + + + {25C350AF-4957-47E8-960A-6C59794C5DEF} + BoC + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + --> \ No newline at end of file diff --git a/Src/Commons.InversionOfControl.Ninject/Extensions.cs b/Src/Commons.InversionOfControl.Ninject/Extensions.cs index 4b5f556..e247904 100644 --- a/Src/Commons.InversionOfControl.Ninject/Extensions.cs +++ b/Src/Commons.InversionOfControl.Ninject/Extensions.cs @@ -20,6 +20,8 @@ public static IBindingNamedWithOrOnSyntax SetLifeStyle(this IBindingInSynt return registration.InRequestScope(); case LifetimeScope.PerThread: return registration.InThreadScope(); + case LifetimeScope.Singleton: + return registration.InSingletonScope(); default: return registration.InTransientScope(); } diff --git a/Src/Commons.InversionOfControl.Ninject/UnityMvcActivator.cs b/Src/Commons.InversionOfControl.Ninject/NinjectWebActivator.cs similarity index 100% rename from Src/Commons.InversionOfControl.Ninject/UnityMvcActivator.cs rename to Src/Commons.InversionOfControl.Ninject/NinjectWebActivator.cs diff --git a/Src/Commons.InversionOfControl.Ninject/Properties/AssemblyInfo.cs b/Src/Commons.InversionOfControl.Ninject/Properties/AssemblyInfo.cs index 9588cf1..42d697e 100644 --- a/Src/Commons.InversionOfControl.Ninject/Properties/AssemblyInfo.cs +++ b/Src/Commons.InversionOfControl.Ninject/Properties/AssemblyInfo.cs @@ -32,7 +32,7 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("4.0.3.2")] -[assembly: AssemblyFileVersion("4.0.3.2")] +[assembly: AssemblyVersion("4.0.6.0")] +[assembly: AssemblyFileVersion("4.0.6.0")] [assembly: AssemblyKeyFileAttribute("../../BoC.snk")] \ No newline at end of file diff --git a/Src/Commons.InversionOfControl.Ninject/packages.config b/Src/Commons.InversionOfControl.Ninject/packages.config index 8243f8c..48338ae 100644 --- a/Src/Commons.InversionOfControl.Ninject/packages.config +++ b/Src/Commons.InversionOfControl.Ninject/packages.config @@ -1,7 +1,7 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/Src/Commons.InversionOfControl.SimpleInjector/BoC.InversionOfControl.SimpleInjector.csproj b/Src/Commons.InversionOfControl.SimpleInjector/BoC.InversionOfControl.SimpleInjector.csproj index c4214ed..019b1cf 100644 --- a/Src/Commons.InversionOfControl.SimpleInjector/BoC.InversionOfControl.SimpleInjector.csproj +++ b/Src/Commons.InversionOfControl.SimpleInjector/BoC.InversionOfControl.SimpleInjector.csproj @@ -36,17 +36,14 @@ ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll True - - ..\..\packages\SimpleInjector.3.2.3\lib\net45\SimpleInjector.dll - True + + ..\..\packages\SimpleInjector.3.3.2\lib\net45\SimpleInjector.dll - - ..\..\packages\SimpleInjector.Extensions.ExecutionContextScoping.3.2.3\lib\net45\SimpleInjector.Extensions.ExecutionContextScoping.dll - True + + ..\..\packages\SimpleInjector.Extensions.ExecutionContextScoping.3.3.2\lib\net45\SimpleInjector.Extensions.ExecutionContextScoping.dll - - ..\..\packages\SimpleInjector.Integration.Web.3.2.3\lib\net40\SimpleInjector.Integration.Web.dll - True + + ..\..\packages\SimpleInjector.Integration.Web.3.3.2\lib\net40\SimpleInjector.Integration.Web.dll diff --git a/Src/Commons.InversionOfControl.SimpleInjector/Properties/AssemblyInfo.cs b/Src/Commons.InversionOfControl.SimpleInjector/Properties/AssemblyInfo.cs index c98c547..c7e4b1a 100644 --- a/Src/Commons.InversionOfControl.SimpleInjector/Properties/AssemblyInfo.cs +++ b/Src/Commons.InversionOfControl.SimpleInjector/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("4.2.3.3")] -[assembly: AssemblyFileVersion("4.2.3.3")] +[assembly: AssemblyVersion("4.2.6.0")] +[assembly: AssemblyFileVersion("4.2.6.0")] diff --git a/Src/Commons.InversionOfControl.SimpleInjector/app.config b/Src/Commons.InversionOfControl.SimpleInjector/app.config index 038418a..0b78b56 100644 --- a/Src/Commons.InversionOfControl.SimpleInjector/app.config +++ b/Src/Commons.InversionOfControl.SimpleInjector/app.config @@ -4,7 +4,7 @@ - + diff --git a/Src/Commons.InversionOfControl.SimpleInjector/packages.config b/Src/Commons.InversionOfControl.SimpleInjector/packages.config index 38cd48e..eb34375 100644 --- a/Src/Commons.InversionOfControl.SimpleInjector/packages.config +++ b/Src/Commons.InversionOfControl.SimpleInjector/packages.config @@ -1,7 +1,7 @@  - - - + + + \ No newline at end of file diff --git a/Src/Commons.InversionOfControl.Unity/BoC.InversionOfControl.Unity.csproj b/Src/Commons.InversionOfControl.Unity/BoC.InversionOfControl.Unity.csproj index 20e1cfa..7dd7a66 100644 --- a/Src/Commons.InversionOfControl.Unity/BoC.InversionOfControl.Unity.csproj +++ b/Src/Commons.InversionOfControl.Unity/BoC.InversionOfControl.Unity.csproj @@ -81,9 +81,8 @@ - - False - ..\..\packages\CommonServiceLocator.1.2\lib\portable-windows8+net40+sl5+windowsphone8\Microsoft.Practices.ServiceLocation.dll + + ..\..\packages\CommonServiceLocator.1.3\lib\portable-net4+sl5+netcore45+wpa81+wp8\Microsoft.Practices.ServiceLocation.dll False @@ -121,8 +120,7 @@ - False - ..\..\packages\WebActivatorEx.2.0.6\lib\net40\WebActivatorEx.dll + ..\..\packages\WebActivatorEx.2.2.0\lib\net40\WebActivatorEx.dll @@ -158,6 +156,7 @@ + diff --git a/Src/Commons.InversionOfControl.Unity/Properties/AssemblyInfo.cs b/Src/Commons.InversionOfControl.Unity/Properties/AssemblyInfo.cs index 1581a79..bf8b49f 100644 --- a/Src/Commons.InversionOfControl.Unity/Properties/AssemblyInfo.cs +++ b/Src/Commons.InversionOfControl.Unity/Properties/AssemblyInfo.cs @@ -32,7 +32,7 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("4.1.3.2")] -[assembly: AssemblyFileVersion("4.1.3.2")] +[assembly: AssemblyVersion("4.1.6.0")] +[assembly: AssemblyFileVersion("4.1.6.0")] [assembly: AssemblyKeyFileAttribute("../../BoC.snk")] \ No newline at end of file diff --git a/Src/Commons.InversionOfControl.Unity/UnityDependencyResolver.cs b/Src/Commons.InversionOfControl.Unity/UnityDependencyResolver.cs index 66087b7..a7539e3 100644 --- a/Src/Commons.InversionOfControl.Unity/UnityDependencyResolver.cs +++ b/Src/Commons.InversionOfControl.Unity/UnityDependencyResolver.cs @@ -218,6 +218,8 @@ private LifetimeManager GetLifetimeManager(LifetimeScope lifetimeScope) return new PerRequestLifetimeManager(); case LifetimeScope.PerThread: return new PerThreadLifetimeManager(); + case LifetimeScope.Singleton: + return new ContainerControlledLifetimeManager(); default: return new TransientLifetimeManager(); } diff --git a/Src/Commons.InversionOfControl.Unity/app.config b/Src/Commons.InversionOfControl.Unity/app.config new file mode 100644 index 0000000..e156c20 --- /dev/null +++ b/Src/Commons.InversionOfControl.Unity/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Src/Commons.InversionOfControl.Unity/packages.config b/Src/Commons.InversionOfControl.Unity/packages.config index ad7837a..2fd1ca4 100644 --- a/Src/Commons.InversionOfControl.Unity/packages.config +++ b/Src/Commons.InversionOfControl.Unity/packages.config @@ -1,7 +1,7 @@  - + - + \ No newline at end of file diff --git a/Src/Commons.Sitecore.Mvc/BoC.Sitecore.Mvc.nuspec b/Src/Commons.Sitecore.Mvc/BoC.Sitecore.Mvc.nuspec index 7e4078c..c96af61 100644 --- a/Src/Commons.Sitecore.Mvc/BoC.Sitecore.Mvc.nuspec +++ b/Src/Commons.Sitecore.Mvc/BoC.Sitecore.Mvc.nuspec @@ -10,7 +10,7 @@ https://github.com/csteeg/boc false $description$ - Summary of changes made in this release of the package. + Copyright 2014 BoC,Persistence,Glass,Mapper,ORM,Repository,Sitecore diff --git a/Src/Commons.Sitecore.Mvc/Properties/AssemblyInfo.cs b/Src/Commons.Sitecore.Mvc/Properties/AssemblyInfo.cs index 36aef46..9be2e5b 100644 --- a/Src/Commons.Sitecore.Mvc/Properties/AssemblyInfo.cs +++ b/Src/Commons.Sitecore.Mvc/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("4.1.1.0")] -[assembly: AssemblyFileVersion("4.1.1.0")] +[assembly: AssemblyVersion("4.1.2.0")] +[assembly: AssemblyFileVersion("4.1.2.0")] diff --git a/Src/Commons.Sitecore.Mvc/SitecoreValueProvider.cs b/Src/Commons.Sitecore.Mvc/SitecoreValueProvider.cs index 6467a93..8e35c54 100644 --- a/Src/Commons.Sitecore.Mvc/SitecoreValueProvider.cs +++ b/Src/Commons.Sitecore.Mvc/SitecoreValueProvider.cs @@ -93,12 +93,10 @@ private ValueProviderResult GetValueResult(Item item, string key) internal class SitecoreValueProviderResult : ValueProviderResult { private readonly Item _item; - private readonly string _stringValue; - public SitecoreValueProviderResult(Item item, string stringValue, CultureInfo currentCulture) + public SitecoreValueProviderResult(Item item, string stringValue, CultureInfo currentCulture): base(item, stringValue, currentCulture) { _item = item; - _stringValue = stringValue; } public override object ConvertTo(Type type, CultureInfo culture) diff --git a/Src/Commons/BoC.csproj b/Src/Commons/BoC.csproj index a5ee7a9..e519196 100644 --- a/Src/Commons/BoC.csproj +++ b/Src/Commons/BoC.csproj @@ -111,8 +111,7 @@ - False - ..\..\packages\WebActivatorEx.2.0.5\lib\net40\WebActivatorEx.dll + ..\..\packages\WebActivatorEx.2.2.0\lib\net40\WebActivatorEx.dll diff --git a/Src/Commons/Properties/AssemblyInfo.cs b/Src/Commons/Properties/AssemblyInfo.cs index 0cfc3e1..7a6e305 100644 --- a/Src/Commons/Properties/AssemblyInfo.cs +++ b/Src/Commons/Properties/AssemblyInfo.cs @@ -32,7 +32,7 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("4.0.5.0")] -[assembly: AssemblyFileVersion("4.0.5.0")] +[assembly: AssemblyVersion("4.0.6.0")] +[assembly: AssemblyFileVersion("4.0.6.0")] [assembly: AssemblyKeyFileAttribute("../../BoC.snk")] \ No newline at end of file diff --git a/Src/Commons/packages.config b/Src/Commons/packages.config index 193fb73..c968cc4 100644 --- a/Src/Commons/packages.config +++ b/Src/Commons/packages.config @@ -1,5 +1,5 @@ - - - - + + + + \ No newline at end of file diff --git a/Tests/Commons.InversionOfControl.SimpleInjector.Tests/app.config b/Tests/Commons.InversionOfControl.SimpleInjector.Tests/app.config index 038418a..0b78b56 100644 --- a/Tests/Commons.InversionOfControl.SimpleInjector.Tests/app.config +++ b/Tests/Commons.InversionOfControl.SimpleInjector.Tests/app.config @@ -4,7 +4,7 @@ - + diff --git a/Tests/Commons.InversionOfControl.Unity.Tests/BoC.InversionOfControl.Unity.Tests.csproj b/Tests/Commons.InversionOfControl.Unity.Tests/BoC.InversionOfControl.Unity.Tests.csproj index 9763f87..aeccf01 100644 --- a/Tests/Commons.InversionOfControl.Unity.Tests/BoC.InversionOfControl.Unity.Tests.csproj +++ b/Tests/Commons.InversionOfControl.Unity.Tests/BoC.InversionOfControl.Unity.Tests.csproj @@ -63,6 +63,9 @@ BoC + + + diff --git a/Tests/Commons.InversionOfControl.Unity.Tests/app.config b/Tests/Commons.InversionOfControl.Unity.Tests/app.config new file mode 100644 index 0000000..e156c20 --- /dev/null +++ b/Tests/Commons.InversionOfControl.Unity.Tests/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file