using System; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace RebootKit.Engine.Foundation { [AttributeUsage(AttributeTargets.Field | AttributeTargets.Method)] public class InjectAttribute : Attribute { } public class DIContext { private const BindingFlags k_fieldsBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; private const BindingFlags k_methodsBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; public interface IFieldInjector { bool Inject(FieldInfo field, object target, DIContext context); } private static readonly Logger Logger = new(nameof(DIContext)); private readonly Dictionary _bindingsMaps = new(); private readonly List _fieldInjectors = new(); public DIContext() { Bind(this); AddInjector(new InjectAttributeFieldInjector()); } public void AddInjector(IFieldInjector injector) { _fieldInjectors.Add(injector); } public void Bind(Type type, object obj) { if (!_bindingsMaps.TryAdd(type, obj)) { 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 (_bindingsMaps.TryGetValue(type, out object obj)) { return obj; } Logger.Error($"Couldn't resolve `{type}`"); return null; } public T Resolve() { return (T) Resolve(typeof(T)); } public void Inject(T target) { Type type = typeof(T); foreach (FieldInfo field in type.GetFields(k_fieldsBindingFlags)) { if (!InjectField(field, target)) { return; } } foreach (MethodInfo method in type.GetMethods(k_methodsBindingFlags)) { if (!Attribute.IsDefined(method, typeof(InjectAttribute))) { continue; } Type[] paramsTypes = method.GetParameters() .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) { Logger.Error($"Failed to resolve method parameter of type `{paramsTypes[i]}`"); } } method.Invoke(target, instances); } } private bool InjectField(FieldInfo field, object target) { for (int i = _fieldInjectors.Count - 1; i >= 0; i--) { if (_fieldInjectors[i].Inject(field, target, this)) { return true; } } return false; } private 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) { Logger.Error($"Cannot resolve `{field.FieldType}`"); return false; } field.SetValue(target, instance); return true; } } } }