using System; using System.Collections.Generic; using System.Linq; using Reflex.Enums; using Reflex.Generics; using Reflex.Resolvers; using UnityEngine.Assertions; namespace Reflex.Core { public sealed class ContainerBuilder { public string Name { get; private set; } public Container Parent { get; private set; } public List Bindings { get; } = new(); public event Action OnContainerBuilt; public Container Build() { var disposables = new DisposableCollection(); var resolversByContract = new Dictionary>(); // Inherited resolvers if (Parent != null) { foreach (var (contract, resolvers) in Parent.ResolversByContract) { resolversByContract[contract] = new List(resolvers); } } // Owned resolvers foreach (var binding in Bindings) { disposables.Add(binding.Resolver); foreach (var contract in binding.Contracts) { if (!resolversByContract.TryGetValue(contract, out var resolvers)) { resolvers = new List(); resolversByContract.Add(contract, resolvers); } resolvers.Add(binding.Resolver); } } var container = new Container(Name, Parent, resolversByContract, disposables); foreach (var binding in Bindings) { binding.Resolver.DeclaringContainer = container; } // Eagerly resolve inherited Scoped + Eager bindings if (Parent != null) { var inheritedEagerResolvers = Parent.ResolversByContract .SelectMany(kvp => kvp.Value) .ToHashSet() .Where(r => r.Lifetime == Lifetime.Scoped && r.Resolution == Resolution.Eager); foreach (var resolver in inheritedEagerResolvers) { resolver.Resolve(container); } } // Eagerly resolve self Singleton/Scoped + Eager bindings if (Bindings != null) { var selfEagerResolvers = Bindings .Select(b => b.Resolver) .Where(r => r.Resolution == Resolution.Eager && (r.Lifetime is Lifetime.Singleton or Lifetime.Scoped)); foreach (var resolver in selfEagerResolvers) { resolver.Resolve(container); } } Bindings.Clear(); OnContainerBuilt?.Invoke(container); return container; } public ContainerBuilder SetName(string name) { Name = name; return this; } public ContainerBuilder SetParent(Container parent) { Parent = parent; return this; } public bool HasBinding(Type contract) { return Bindings.Any(binding => binding.Contracts.Contains(contract)); } public ContainerBuilder RegisterType(Type type, Lifetime lifetime, Resolution resolution) { return RegisterType(type, new[] { type }, lifetime, resolution); } public ContainerBuilder RegisterType(Type type, Type[] contracts, Lifetime lifetime, Resolution resolution) { Assert.IsNotNull(type); Assert.IsTrue(contracts != null && contracts.Length > 0); Assert.IsFalse(lifetime == Lifetime.Transient && resolution == Resolution.Eager, "Type registration Lifetime.Transient + Resolution.Eager not allowed"); IResolver resolver = lifetime switch { Lifetime.Singleton => new SingletonTypeResolver(type, resolution), Lifetime.Transient => new TransientTypeResolver(type), Lifetime.Scoped => new ScopedTypeResolver(type, resolution), _ => throw new ArgumentOutOfRangeException(nameof(lifetime), lifetime, "Unhandled lifetime in ContainerBuilder.RegisterType() method.") }; return Add(type, contracts, resolver); } public ContainerBuilder RegisterValue(object value) { return RegisterValue(value, new[] { value.GetType() }); } public ContainerBuilder RegisterValue(object value, Type[] contracts) { Assert.IsTrue(contracts != null && contracts.Length > 0); var resolver = new SingletonValueResolver(value); return Add(value.GetType(), contracts, resolver); } public ContainerBuilder RegisterFactory(Func factory, Lifetime lifetime, Resolution resolution) { return RegisterFactory(factory, new[] { typeof(T) }, lifetime, resolution); } public ContainerBuilder RegisterFactory(Func factory, Type[] contracts, Lifetime lifetime, Resolution resolution) { Assert.IsNotNull(factory); Assert.IsTrue(contracts != null && contracts.Length > 0); Assert.IsFalse(lifetime == Lifetime.Transient && resolution == Resolution.Eager, "Factory registration Lifetime.Transient + Resolution.Eager not allowed"); object TypelessFactory(Container container) { return factory.Invoke(container); } IResolver resolver = lifetime switch { Lifetime.Singleton => new SingletonFactoryResolver(TypelessFactory, resolution), Lifetime.Transient => new TransientFactoryResolver(TypelessFactory), Lifetime.Scoped => new ScopedFactoryResolver(TypelessFactory, resolution), _ => throw new ArgumentOutOfRangeException(nameof(lifetime), lifetime, "Unhandled lifetime in ContainerBuilder.RegisterFactory() method.") }; return Add(typeof(T), contracts, resolver); } public ContainerBuilder RegisterFactory(Func factory, Type concreteType, Type[] contracts, Lifetime lifetime, Resolution resolution) { Assert.IsNotNull(factory); Assert.IsTrue(contracts != null && contracts.Length > 0); Assert.IsTrue(contracts != null && contracts.Length > 0); Assert.IsFalse(lifetime == Lifetime.Transient && resolution == Resolution.Eager, "Factory registration Lifetime.Transient + Resolution.Eager not allowed"); IResolver resolver = lifetime switch { Lifetime.Singleton => new SingletonFactoryResolver(factory, resolution), Lifetime.Transient => new TransientFactoryResolver(factory), Lifetime.Scoped => new ScopedFactoryResolver(factory, resolution), _ => throw new ArgumentOutOfRangeException(nameof(lifetime), lifetime, "Unhandled lifetime in ContainerBuilder.RegisterFactory() method.") }; return Add(concreteType, contracts, resolver); } private ContainerBuilder Add(Type concrete, Type[] contracts, IResolver resolver) { var binding = Binding.Validated(resolver, concrete, contracts); Bindings.Add(binding); return this; } } }