diff --git a/VContainer/Assets/VContainer/Runtime/ContainerBuilder.cs b/VContainer/Assets/VContainer/Runtime/ContainerBuilder.cs index 731611c1..3f81aa8e 100644 --- a/VContainer/Assets/VContainer/Runtime/ContainerBuilder.cs +++ b/VContainer/Assets/VContainer/Runtime/ContainerBuilder.cs @@ -12,8 +12,8 @@ public interface IContainerBuilder object ApplicationOrigin { get; set; } RegistrationBuilder Register(Type type, Lifetime lifetime); - RegistrationBuilder RegisterInstance(object instance); RegistrationBuilder Register(RegistrationBuilder registrationBuilder); + void RegisterBuildCallback(Action container); bool Exists(Type type, bool includeInterfaceTypes = false); @@ -52,9 +52,6 @@ public class ContainerBuilder : IContainerBuilder public RegistrationBuilder Register(Type type, Lifetime lifetime) => Register(new RegistrationBuilder(type, lifetime)); - public RegistrationBuilder RegisterInstance(object instance) - => Register(new RegistrationBuilder(instance)); - public RegistrationBuilder Register(RegistrationBuilder registrationBuilder) { registrationBuilders.Add(registrationBuilder); diff --git a/VContainer/Assets/VContainer/Runtime/ContainerBuilderExtensions.cs b/VContainer/Assets/VContainer/Runtime/ContainerBuilderExtensions.cs index 62cf0b13..574c9985 100644 --- a/VContainer/Assets/VContainer/Runtime/ContainerBuilderExtensions.cs +++ b/VContainer/Assets/VContainer/Runtime/ContainerBuilderExtensions.cs @@ -28,18 +28,26 @@ public static RegistrationBuilder Register builder.Register(lifetime).As(typeof(TInterface1), typeof(TInterface2), typeof(TInterface3)); + public static RegistrationBuilder Register( + this IContainerBuilder builder, + Func implementationConfiguration, + Lifetime lifetime) + where TInterface : class + => builder.Register(new FuncRegistrationBuilder(implementationConfiguration, typeof(TInterface), lifetime)); + public static RegistrationBuilder RegisterInstance( this IContainerBuilder builder, TInterface instance) - => builder.RegisterInstance(instance).As(typeof(TInterface)); + => builder.Register(new InstanceRegistrationBuilder(instance)).As(typeof(TInterface)); public static RegistrationBuilder RegisterInstance( this IContainerBuilder builder, - object instance) + TInterface1 instance) => builder.RegisterInstance(instance).As(typeof(TInterface1), typeof(TInterface2)); public static RegistrationBuilder RegisterInstance( - this IContainerBuilder builder, object instance) + this IContainerBuilder builder, + TInterface1 instance) => builder.RegisterInstance(instance).As(typeof(TInterface1), typeof(TInterface2), typeof(TInterface3)); public static RegistrationBuilder RegisterFactory( diff --git a/VContainer/Assets/VContainer/Runtime/Internal/FuncRegistration.cs b/VContainer/Assets/VContainer/Runtime/Internal/FuncRegistration.cs new file mode 100644 index 00000000..905f2c0f --- /dev/null +++ b/VContainer/Assets/VContainer/Runtime/Internal/FuncRegistration.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; + +namespace VContainer.Internal +{ + sealed class FuncRegistration : IRegistration + { + public Type ImplementationType { get; } + public IReadOnlyList InterfaceTypes { get; } + public Lifetime Lifetime { get; } + + readonly Func implementationProvider; + + public FuncRegistration( + Func implementationProvider, + Type implementationType, + Lifetime lifetime, + IReadOnlyList interfaceTypes) + { + ImplementationType = implementationType; + Lifetime = lifetime; + InterfaceTypes = interfaceTypes; + + this.implementationProvider = implementationProvider; + } + + public object SpawnInstance(IObjectResolver resolver) => implementationProvider(resolver); + } +} \ No newline at end of file diff --git a/VContainer/Assets/VContainer/Runtime/Internal/FuncRegistration.cs.meta b/VContainer/Assets/VContainer/Runtime/Internal/FuncRegistration.cs.meta new file mode 100644 index 00000000..fbc5870f --- /dev/null +++ b/VContainer/Assets/VContainer/Runtime/Internal/FuncRegistration.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a4fb0cd9294e424bb69b28711f95ccc0 +timeCreated: 1619867067 \ No newline at end of file diff --git a/VContainer/Assets/VContainer/Runtime/Internal/FuncRegistrationBuilder.cs b/VContainer/Assets/VContainer/Runtime/Internal/FuncRegistrationBuilder.cs new file mode 100644 index 00000000..90c6c27d --- /dev/null +++ b/VContainer/Assets/VContainer/Runtime/Internal/FuncRegistrationBuilder.cs @@ -0,0 +1,23 @@ +using System; + +namespace VContainer.Internal +{ + sealed class FuncRegistrationBuilder : RegistrationBuilder + { + readonly Func implementationConfiguration; + + public FuncRegistrationBuilder( + Func implementationConfiguration, + Type implementationType, + Lifetime lifetime) : base(implementationType, lifetime) + { + this.implementationConfiguration = implementationConfiguration; + } + + public override IRegistration Build() => new FuncRegistration( + implementationConfiguration, + ImplementationType, + Lifetime, + InterfaceTypes); + } +} diff --git a/VContainer/Assets/VContainer/Runtime/Internal/FuncRegistrationBuilder.cs.meta b/VContainer/Assets/VContainer/Runtime/Internal/FuncRegistrationBuilder.cs.meta new file mode 100644 index 00000000..4267c4ee --- /dev/null +++ b/VContainer/Assets/VContainer/Runtime/Internal/FuncRegistrationBuilder.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 959eabb8a5cb4f198c7e96512ea177b8 +timeCreated: 1619866780 \ No newline at end of file diff --git a/VContainer/Assets/VContainer/Runtime/Internal/InstanceRegistration.cs b/VContainer/Assets/VContainer/Runtime/Internal/InstanceRegistration.cs new file mode 100644 index 00000000..79d8e0fa --- /dev/null +++ b/VContainer/Assets/VContainer/Runtime/Internal/InstanceRegistration.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Threading; + +namespace VContainer.Internal +{ + sealed class InstanceRegistration : IRegistration + { + public Type ImplementationType { get; } + public IReadOnlyList InterfaceTypes { get; } + public Lifetime Lifetime { get; } + + readonly object implementationInstance; + readonly IInjector injector; + readonly IReadOnlyList parameters; + + long injected; + + public InstanceRegistration( + object implementationInstance, + Type implementationType, + Lifetime lifetime, + IReadOnlyList interfaceTypes, + IReadOnlyList parameters, + IInjector injector) + { + ImplementationType = implementationType; + Lifetime = lifetime; + InterfaceTypes = interfaceTypes; + + this.implementationInstance = implementationInstance; + this.injector = injector; + this.parameters = parameters; + } + + public object SpawnInstance(IObjectResolver resolver) + { + if (Interlocked.CompareExchange(ref injected, 1, 0) == 0) + { + injector.Inject(implementationInstance, resolver, parameters); + } + return implementationInstance; + } + } +} \ No newline at end of file diff --git a/VContainer/Assets/VContainer/Runtime/Internal/InstanceRegistration.cs.meta b/VContainer/Assets/VContainer/Runtime/Internal/InstanceRegistration.cs.meta new file mode 100644 index 00000000..2b2de677 --- /dev/null +++ b/VContainer/Assets/VContainer/Runtime/Internal/InstanceRegistration.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4fcef74c11fc4018bbc88bc395ef8657 +timeCreated: 1619863673 \ No newline at end of file diff --git a/VContainer/Assets/VContainer/Runtime/Internal/InstanceRegistrationBuilder.cs b/VContainer/Assets/VContainer/Runtime/Internal/InstanceRegistrationBuilder.cs new file mode 100644 index 00000000..8ecd51a2 --- /dev/null +++ b/VContainer/Assets/VContainer/Runtime/Internal/InstanceRegistrationBuilder.cs @@ -0,0 +1,26 @@ +namespace VContainer.Internal +{ + sealed class InstanceRegistrationBuilder : RegistrationBuilder + { + readonly object implementationInstance; + + public InstanceRegistrationBuilder(object implementationInstance) + : base(implementationInstance.GetType(), Lifetime.Singleton) + { + this.implementationInstance = implementationInstance; + } + + public override IRegistration Build() + { + var injector = InjectorCache.GetOrBuild(ImplementationType); + + return new InstanceRegistration( + implementationInstance, + ImplementationType, + Lifetime, + InterfaceTypes, + Parameters, + injector); + } + } +} \ No newline at end of file diff --git a/VContainer/Assets/VContainer/Runtime/Internal/InstanceRegistrationBuilder.cs.meta b/VContainer/Assets/VContainer/Runtime/Internal/InstanceRegistrationBuilder.cs.meta new file mode 100644 index 00000000..72123ca3 --- /dev/null +++ b/VContainer/Assets/VContainer/Runtime/Internal/InstanceRegistrationBuilder.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 097b02c25ccf47df97386afb6a4f0c21 +timeCreated: 1619863604 \ No newline at end of file diff --git a/VContainer/Assets/VContainer/Runtime/Internal/Registration.cs b/VContainer/Assets/VContainer/Runtime/Internal/Registration.cs index 0585603e..5441af0c 100644 --- a/VContainer/Assets/VContainer/Runtime/Internal/Registration.cs +++ b/VContainer/Assets/VContainer/Runtime/Internal/Registration.cs @@ -11,38 +11,27 @@ sealed class Registration : IRegistration public Lifetime Lifetime { get; } readonly IInjector injector; - readonly IReadOnlyList parameters; - readonly object specificInstance; internal Registration( Type implementationType, Lifetime lifetime, IReadOnlyList interfaceTypes, IReadOnlyList parameters, - IInjector injector, - object specificInstance) + IInjector injector) { ImplementationType = implementationType; InterfaceTypes = interfaceTypes; Lifetime = lifetime; this.injector = injector; - this.specificInstance = specificInstance; this.parameters = parameters; } public override string ToString() => $"ConcreteType={ImplementationType.Name} ContractTypes={string.Join(", ", InterfaceTypes)} {Lifetime} {injector.GetType().Name}"; public object SpawnInstance(IObjectResolver resolver) - { - if (specificInstance != null) - { - injector.Inject(specificInstance, resolver, parameters); - return specificInstance; - } - return injector.CreateInstance(resolver, parameters); - } + => injector.CreateInstance(resolver, parameters); } sealed class CollectionRegistration : IRegistration, IEnumerable diff --git a/VContainer/Assets/VContainer/Runtime/RegistrationBuilder.cs b/VContainer/Assets/VContainer/Runtime/RegistrationBuilder.cs index 658bce6f..dce68d87 100644 --- a/VContainer/Assets/VContainer/Runtime/RegistrationBuilder.cs +++ b/VContainer/Assets/VContainer/Runtime/RegistrationBuilder.cs @@ -10,25 +10,16 @@ public class RegistrationBuilder internal readonly Type ImplementationType; internal readonly Lifetime Lifetime; - internal readonly object SpecificInstance; internal List InterfaceTypes; internal List Parameters; - public RegistrationBuilder(Type implementationType, Lifetime lifetime, List interfaceTypes = null) + public RegistrationBuilder(Type implementationType, Lifetime lifetime) { ImplementationType = implementationType; - InterfaceTypes = interfaceTypes; Lifetime = lifetime; } - public RegistrationBuilder(object instance) - { - ImplementationType = instance.GetType(); - Lifetime = Lifetime.Singleton; - SpecificInstance = instance; - } - public virtual IRegistration Build() { var injector = InjectorCache.GetOrBuild(ImplementationType); @@ -38,8 +29,7 @@ public virtual IRegistration Build() Lifetime, InterfaceTypes, Parameters, - injector, - SpecificInstance); + injector); } public RegistrationBuilder As() diff --git a/VContainer/Assets/VContainer/Runtime/Unity/ComponentRegistrationBuilder.cs b/VContainer/Assets/VContainer/Runtime/Unity/ComponentRegistrationBuilder.cs index 072d6f15..9524ec47 100644 --- a/VContainer/Assets/VContainer/Runtime/Unity/ComponentRegistrationBuilder.cs +++ b/VContainer/Assets/VContainer/Runtime/Unity/ComponentRegistrationBuilder.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; using VContainer.Internal; @@ -19,9 +18,8 @@ public sealed class ComponentRegistrationBuilder : RegistrationBuilder internal ComponentRegistrationBuilder( Component prefab, Type implementationType, - Lifetime lifetime, - List interfaceTypes = null) - : this(false, default, implementationType, lifetime, interfaceTypes) + Lifetime lifetime) + : this(false, default, implementationType, lifetime) { this.prefab = prefab; } @@ -29,18 +27,14 @@ internal ComponentRegistrationBuilder( internal ComponentRegistrationBuilder( string gameObjectName, Type implementationType, - Lifetime lifetime, - List interfaceTypes = null) - : this(false, default, implementationType, lifetime, interfaceTypes) + Lifetime lifetime) + : this(false, default, implementationType, lifetime) { this.gameObjectName = gameObjectName; } - internal ComponentRegistrationBuilder( - Scene scene, - Type implementationType, - List interfaceTypes = null) - : this(true, scene, implementationType, Lifetime.Scoped, interfaceTypes) + internal ComponentRegistrationBuilder(Scene scene, Type implementationType) + : this(true, scene, implementationType, Lifetime.Scoped) { } @@ -48,9 +42,8 @@ internal ComponentRegistrationBuilder( bool find, Scene scene, Type implementationType, - Lifetime lifetime, - List interfaceTypes = null) - : base(implementationType, lifetime, interfaceTypes) + Lifetime lifetime) + : base(implementationType, lifetime) { this.find = find; this.scene = scene; diff --git a/VContainer/Assets/VContainer/Runtime/Unity/SystemRegistrationBuilder.cs b/VContainer/Assets/VContainer/Runtime/Unity/SystemRegistrationBuilder.cs index e23f1654..798339da 100644 --- a/VContainer/Assets/VContainer/Runtime/Unity/SystemRegistrationBuilder.cs +++ b/VContainer/Assets/VContainer/Runtime/Unity/SystemRegistrationBuilder.cs @@ -11,16 +11,15 @@ public sealed class SystemRegistrationBuilder : RegistrationBuilder readonly string worldName; Type systemGroupType; - internal SystemRegistrationBuilder( - Type implementationType, - string worldName, - List interfaceTypes = null) - : base(implementationType, default, interfaceTypes) + internal SystemRegistrationBuilder(Type implementationType, string worldName) + : base(implementationType, default) { this.worldName = worldName; - InterfaceTypes = InterfaceTypes ?? new List(); - InterfaceTypes.Add(typeof(ComponentSystemBase)); - InterfaceTypes.Add(ImplementationType); + InterfaceTypes = new List + { + typeof(ComponentSystemBase), + implementationType + }; } public override IRegistration Build() diff --git a/VContainer/Assets/VContainer/Tests/ContainerTest.cs b/VContainer/Assets/VContainer/Tests/ContainerTest.cs index 1fec11d2..9fece785 100644 --- a/VContainer/Assets/VContainer/Tests/ContainerTest.cs +++ b/VContainer/Assets/VContainer/Tests/ContainerTest.cs @@ -252,22 +252,20 @@ public void RegisterInstance() var builder = new ContainerBuilder(); var instance1 = new NoDependencyServiceB(); var instance2 = new MultipleInterfaceServiceA(); - builder.RegisterInstance(instance1); - builder.RegisterInstance(instance2); + builder.RegisterInstance(instance1); + builder.RegisterInstance(instance2); var container = builder.Build(); - var resolve1a = container.Resolve(); - var resolve1b = container.Resolve(); + var resolve1a = container.Resolve(); + var resolve1b = container.Resolve(); + var resolve2a = container.Resolve(); + var resolve2b = container.Resolve(); Assert.That(resolve1a, Is.EqualTo(instance1)); Assert.That(resolve1b, Is.EqualTo(instance1)); - Assert.Throws(() => container.Resolve()); - - var resolve2a = container.Resolve(); - var resolve2b = container.Resolve(); Assert.That(resolve2a, Is.EqualTo(instance2)); Assert.That(resolve2b, Is.EqualTo(instance2)); - Assert.Throws(() => container.Resolve()); + Assert.Throws(() => container.Resolve()); } [Test] @@ -281,6 +279,58 @@ public void RegisterInstanceWithEnum() Assert.That(enumResolved, Is.EqualTo(Lifetime.Scoped)); } + [Test] + public void RegisterFromFunc() + { + var builder = new ContainerBuilder(); + builder.Register(Lifetime.Transient); + + builder.Register(c => + { + return new ServiceA(c.Resolve()); + }, Lifetime.Scoped); + + var container = builder.Build(); + var resolved = container.Resolve(); + Assert.That(resolved, Is.InstanceOf()); + Assert.That(resolved.Service2, Is.InstanceOf()); + } + + [Test] + public void RegisterFromFuncWithInterface() + { + var builder = new ContainerBuilder(); + builder.Register(Lifetime.Transient); + + builder.Register(c => + { + return new ServiceA(c.Resolve()); + }, Lifetime.Scoped); + + var container = builder.Build(); + var resolved = container.Resolve(); + Assert.That(resolved, Is.InstanceOf()); + Assert.Throws(() => container.Resolve()); + } + + [Test] + public void RegisterFromFuncWithDisposable() + { + var builder = new ContainerBuilder(); + builder.Register(_ => + { + return new DisposableServiceA(); + }, Lifetime.Scoped); + + var container = builder.Build(); + var resolved = container.Resolve(); + Assert.That(resolved, Is.InstanceOf()); + Assert.That(resolved.Disposed, Is.False); + + container.Dispose(); + Assert.That(resolved.Disposed, Is.True); + } + [Test] public void RegisterMultipleDisposables() { diff --git a/website/docs/registering/register-type.mdx b/website/docs/registering/register-type.mdx index 158bebd0..c89f3d03 100644 --- a/website/docs/registering/register-type.mdx +++ b/website/docs/registering/register-type.mdx @@ -115,7 +115,7 @@ class GameController : IStartable, ITickable, IDisposable { /* ... */ } ``` ```csharp -builder.RegisterEntryPoint(Lifetime.Singleton); +builder.RegisterEntryPoint(); ``` :::note @@ -192,6 +192,51 @@ builder.RegisterInstance() .AsImplementedInterfaces(); ``` +## Register instance to set up from delegate + +Instance creation can be delegated to a lambda expression or another method or class. + +```csharp +builder.Register(_ => new Foo(), Lifetime.Scoped); +``` + +It can resolve like this: + +```csharp +class ClassA +{ + public ClassA(IFoo foo) { /* ...*/ } +} +``` + +The first argument that can be used in the expression is `IObjectResolver`. +Using this, we can retrieve and use the registered object. + +```csharp +builder.Register(container => +{ + var serviceA = container.Resolve(); + return serviceA.ProvideFoo(); +}, Lifetime.Scoped); +``` + +`IObjectResolver.Instantiate` can also be used to generate GameObjects executed inject. + +```csharp +builder.Register(container => +{ + return container.Instantiate(prefab); +}, Lifetime.Scoped); + +``` +See [Use Container directory](../container-api/use-container-directory) more information. + +:::note +These delegates will be executed only once during scope construction. +If you want to create an instance at any time during runtime, please refer to [Register Factory](./register-factory). +::: + + ## Register type-specific parameters If the types are not unique, but you have a dependency you want to inject at startup, you can use the following: