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