diff --git a/ProjectCeilidh.Cobble.Tests/AsyncCobbleContextTests.cs b/ProjectCeilidh.Cobble.Tests/AsyncCobbleContextTests.cs index 59b5138..53ffa7a 100644 --- a/ProjectCeilidh.Cobble.Tests/AsyncCobbleContextTests.cs +++ b/ProjectCeilidh.Cobble.Tests/AsyncCobbleContextTests.cs @@ -108,6 +108,32 @@ public async Task DictInstanceGenerator() Assert.Equal("Hi", testUnit.TestValue); } + [Fact] + public async Task DisposeTest() + { + _context.AddManaged(); + + await _context.ExecuteAsync(); + Assert.True(_context.TryGetSingleton(out DisposeTestUnit testUnit) && !testUnit.IsDisposed); + _context.Dispose(); + Assert.True(testUnit.IsDisposed); + } + + private class DisposeTestUnit : IDisposable + { + public bool IsDisposed { get; private set; } + + public DisposeTestUnit() + { + IsDisposed = false; + } + + public void Dispose() + { + IsDisposed = true; + } + } + private interface ITestUnit { string TestValue { get; } diff --git a/ProjectCeilidh.Cobble.Tests/CobbleContextTests.cs b/ProjectCeilidh.Cobble.Tests/CobbleContextTests.cs index a601a76..e036a3c 100644 --- a/ProjectCeilidh.Cobble.Tests/CobbleContextTests.cs +++ b/ProjectCeilidh.Cobble.Tests/CobbleContextTests.cs @@ -7,7 +7,7 @@ namespace ProjectCeilidh.Cobble.Tests { - public class CobbleContextTests + public class CobbleContextTests : IDisposable { private readonly CobbleContext _context; @@ -107,6 +107,32 @@ public void DictInstanceGenerator() Assert.Equal("Hi", testUnit.TestValue); } + [Fact] + public void DisposeTest() + { + _context.AddManaged(); + + _context.Execute(); + Assert.True(_context.TryGetSingleton(out DisposeTestUnit testUnit) && !testUnit.IsDisposed); + _context.Dispose(); + Assert.True(testUnit.IsDisposed); + } + + private class DisposeTestUnit : IDisposable + { + public bool IsDisposed { get; private set; } + + public DisposeTestUnit() + { + IsDisposed = false; + } + + public void Dispose() + { + IsDisposed = true; + } + } + private interface ITestUnit { string TestValue { get; } @@ -151,5 +177,10 @@ public void UnitLoaded(ITestUnit unit) TestUnits.Add(unit); } } + + public void Dispose() + { + _context.Dispose(); + } } } diff --git a/ProjectCeilidh.Cobble/CobbleContext.cs b/ProjectCeilidh.Cobble/CobbleContext.cs index 4b30079..2e1d963 100644 --- a/ProjectCeilidh.Cobble/CobbleContext.cs +++ b/ProjectCeilidh.Cobble/CobbleContext.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Concurrent; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using ProjectCeilidh.Cobble.Data; using ProjectCeilidh.Cobble.Generator; @@ -11,7 +12,8 @@ namespace ProjectCeilidh.Cobble /// /// An application context which allows for dependency graph construction and object creation. /// - public sealed class CobbleContext + /// + public sealed class CobbleContext : IDisposable { public delegate object DuplicateResolutionHandler(Type dependencyType, object[] instances); @@ -22,6 +24,7 @@ public sealed class CobbleContext private readonly List _instanceGenerators; private readonly ConcurrentDictionary> _lateInjectInstances; private readonly ConcurrentDictionary> _implementations; + private readonly ConcurrentBag _disposeHooks; /// /// Construct a new CobbleContext. @@ -31,6 +34,7 @@ public CobbleContext() _instanceGenerators = new List(); _lateInjectInstances = new ConcurrentDictionary>(); _implementations = new ConcurrentDictionary>(); + _disposeHooks = new ConcurrentBag(); AddUnmanaged(this); } @@ -94,7 +98,7 @@ public void AddManaged(Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); - if (type.GetConstructors().Length != 1) + if (type.GetTypeInfo().DeclaredConstructors.Count() != 1) throw new ArgumentException("Managed types must have exactly one public constructor.", nameof(type)); AddManaged(new TypeLateInstanceGenerator(type)); @@ -113,13 +117,13 @@ public void AddManaged(IInstanceGenerator generator) else { var inst = CreateInstance(generator, _implementations); - PushInstanceProvides(generator, inst, _implementations); + PushInstanceProvides(generator, inst); foreach(var prov in generator.Provides) { if (!_lateInjectInstances.TryGetValue(prov, out var set)) continue; foreach (var late in set) - late.GetType().GetMethod(nameof(ILateInject.UnitLoaded), new[] { prov })?.Invoke(late, new []{ inst }); + late.GetType().GetRuntimeMethod(nameof(ILateInject.UnitLoaded), new[] { prov })?.Invoke(late, new []{ inst }); } } } @@ -135,7 +139,7 @@ public void Execute() // Create a lookup which maps provided type to the set off all generators that provide it. var implMap = _instanceGenerators - .SelectMany(x => x.Provides.Select(y => (Type: y, Generator: x))) + .SelectMany(x => x.Provides.Select(y => new { Type = y, Generator = x })) .ToLookup(x => x.Type, x => x.Generator); var graph = new DirectedGraph(_instanceGenerators); @@ -147,7 +151,7 @@ public void Execute() var depType = dep; if (dep.IsConstructedGenericType && dep.GetGenericTypeDefinition() == typeof(IEnumerable<>)) - depType = dep.GetGenericArguments()[0]; + depType = dep.GenericTypeArguments[0]; foreach (var impl in implMap[depType]) graph.Link(impl, gen); @@ -159,7 +163,7 @@ public void Execute() foreach (var gen in graph.TopologicalSort()) // Sort the dependency graph topologically - all dependencies should be satisfied by the time we get to each unit { var obj = CreateInstance(gen, _implementations); - PushInstanceProvides(gen, obj, _implementations); + PushInstanceProvides(gen, obj); if (!(gen is ILateInstanceGenerator late)) continue; @@ -187,7 +191,7 @@ public async Task ExecuteAsync() // Create a lookup which maps provided type to the set off all generators that provide it. var implMap = _instanceGenerators - .SelectMany(x => x.Provides.Select(y => (Type: y, Generator: x))) + .SelectMany(x => x.Provides.Select(y => new { Type = y, Generator = x })) .ToLookup(x => x.Type, x => x.Generator); var graph = new DirectedGraph(_instanceGenerators); @@ -199,7 +203,7 @@ public async Task ExecuteAsync() var depType = dep; if (dep.IsConstructedGenericType && dep.GetGenericTypeDefinition() == typeof(IEnumerable<>)) - depType = dep.GetGenericArguments()[0]; + depType = dep.GenericTypeArguments[0]; foreach (var impl in implMap[depType]) graph.Link(impl, gen); @@ -211,7 +215,7 @@ public async Task ExecuteAsync() await graph.ParallelTopologicalSort(gen => { var obj = CreateInstance(gen, _implementations); - PushInstanceProvides(gen, obj, _implementations); + PushInstanceProvides(gen, obj); if (!(gen is ILateInstanceGenerator late)) return; @@ -230,20 +234,29 @@ await graph.ParallelTopologicalSort(gen => } } + public void Dispose() + { + while (_disposeHooks.TryTake(out var result)) + result.Dispose(); + } + /// /// Given a generator and an instance, push it into the relevant provider dictionaries /// /// The instance generator that produced the instance. /// The instance that was produced. - /// A dictionary mapping provided types to a set of instances. - private static void PushInstanceProvides(IInstanceGenerator gen, object instance, ConcurrentDictionary> instances) + private void PushInstanceProvides(IInstanceGenerator gen, object instance) { foreach (var prov in gen.Provides) - instances.AddOrUpdate(prov, x => new ConcurrentBag(new[] {instance}), (a, b) => + _implementations.AddOrUpdate(prov, x => new ConcurrentBag(new[] {instance}), (a, b) => { b.Add(instance); return b; }); + + if (instance is IDisposable disp) + _disposeHooks.Add(disp); + } /// @@ -260,7 +273,7 @@ private object CreateInstance(IInstanceGenerator gen, IDictionary)) { - var depType = dep.GetGenericArguments()[0]; + var depType = dep.GenericTypeArguments[0]; if (instances.TryGetValue(depType, out var set)) { diff --git a/ProjectCeilidh.Cobble/Extensions.cs b/ProjectCeilidh.Cobble/Extensions.cs index 11c6188..0c5aae6 100644 --- a/ProjectCeilidh.Cobble/Extensions.cs +++ b/ProjectCeilidh.Cobble/Extensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Reflection; namespace ProjectCeilidh.Cobble { @@ -36,16 +37,16 @@ public static IEnumerable Unroll(this T value, Func> tra public static IEnumerable GetAssignableFrom(this Type type) { - foreach (var intf in type.GetInterfaces()) + foreach (var intf in type.GetTypeInfo().ImplementedInterfaces) yield return intf; yield return type; - var b = type.BaseType; + var b = type.GetTypeInfo().BaseType; while (b != null && b != typeof(object)) { - b = b.BaseType; + b = b.GetTypeInfo().BaseType; yield return b; } } diff --git a/ProjectCeilidh.Cobble/Generator/BareLateInstanceGenerator.cs b/ProjectCeilidh.Cobble/Generator/BareLateInstanceGenerator.cs index e5b3c4f..474fc7f 100644 --- a/ProjectCeilidh.Cobble/Generator/BareLateInstanceGenerator.cs +++ b/ProjectCeilidh.Cobble/Generator/BareLateInstanceGenerator.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; namespace ProjectCeilidh.Cobble.Generator { @@ -21,7 +22,7 @@ public BareLateInstanceGenerator(object instance) _instance = instance ?? throw new ArgumentNullException(nameof(instance)); var type = _instance.GetType(); - LateDependencies = type.GetInterfaces().Where(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(ILateInject<>)).Select(x => x.GetGenericArguments()[0]).ToArray(); + LateDependencies = type.GetTypeInfo().ImplementedInterfaces.Where(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(ILateInject<>)).Select(x => x.GenericTypeArguments[0]).ToArray(); Provides = type.GetAssignableFrom().ToArray(); } diff --git a/ProjectCeilidh.Cobble/Generator/DictionaryInstanceGenerator.cs b/ProjectCeilidh.Cobble/Generator/DictionaryInstanceGenerator.cs index 55156a0..fe28dfa 100644 --- a/ProjectCeilidh.Cobble/Generator/DictionaryInstanceGenerator.cs +++ b/ProjectCeilidh.Cobble/Generator/DictionaryInstanceGenerator.cs @@ -11,7 +11,7 @@ namespace ProjectCeilidh.Cobble.Generator /// public class DictionaryInstanceGenerator : IInstanceGenerator { - private static readonly MethodInfo DispatchProxyCreate = typeof(DispatchProxy).GetMethod(nameof(DispatchProxy.Create)) ?? throw new Exception("Cannot find DispatchProxy.Create - this should be impossible."); + private static readonly MethodInfo DispatchProxyCreate = typeof(DispatchProxy).GetRuntimeMethod(nameof(DispatchProxy.Create), new Type[0]) ?? throw new Exception("Cannot find DispatchProxy.Create - this should be impossible."); public IEnumerable Provides { get; } public IEnumerable Dependencies { get; } @@ -28,8 +28,8 @@ public class DictionaryInstanceGenerator : IInstanceGenerator /// A dictionary containing all implemented functions as delegates. public DictionaryInstanceGenerator(Type contractType, Delegate ctor, IReadOnlyDictionary implementations) { - Dependencies = ctor == null ? new Type[0] : ctor.Method.GetParameters().Select(x => x.ParameterType).ToArray(); - Provides = contractType.GetInterfaces().Concat(new []{ contractType }).Concat(contractType.Unroll(x => x.BaseType == typeof(object) || x.BaseType == null ? new Type[0] : new []{ x.BaseType })); + Dependencies = ctor == null ? new Type[0] : ctor.GetMethodInfo().GetParameters().Select(x => x.ParameterType).ToArray(); + Provides = contractType.GetTypeInfo().ImplementedInterfaces.Concat(new []{ contractType }).Concat(contractType.Unroll(x => x.GetTypeInfo().BaseType == typeof(object) || x.GetTypeInfo().BaseType == null ? new Type[0] : new []{ x.GetTypeInfo().BaseType })); _contractType = contractType; _ctor = ctor; _implementations = implementations; diff --git a/ProjectCeilidh.Cobble/Generator/TypeLateInstanceGenerator.cs b/ProjectCeilidh.Cobble/Generator/TypeLateInstanceGenerator.cs index 2b9a530..3aa9eb3 100644 --- a/ProjectCeilidh.Cobble/Generator/TypeLateInstanceGenerator.cs +++ b/ProjectCeilidh.Cobble/Generator/TypeLateInstanceGenerator.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; namespace ProjectCeilidh.Cobble.Generator { @@ -19,14 +20,14 @@ public class TypeLateInstanceGenerator : ILateInstanceGenerator public TypeLateInstanceGenerator(Type target) { _target = target; - Dependencies = target.GetConstructors().Single().GetParameters().Select(x => x.ParameterType).ToArray(); + Dependencies = target.GetTypeInfo().DeclaredConstructors.Single().GetParameters().Select(x => x.ParameterType).ToArray(); Provides = target.GetAssignableFrom().ToArray(); - LateDependencies = target.GetInterfaces().Where(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(ILateInject<>)).Select(x => x.GetGenericArguments()[0]).ToArray(); + LateDependencies = target.GetTypeInfo().ImplementedInterfaces.Where(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(ILateInject<>)).Select(x => x.GenericTypeArguments[0]).ToArray(); } public object GenerateInstance(object[] args) { - var ctor = _target.GetConstructors().Single(); + var ctor = _target.GetTypeInfo().DeclaredConstructors.Single(); return ctor.Invoke(args); } diff --git a/ProjectCeilidh.Cobble/ProjectCeilidh.Cobble.csproj b/ProjectCeilidh.Cobble/ProjectCeilidh.Cobble.csproj index 9332426..5fbc5b6 100644 --- a/ProjectCeilidh.Cobble/ProjectCeilidh.Cobble.csproj +++ b/ProjectCeilidh.Cobble/ProjectCeilidh.Cobble.csproj @@ -1,6 +1,6 @@  - netstandard2.0 + netstandard1.3 latest Olivia Trewin Project Ceilidh @@ -12,7 +12,7 @@ https://github.com/Ceilidh-Team/Cobble.git 2018 Olivia Trewin true - 1.1.1 + 1.2.0