using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using ZLinq; namespace RebootKit.Engine.Foundation { [AttributeUsage(AttributeTargets.Field | AttributeTargets.Method)] public class InjectAttribute : Attribute { } public class DIContext { const BindingFlags k_fieldsBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; const BindingFlags k_methodsBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; static readonly Logger s_logger = new Logger(nameof(DIContext)); readonly Dictionary m_BindingsMaps = new Dictionary(); readonly List m_FieldInjectors = new List(); public DIContext() { Bind(this); AddInjector(new InjectAttributeFieldInjector()); } public void AddInjector(IFieldInjector injector) { m_FieldInjectors.Add(injector); } public void Bind(Type type, object obj) { if (!m_BindingsMaps.TryAdd(type, obj)) { s_logger.Error($"Cannot bind to '{type}', slot is already occupied"); } } public void Bind(TBind obj) { Bind(typeof(TBind), obj); } public object Resolve(Type type) { if (m_BindingsMaps.TryGetValue(type, out object obj)) return obj; s_logger.Error($"Couldn't resolve `{type}`"); return null; } public T Resolve() { return (T) Resolve(typeof(T)); } // @brief creates new instance of an object and injects dependencies public T Create() { T instance = Activator.CreateInstance(); Inject(instance); return instance; } public T Create(params object[] args) { T instance = (T) Activator.CreateInstance(typeof(T), args); Inject(instance); return instance; } public void Inject(object target) { Type type = target.GetType(); foreach (FieldInfo field in type.GetFields(k_fieldsBindingFlags)) { InjectField(field, target); } foreach (MethodInfo method in type.GetMethods(k_methodsBindingFlags)) { if (!Attribute.IsDefined(method, typeof(InjectAttribute))) { continue; } Type[] paramsTypes = method.GetParameters() .AsValueEnumerable() .Select(t => t.ParameterType) .ToArray(); object[] instances = new object[paramsTypes.Length]; for (int i = 0; i < paramsTypes.Length; ++i) { instances[i] = Resolve(paramsTypes[i]); if (instances[i] == null) { s_logger.Error($"Failed to resolve method parameter of type `{paramsTypes[i]}`"); } } method.Invoke(target, instances); } } bool InjectField(FieldInfo field, object target) { for (int i = m_FieldInjectors.Count - 1; i >= 0; i--) { if (m_FieldInjectors[i].Inject(field, target, this)) { return true; } } return false; } public interface IFieldInjector { bool Inject(FieldInfo field, object target, DIContext context); } class InjectAttributeFieldInjector : IFieldInjector { public bool Inject(FieldInfo field, object target, DIContext context) { if (!Attribute.IsDefined(field, typeof(InjectAttribute))) return false; object instance = context.Resolve(field.FieldType); if (instance == null) { s_logger.Error($"Cannot resolve `{field.FieldType}`"); return false; } field.SetValue(target, instance); return true; } } } }