// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; namespace Microsoft.MinIoC { /// /// Inversion of control container handles dependency injection for registered types /// class Container : Container.IScope { #region Public interfaces /// /// Represents a scope in which per-scope objects are instantiated a single time /// public interface IScope : IDisposable, IServiceProvider { } /// /// IRegisteredType is return by Container.Register and allows further configuration for the registration /// public interface IRegisteredType { /// /// Make registered type a singleton /// void AsSingleton(); /// /// Make registered type a per-scope type (single instance within a Scope) /// void PerScope(); } #endregion // Map of registered types private readonly Dictionary> _registeredTypes = new Dictionary>(); // Lifetime management private readonly ContainerLifetime _lifetime; /// /// Creates a new instance of IoC Container /// public Container() => _lifetime = new ContainerLifetime(t => _registeredTypes[t]); /// /// Registers a factory function which will be called to resolve the specified interface /// /// Interface to register /// Factory function /// public IRegisteredType Register(Type @interface, Func factory) => RegisterType(@interface, _ => factory()); /// /// Registers an implementation type for the specified interface /// /// Interface to register /// Implementing type /// public IRegisteredType Register(Type @interface, Type implementation) => RegisterType(@interface, FactoryFromType(implementation)); private IRegisteredType RegisterType(Type itemType, Func factory) => new RegisteredType(itemType, f => _registeredTypes[itemType] = f, factory); /// /// Returns the object registered for the given type, if registered /// /// Type as registered with the container /// Instance of the registered type, if registered; otherwise public object GetService(Type type) { Func registeredType; if (!_registeredTypes.TryGetValue(type, out registeredType)) { return null; } return registeredType(_lifetime); } /// /// Creates a new scope /// /// Scope object public IScope CreateScope() => new ScopeLifetime(_lifetime); /// /// Disposes any objects owned by this container. /// public void Dispose() => _lifetime.Dispose(); #region Lifetime management // ILifetime management adds resolution strategies to an IScope interface ILifetime : IScope { object GetServiceAsSingleton(Type type, Func factory); object GetServicePerScope(Type type, Func factory); } // ObjectCache provides common caching logic for lifetimes abstract class ObjectCache { // Instance cache private readonly ConcurrentDictionary _instanceCache = new ConcurrentDictionary(); // Get from cache or create and cache object protected object GetCached(Type type, Func factory, ILifetime lifetime) => _instanceCache.GetOrAdd(type, _ => factory(lifetime)); public void Dispose() { foreach (var obj in _instanceCache.Values) (obj as IDisposable)?.Dispose(); } } // Container lifetime management class ContainerLifetime : ObjectCache, ILifetime { // Retrieves the factory functino from the given type, provided by owning container public Func> GetFactory { get; private set; } public ContainerLifetime(Func> getFactory) => GetFactory = getFactory; public object GetService(Type type) => GetFactory(type)(this); // Singletons get cached per container public object GetServiceAsSingleton(Type type, Func factory) => GetCached(type, factory, this); // At container level, per-scope items are equivalent to singletons public object GetServicePerScope(Type type, Func factory) => GetServiceAsSingleton(type, factory); } // Per-scope lifetime management class ScopeLifetime : ObjectCache, ILifetime { // Singletons come from parent container's lifetime private readonly ContainerLifetime _parentLifetime; public ScopeLifetime(ContainerLifetime parentContainer) => _parentLifetime = parentContainer; public object GetService(Type type) => _parentLifetime.GetFactory(type)(this); // Singleton resolution is delegated to parent lifetime public object GetServiceAsSingleton(Type type, Func factory) => _parentLifetime.GetServiceAsSingleton(type, factory); // Per-scope objects get cached public object GetServicePerScope(Type type, Func factory) => GetCached(type, factory, this); } #endregion #region Container items // Compiles a lambda that calls the given type's first constructor resolving arguments private static Func FactoryFromType(Type itemType) { // Get first constructor for the type var constructors = itemType.GetConstructors(); if (constructors.Length == 0) { // If no public constructor found, search for an internal constructor constructors = itemType.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic); } var constructor = constructors.First(); // Compile constructor call as a lambda expression var arg = Expression.Parameter(typeof(ILifetime)); return (Func)Expression.Lambda( Expression.New(constructor, constructor.GetParameters().Select( param => { var resolve = new Func( lifetime => lifetime.GetService(param.ParameterType)); return Expression.Convert( Expression.Call(Expression.Constant(resolve.Target), resolve.Method, arg), param.ParameterType); })), arg).Compile(); } // RegisteredType is supposed to be a short lived object tying an item to its container // and allowing users to mark it as a singleton or per-scope item class RegisteredType : IRegisteredType { private readonly Type _itemType; private readonly Action> _registerFactory; private readonly Func _factory; public RegisteredType(Type itemType, Action> registerFactory, Func factory) { _itemType = itemType; _registerFactory = registerFactory; _factory = factory; registerFactory(_factory); } public void AsSingleton() => _registerFactory(lifetime => lifetime.GetServiceAsSingleton(_itemType, _factory)); public void PerScope() => _registerFactory(lifetime => lifetime.GetServicePerScope(_itemType, _factory)); } #endregion } /// /// Extension methods for Container /// static class ContainerExtensions { /// /// Registers an implementation type for the specified interface /// /// Interface to register /// This container instance /// Implementing type /// IRegisteredType object public static Container.IRegisteredType Register(this Container container, Type type) => container.Register(typeof(T), type); /// /// Registers an implementation type for the specified interface /// /// Interface to register /// Implementing type /// This container instance /// IRegisteredType object public static Container.IRegisteredType Register(this Container container) where TImplementation : TInterface => container.Register(typeof(TInterface), typeof(TImplementation)); /// /// Registers a factory function which will be called to resolve the specified interface /// /// Interface to register /// This container instance /// Factory method /// IRegisteredType object public static Container.IRegisteredType Register(this Container container, Func factory) => container.Register(typeof(T), () => factory()); /// /// Registers a type /// /// This container instance /// Type to register /// IRegisteredType object public static Container.IRegisteredType Register(this Container container) => container.Register(typeof(T), typeof(T)); /// /// Returns an implementation of the specified interface /// /// Interface type /// This scope instance /// Object implementing the interface public static T Resolve(this Container.IScope scope) => (T)scope.GetService(typeof(T)); } }