From 0f334d24a0f53d5906e54489eefc18f44f0ee94e Mon Sep 17 00:00:00 2001 From: Christopher F Date: Wed, 14 Dec 2016 16:12:02 -0500 Subject: [PATCH 1/6] Add Transients/Factories to Dependency Injection --- .../Dependencies/DependencyMap.cs | 25 +++++++++++++++---- .../Dependencies/IDependencyMap.cs | 5 +++- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/Discord.Net.Commands/Dependencies/DependencyMap.cs b/src/Discord.Net.Commands/Dependencies/DependencyMap.cs index ba5995f26..7f489fda0 100644 --- a/src/Discord.Net.Commands/Dependencies/DependencyMap.cs +++ b/src/Discord.Net.Commands/Dependencies/DependencyMap.cs @@ -5,21 +5,29 @@ namespace Discord.Commands { public class DependencyMap : IDependencyMap { - private Dictionary map; + private Dictionary> map; public static DependencyMap Empty => new DependencyMap(); public DependencyMap() { - map = new Dictionary(); + map = new Dictionary>(); } - public void Add(T obj) + public void Add(T obj) where T : class + => AddFactory(() => obj); + public void AddTransient() where T : class, new() + => AddFactory(() => new T()); + public void AddTransient() where TKey : class + where TImpl : class, TKey, new() + => AddFactory(() => new TImpl()); + + public void AddFactory(Func factory) where T : class { var t = typeof(T); if (map.ContainsKey(t)) throw new InvalidOperationException($"The dependency map already contains \"{t.FullName}\""); - map.Add(t, obj); + map.Add(t, factory); } public T Get() @@ -51,7 +59,14 @@ namespace Discord.Commands } public bool TryGet(Type t, out object result) { - return map.TryGetValue(t, out result); + Func func; + if (map.TryGetValue(t, out func)) + { + result = func(); + return true; + } + result = null; + return false; } } } diff --git a/src/Discord.Net.Commands/Dependencies/IDependencyMap.cs b/src/Discord.Net.Commands/Dependencies/IDependencyMap.cs index 784a9bc56..aab94156b 100644 --- a/src/Discord.Net.Commands/Dependencies/IDependencyMap.cs +++ b/src/Discord.Net.Commands/Dependencies/IDependencyMap.cs @@ -4,7 +4,10 @@ namespace Discord.Commands { public interface IDependencyMap { - void Add(T obj); + void Add(T obj) where T : class; + void AddTransient() where T : class, new(); + void AddTransient() where TKey: class where TImpl : class, TKey, new(); + void AddFactory(Func factory) where T : class; T Get(); bool TryGet(out T result); From b33df6ad7712d0330d3bb93b7d75bd6f0db05993 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Wed, 14 Dec 2016 16:38:28 -0500 Subject: [PATCH 2/6] Add InjectAttribute, inject into fields flagged with it from DepMap This allows users to flag a field with InjectAttribute, and when the module is created at runtime, this field will be filled in with the object from the dependency map. --- .../Attributes/InjectAttribute.cs | 9 +++++++++ .../Utilities/ReflectionUtils.cs | 18 +++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 src/Discord.Net.Commands/Attributes/InjectAttribute.cs diff --git a/src/Discord.Net.Commands/Attributes/InjectAttribute.cs b/src/Discord.Net.Commands/Attributes/InjectAttribute.cs new file mode 100644 index 000000000..1ad1d4ee3 --- /dev/null +++ b/src/Discord.Net.Commands/Attributes/InjectAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace Discord.Commands +{ + [AttributeUsage(AttributeTargets.Field)] + public class InjectAttribute : Attribute + { + } +} diff --git a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs index 27ea601bf..b7320ba5a 100644 --- a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs +++ b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs @@ -42,7 +42,23 @@ namespace Discord.Commands try { - return (T)constructor.Invoke(args); + T type = (T)constructor.Invoke(args); + var fields = type.GetType().GetRuntimeFields().Where(p => p.GetCustomAttribute() != null); + foreach (var field in fields) + { + object arg; + if (map == null || !map.TryGet(field.FieldType, out arg)) + { + if (field.FieldType == typeof(CommandService)) + arg = service; + else if (field.FieldType == typeof(IDependencyMap)) + arg = map; + else + throw new InvalidOperationException($"Failed to inject \"{typeInfo.FullName}\", dependency \"{field.FieldType.FullName}\" was not found."); + } + field.SetValue(type, arg); + } + return type; } catch (Exception ex) { From 7fb032c9d2230d02ad3a88b0020f8e5cbe0a39af Mon Sep 17 00:00:00 2001 From: Christopher F Date: Wed, 14 Dec 2016 17:07:24 -0500 Subject: [PATCH 3/6] Make changes per discussion Instead of using fields, we will now use properties (that must have a setter). --- .../Attributes/InjectAttribute.cs | 4 ++-- .../Utilities/ReflectionUtils.cs | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Discord.Net.Commands/Attributes/InjectAttribute.cs b/src/Discord.Net.Commands/Attributes/InjectAttribute.cs index 1ad1d4ee3..836a4c668 100644 --- a/src/Discord.Net.Commands/Attributes/InjectAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/InjectAttribute.cs @@ -2,8 +2,8 @@ namespace Discord.Commands { - [AttributeUsage(AttributeTargets.Field)] - public class InjectAttribute : Attribute + [AttributeUsage(AttributeTargets.Property)] + public sealed class InjectAttribute : Attribute { } } diff --git a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs index b7320ba5a..0bcb24882 100644 --- a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs +++ b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs @@ -42,23 +42,23 @@ namespace Discord.Commands try { - T type = (T)constructor.Invoke(args); - var fields = type.GetType().GetRuntimeFields().Where(p => p.GetCustomAttribute() != null); + T instance = (T)constructor.Invoke(args); + var fields = instance.GetType().GetRuntimeProperties().Where(p => p.GetCustomAttribute() != null).Where(p => p.CanWrite); foreach (var field in fields) { object arg; - if (map == null || !map.TryGet(field.FieldType, out arg)) + if (map == null || !map.TryGet(field.PropertyType, out arg)) { - if (field.FieldType == typeof(CommandService)) + if (field.PropertyType == typeof(CommandService)) arg = service; - else if (field.FieldType == typeof(IDependencyMap)) + else if (field.PropertyType == typeof(IDependencyMap)) arg = map; else - throw new InvalidOperationException($"Failed to inject \"{typeInfo.FullName}\", dependency \"{field.FieldType.FullName}\" was not found."); + throw new InvalidOperationException($"Failed to inject \"{typeInfo.FullName}\", dependency \"{field.PropertyType.FullName}\" was not found."); } - field.SetValue(type, arg); + field.SetValue(instance, arg); } - return type; + return instance; } catch (Exception ex) { From b9b6ac36feeae53576f0d534d32b5c7b3f48920f Mon Sep 17 00:00:00 2001 From: Christopher F Date: Fri, 16 Dec 2016 19:18:27 -0500 Subject: [PATCH 4/6] Add docstrings, per volt's feedback --- .../Attributes/InjectAttribute.cs | 6 +++ .../Dependencies/DependencyMap.cs | 10 ++++- .../Dependencies/IDependencyMap.cs | 44 +++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net.Commands/Attributes/InjectAttribute.cs b/src/Discord.Net.Commands/Attributes/InjectAttribute.cs index 836a4c668..09c0b553b 100644 --- a/src/Discord.Net.Commands/Attributes/InjectAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/InjectAttribute.cs @@ -2,6 +2,12 @@ namespace Discord.Commands { + /// + /// Indicates that this property should be filled in by dependency injection. + /// + /// + /// This property **MUST** have a setter. + /// [AttributeUsage(AttributeTargets.Property)] public sealed class InjectAttribute : Attribute { diff --git a/src/Discord.Net.Commands/Dependencies/DependencyMap.cs b/src/Discord.Net.Commands/Dependencies/DependencyMap.cs index 7f489fda0..0761f5fff 100644 --- a/src/Discord.Net.Commands/Dependencies/DependencyMap.cs +++ b/src/Discord.Net.Commands/Dependencies/DependencyMap.cs @@ -14,14 +14,18 @@ namespace Discord.Commands map = new Dictionary>(); } + /// public void Add(T obj) where T : class => AddFactory(() => obj); + /// public void AddTransient() where T : class, new() => AddFactory(() => new T()); + /// public void AddTransient() where TKey : class where TImpl : class, TKey, new() => AddFactory(() => new TImpl()); - + + /// public void AddFactory(Func factory) where T : class { var t = typeof(T); @@ -30,10 +34,12 @@ namespace Discord.Commands map.Add(t, factory); } + /// public T Get() { return (T)Get(typeof(T)); } + /// public object Get(Type t) { object result; @@ -43,6 +49,7 @@ namespace Discord.Commands return result; } + /// public bool TryGet(out T result) { object untypedResult; @@ -57,6 +64,7 @@ namespace Discord.Commands return false; } } + /// public bool TryGet(Type t, out object result) { Func func; diff --git a/src/Discord.Net.Commands/Dependencies/IDependencyMap.cs b/src/Discord.Net.Commands/Dependencies/IDependencyMap.cs index aab94156b..fb042c304 100644 --- a/src/Discord.Net.Commands/Dependencies/IDependencyMap.cs +++ b/src/Discord.Net.Commands/Dependencies/IDependencyMap.cs @@ -4,15 +4,59 @@ namespace Discord.Commands { public interface IDependencyMap { + /// + /// Add an instance of a service to be injected. + /// + /// The type of service. + /// The instance of a service. void Add(T obj) where T : class; + /// + /// Add a service that will be injected by a new instance every time. + /// + /// The type of instance to inject. void AddTransient() where T : class, new(); + /// + /// Add a service that will be injected by a new instance every time. + /// + /// The type to look for when injecting. + /// The type to inject when injecting. + /// + /// map.AddTransient<IService, Service> + /// void AddTransient() where TKey: class where TImpl : class, TKey, new(); + /// + /// Add a service that will be injected by a factory. + /// + /// The type to look for when injecting. + /// The factory that returns a type of this service. void AddFactory(Func factory) where T : class; + /// + /// Pull an object from the map. + /// + /// The type of service. + /// An instance of this service. T Get(); + /// + /// Try to pull an object from the map. + /// + /// The type of service. + /// The instance of this service. + /// Whether or not this object could be found in the map. bool TryGet(out T result); + /// + /// Pull an object from the map. + /// + /// The type of service. + /// An instance of this service. object Get(Type t); + /// + /// Try to pull an object from the map. + /// + /// The type of service. + /// An instance of this service. + /// Whether or not this object could be found in the map. bool TryGet(Type t, out object result); } } From 40ede62e4d635081b207cd44108caa68db3e7e04 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sat, 11 Feb 2017 13:38:26 -0500 Subject: [PATCH 5/6] Remove Auto-Injection this should be handled by #520 --- .../Attributes/InjectAttribute.cs | 15 --------------- .../Utilities/ReflectionUtils.cs | 15 --------------- 2 files changed, 30 deletions(-) delete mode 100644 src/Discord.Net.Commands/Attributes/InjectAttribute.cs diff --git a/src/Discord.Net.Commands/Attributes/InjectAttribute.cs b/src/Discord.Net.Commands/Attributes/InjectAttribute.cs deleted file mode 100644 index 09c0b553b..000000000 --- a/src/Discord.Net.Commands/Attributes/InjectAttribute.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Discord.Commands -{ - /// - /// Indicates that this property should be filled in by dependency injection. - /// - /// - /// This property **MUST** have a setter. - /// - [AttributeUsage(AttributeTargets.Property)] - public sealed class InjectAttribute : Attribute - { - } -} diff --git a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs index 0bcb24882..1923734e7 100644 --- a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs +++ b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs @@ -43,21 +43,6 @@ namespace Discord.Commands try { T instance = (T)constructor.Invoke(args); - var fields = instance.GetType().GetRuntimeProperties().Where(p => p.GetCustomAttribute() != null).Where(p => p.CanWrite); - foreach (var field in fields) - { - object arg; - if (map == null || !map.TryGet(field.PropertyType, out arg)) - { - if (field.PropertyType == typeof(CommandService)) - arg = service; - else if (field.PropertyType == typeof(IDependencyMap)) - arg = map; - else - throw new InvalidOperationException($"Failed to inject \"{typeInfo.FullName}\", dependency \"{field.PropertyType.FullName}\" was not found."); - } - field.SetValue(instance, arg); - } return instance; } catch (Exception ex) From 6352cbebeff0f686098176eb477f8509da3cf246 Mon Sep 17 00:00:00 2001 From: Christopher F Date: Sat, 11 Feb 2017 13:53:14 -0500 Subject: [PATCH 6/6] Add TryAdd to DependencyMaps --- .../Dependencies/DependencyMap.cs | 18 +++++++++++++ .../Dependencies/IDependencyMap.cs | 27 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/Discord.Net.Commands/Dependencies/DependencyMap.cs b/src/Discord.Net.Commands/Dependencies/DependencyMap.cs index 0761f5fff..f5adf1a8c 100644 --- a/src/Discord.Net.Commands/Dependencies/DependencyMap.cs +++ b/src/Discord.Net.Commands/Dependencies/DependencyMap.cs @@ -18,12 +18,21 @@ namespace Discord.Commands public void Add(T obj) where T : class => AddFactory(() => obj); /// + public bool TryAdd(T obj) where T : class + => TryAddFactory(() => obj); + /// public void AddTransient() where T : class, new() => AddFactory(() => new T()); /// + public bool TryAddTransient() where T : class, new() + => TryAddFactory(() => new T()); + /// public void AddTransient() where TKey : class where TImpl : class, TKey, new() => AddFactory(() => new TImpl()); + public bool TryAddTransient() where TKey : class + where TImpl : class, TKey, new() + => TryAddFactory(() => new TImpl()); /// public void AddFactory(Func factory) where T : class @@ -33,6 +42,15 @@ namespace Discord.Commands throw new InvalidOperationException($"The dependency map already contains \"{t.FullName}\""); map.Add(t, factory); } + /// + public bool TryAddFactory(Func factory) where T : class + { + var t = typeof(T); + if (map.ContainsKey(t)) + return false; + map.Add(t, factory); + return true; + } /// public T Get() diff --git a/src/Discord.Net.Commands/Dependencies/IDependencyMap.cs b/src/Discord.Net.Commands/Dependencies/IDependencyMap.cs index fb042c304..a55a9e4c5 100644 --- a/src/Discord.Net.Commands/Dependencies/IDependencyMap.cs +++ b/src/Discord.Net.Commands/Dependencies/IDependencyMap.cs @@ -11,11 +11,24 @@ namespace Discord.Commands /// The instance of a service. void Add(T obj) where T : class; /// + /// Tries to add an instance of a service to be injected. + /// + /// The type of service. + /// The instance of a service. + /// A bool, indicating if the service was successfully added to the DependencyMap. + bool TryAdd(T obj) where T : class; + /// /// Add a service that will be injected by a new instance every time. /// /// The type of instance to inject. void AddTransient() where T : class, new(); /// + /// Tries to add a service that will be injected by a new instance every time. + /// + /// The type of instance to inject. + /// A bool, indicating if the service was successfully added to the DependencyMap. + bool TryAddTransient() where T : class, new(); + /// /// Add a service that will be injected by a new instance every time. /// /// The type to look for when injecting. @@ -25,11 +38,25 @@ namespace Discord.Commands /// void AddTransient() where TKey: class where TImpl : class, TKey, new(); /// + /// Tries to add a service that will be injected by a new instance every time. + /// + /// The type to look for when injecting. + /// The type to inject when injecting. + /// A bool, indicating if the service was successfully added to the DependencyMap. + bool TryAddTransient() where TKey : class where TImpl : class, TKey, new(); + /// /// Add a service that will be injected by a factory. /// /// The type to look for when injecting. /// The factory that returns a type of this service. void AddFactory(Func factory) where T : class; + /// + /// Tries to add a service that will be injected by a factory. + /// + /// The type to look for when injecting. + /// The factory that returns a type of this service. + /// A bool, indicating if the service was successfully added to the DependencyMap. + bool TryAddFactory(Func factory) where T : class; /// /// Pull an object from the map.