The simple definition of my container, which is an extended interface based on the System.IServiceProvider
/// <summary>
/// Use to manage instances and the creation of instances
/// </summary>
public interface IContainer : IDisposable, IServiceProvider
{
#region Others
/// <summary>
/// Get an instance of a specific service
/// </summary>
/// <typeparam name="TService">The type of the service</typeparam>
/// <returns>The instance of the service</returns>
TService GetService<TService>();
/// <summary>
/// Inject the instance properties from the <see cref="IContainer" />
/// </summary>
/// <param name="instance">The instance to inject</param>
void InjectProperties(object instance);
/// <summary>
/// Register the type of the service to the container
/// </summary>
/// <typeparam name="TService">The type of the service</typeparam>
void Register<TService>();
/// <summary>
/// Register the type of the service to the container with the implementation type
/// </summary>
/// <typeparam name="TService">The type of the service</typeparam>
/// <typeparam name="TImpl">The implementation type</typeparam>
void Register<TService, TImpl>() where TImpl : TService;
/// <summary>
/// Register the type of the service to the container with a delegate to create the instance
/// </summary>
/// <typeparam name="TService">The type of the service</typeparam>
/// <param name="instanceCreator">The delegate to create the service instance</param>
void Register<TService>(Func<TService> instanceCreator);
#endregion
}
The simple implementation of IContainer which is a singleton
public class SimpleContainer : IContainer
{
#region Constructors
static SimpleContainer()
{
Container = new SimpleContainer();
}
private SimpleContainer()
{
this.Singleton<IContainer>(this);
}
#endregion
#region Properties
public static IContainer Container { get; }
private static ConcurrentDictionary<Type, IEnumerable<PropertyInfo>> InjectableProperties { get; } =
new ConcurrentDictionary<Type, IEnumerable<PropertyInfo>>();
private Dictionary<Type, Func<object>> Registrations { get; } = new Dictionary<Type, Func<object>>();
#endregion
#region Interface Implementations
public T GetService<T>()
{
return (T) GetService(typeof(T));
}
/// <summary>
/// Inject the properties attributed with InjectAttribute
/// </summary>
/// <param name="instance">The instance</param>
public void InjectProperties(object instance)
{
if (null == instance) return;
GetInjectableProperties(instance)
.ToList()
.ForEach(property => { property.SetValue(instance, GetService(property.PropertyType), null); });
}
public void Register<TService>()
{
Register<TService, TService>();
}
public void Register<TService, TImpl>() where TImpl : TService
{
InternalRegister<TService>(() =>
{
var implType = typeof(TImpl);
return typeof(TService) == implType
? this.CreateInstance(implType)
: GetService(implType);
});
}
public void Register<TService>(Func<TService> instanceCreator)
{
InternalRegister<TService>(() => instanceCreator());
}
public void Dispose()
{
Registrations.Clear();
}
public object GetService(Type serviceType)
{
if (Registrations.TryGetValue(serviceType, out var creator))
{
return creator();
}
if (!serviceType.IsAbstract)
{
return this.CreateInstance(serviceType);
}
throw new Exception($"No registration found for service : {serviceType}");
}
#endregion
#region Others
private static IEnumerable<PropertyInfo> GetInjectableProperties(object obj)
{
var type = obj.GetType();
var properties = InjectableProperties.GetOrAdd(type,
t =>
{
return t.GetProperties(BindingFlags.Instance
| BindingFlags.SetProperty
| BindingFlags.NonPublic
| BindingFlags.Public)
.Where(property =>
property.GetCustomAttributes(typeof(InjectAttribute), false).Any())
.Where(property => property.GetValue(obj, null) == null).ToList();
});
return properties;
}
private void InternalRegister<TService>(Func<object> creator)
{
var key = typeof(TService);
if (Registrations.ContainsKey(key))
Registrations[key] = creator;
else
Registrations.Add(typeof(TService), creator);
}
#endregion
}
And then I created a base class for all the objects who wants to consume the DI ability of the container
/// <summary>
/// A base object which can automatically inject properties attributed with <see cref="InjectAttribute" /> from the
/// <see cref="IContainer" />
/// </summary>
public abstract class ContainerObject
{
#region Constructors
// ReSharper disable once UnusedMember.Global
protected ContainerObject()
{
InjectProperties();
}
#endregion
#region Properties
protected IContainer Container => SimpleContainer.Container;
#endregion
#region Others
protected void InjectProperties()
{
Container?.InjectProperties(this);
}
#endregion
}
Of course with the help of InjectAttribute, I can inject service automatically
/// <summary>
/// Inject an object to a property from the <see cref="IContainer" />, the property should always have a setter
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class InjectAttribute : Attribute
{
}
To better register services to the container, I created some extension methods around IContainer
public static class ContainerExtensions
{
#region Others
public static IContainer Singleton<TService>(this IContainer container, TService instance)
{
container.Register(() => instance);
return container;
}
public static IContainer Singleton<TService>(this IContainer container, Func<TService> instanceCreator)
{
var lazy = new Lazy<TService>(instanceCreator);
container.Register(() => lazy.Value);
return container;
}
public static IContainer Singleton<TService, TImpl>(this IContainer container)
where TImpl : TService
{
return container.Singleton<TService, TImpl>(() =>
typeof(TService) == typeof(TImpl) ? container.CreateInstance<TImpl>() : container.GetService<TImpl>());
}
public static IContainer Singleton<TService, TImpl>(this IContainer container, Func<TImpl> instanceCreator)
where TImpl : TService
{
var lazy = new Lazy<TImpl>(instanceCreator);
container.Register<TService>(() => lazy.Value);
return container;
}
public static IContainer Singleton<TService1, TService2, TImpl>(this IContainer container)
where TImpl : TService1, TService2
{
var implType = typeof(TImpl);
return container.Singleton<TService1, TService2, TImpl>(() => typeof(TService1) == implType ||
typeof(TService2) == implType
? container.CreateInstance<TImpl>()
: container.GetService<TImpl>());
}
public static IContainer Singleton<TService1, TService2, TImpl>(this IContainer container, Func<TImpl> instanceCreator)
where TImpl : TService1, TService2
{
var lazy = new Lazy<TImpl>(instanceCreator);
container.Register<TService1>(() => lazy.Value);
container.Register<TService2>(() => lazy.Value);
return container;
}
public static IContainer Singleton<TService1, TService2, TService3, TImpl>(this IContainer container)
where TImpl : TService1, TService2, TService3
{
var implType = typeof(TImpl);
return container.Singleton<TService1, TService2, TImpl>(() => typeof(TService1) == implType ||
typeof(TService2) == implType ||
typeof(TService3) == implType
? container.CreateInstance<TImpl>()
: container.GetService<TImpl>());
}
public static IContainer Singleton<TService1, TService2, TService3, TImpl>(this IContainer container, Func<TImpl> instanceCreator)
where TImpl : TService1, TService2, TService3
{
var lazy = new Lazy<TImpl>(instanceCreator);
container.Register<TService1>(() => lazy.Value);
container.Register<TService2>(() => lazy.Value);
container.Register<TService3>(() => lazy.Value);
return container;
}
public static IContainer Singleton<TService1, TService2, TService3, TService4, TImpl>(this IContainer container)
where TImpl : TService1, TService2, TService3, TService4
{
var implType = typeof(TImpl);
return container.Singleton<TService1, TService2, TImpl>(() => typeof(TService1) == implType ||
typeof(TService2) == implType ||
typeof(TService3) == implType ||
typeof(TService4) == implType
? container.CreateInstance<TImpl>()
: container.GetService<TImpl>());
}
public static IContainer Singleton<TService1, TService2, TService3, TService4, TImpl>(this IContainer container, Func<TImpl> instanceCreator)
where TImpl : TService1, TService2, TService3, TService4
{
var lazy = new Lazy<TImpl>(instanceCreator);
container.Register<TService1>(() => lazy.Value);
container.Register<TService2>(() => lazy.Value);
container.Register<TService3>(() => lazy.Value);
container.Register<TService4>(() => lazy.Value);
return container;
}
#endregion
}
And also an extension around System.IServiceProvider to create service instances
public static class ServiceProviderExtensions
{
#region Others
/// <summary>
/// Create the instance of the implementation type, inject all the constructor parameters and inject properties
/// </summary>
/// <param name="container">The instance of <see cref="IServiceProvider" /></param>
/// <param name="implementationType"></param>
/// <returns></returns>
public static object CreateInstance(this IServiceProvider container, Type implementationType)
{
var ctor = implementationType
.GetConstructors(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.CreateInstance |
BindingFlags.Instance).OrderByDescending(x => x.GetParameters().Length).First();
var parameterTypes = ctor.GetParameters().Select(p => p.ParameterType);
var dependencies = parameterTypes.Select(container.GetService).ToArray();
var instance = Activator.CreateInstance(implementationType,
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.CreateInstance |
BindingFlags.Instance, null, dependencies, CultureInfo.CurrentCulture);
return instance;
}
public static T CreateInstance<T>(this IServiceProvider container)
{
return (T) container.CreateInstance(typeof(T));
}
public static T GetService<T>(this IServiceProvider container)
{
var service = container.GetService(typeof(T));
return (T) service;
}
/// <summary>
/// Inject all the injectable properties
/// </summary>
/// <param name="container"></param>
/// <param name="instance"></param>
/// <param name="selector"></param>
internal static void InjectProperties(this IServiceProvider container, object instance, Func<PropertyInfo, bool> selector)
{
if (null == instance) return;
try
{
instance.GetType().GetProperties().Where(selector).ToList()
.ForEach(property =>
property.SetValue(instance, container.GetService(property.PropertyType), null));
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
#endregion
}
Now I can write a service and register it on App starts up.
public interface IMyServiceA
{
void Method();
}
public class MyServiceA: IMyServiceA
{
void Method()
{
}
}
public class App: ContainerObject, IExternalApplication
{
public Result OnShutdown(UIControlledApplication application)
{
try
{
// do some cleaning
return Result.Succeeded;
}
catch (Exception e)
{
return Result.Failed;
}
}
public Result OnStartup(UIControlledApplication application)
{
try
{
this.Container.Singleton<IMyServiceA, MyServiceA>()
}
catch (Exception e)
{
MessageBox.Show(e.ToString(), @"Oops!");
}
return Result.Succeeded;
}
}
In another service I can consume a registered service like this(property injection and constructor injection)
public interface IMyServiceB
{
void Method();
}
// property injection
public class MyServiceB: ContainerObject, IMyServiceB
{
[Inject]
private IMyServiceA ServiceA{ get; set; }
void Method()
{
this.ServiceA.Method()
}
}
public interface IMyServiceC
{
void Method();
}
// constructor injection
// MyServiceC is better to be registered to the Container to get created automatically with an instance of IMyServiceA
public class MyServiceC: ContainerObject, IMyServiceC
{
public MyServiceC(IMyServiceA serviceA)
{
this.ServiceA = serviceA;
}
private IMyServiceA ServiceA{ get; set; }
void Method()
{
this.ServiceA.Method()
}
}
It is just a basic design of my DI system with detail reduced. Hope it helps