Skip to content

Commit

Permalink
Adds AddTransient overload that accepts a factory
Browse files Browse the repository at this point in the history
  • Loading branch information
gustavopsantos committed Dec 1, 2023
1 parent b8ae8ce commit 8d246f1
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 4 deletions.
30 changes: 28 additions & 2 deletions Assets/Reflex.Tests/ContainerDescriptorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,20 +78,46 @@ Valuable Factory(Container container)
}

[Test]
public void AddTransient_ValuableWithIDisposableAsContract_ShouldThrow()
public void AddTransientFromType_ValuableWithIDisposableAsContract_ShouldThrow()
{
var builder = new ContainerDescriptor("");
Action addSingleton = () => builder.AddTransient(typeof(Valuable), typeof(IDisposable));
addSingleton.Should().ThrowExactly<ContractDefinitionException>();
}

[Test]
public void AddTransient_ValuableWithObjectAndValuableAndIValuableAsContract_ShouldNotThrow()
public void AddTransientFromType_ValuableWithObjectAndValuableAndIValuableAsContract_ShouldNotThrow()
{
var builder = new ContainerDescriptor("");
Action addSingleton = () => builder.AddTransient(typeof(Valuable), typeof(object), typeof(Valuable), typeof(IValuable));
addSingleton.Should().NotThrow();
}

[Test]
public void AddTransientFromFactory_ValuableWithIDisposableAsContract_ShouldThrow()
{
Valuable Factory(Container container)
{
return new Valuable();
}

var builder = new ContainerDescriptor("");
Action addSingleton = () => builder.AddTransient(Factory, typeof(IDisposable));
addSingleton.Should().ThrowExactly<ContractDefinitionException>();
}

[Test]
public void AddTransientFromFactory_ValuableWithObjectAndValuableAndIValuableAsContract_ShouldNotThrow()
{
Valuable Factory(Container container)
{
return new Valuable();
}

var builder = new ContainerDescriptor("");
Action addSingleton = () => builder.AddTransient(Factory, typeof(object), typeof(Valuable), typeof(IValuable));
addSingleton.Should().NotThrow();
}

[Test]
public void HasBinding_ShouldTrue()
Expand Down
23 changes: 22 additions & 1 deletion Assets/Reflex.Tests/ContainerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public void Resolve_UninstalledValueType_ShouldThrowUnknownContractException()
}

[Test]
public void Resolve_AsTransient_ShouldReturnAlwaysANewInstance()
public void Resolve_AsTransientFromType_ShouldReturnAlwaysANewInstance()
{
var container = new ContainerDescriptor("")
.AddTransient(typeof(Valuable), typeof(IValuable))
Expand All @@ -60,6 +60,27 @@ public void Resolve_AsTransient_ShouldReturnAlwaysANewInstance()
container.Single<IValuable>().Value = 123;
container.Single<IValuable>().Value.Should().Be(default(int));
}

[Test]
public void Resolve_AsTransientFromFactory_ShouldRunFactoryAlways()
{
var callbackAssertion = new CallbackAssertion();

string Factory(Container container)
{
callbackAssertion.Invoke();
return "Hello World!";
}

var container = new ContainerDescriptor("")
.AddTransient(Factory)
.Build();

container.Single<string>().Should().Be("Hello World!");
container.Single<string>().Should().Be("Hello World!");
container.Single<string>().Should().Be("Hello World!");
callbackAssertion.ShouldHaveBeenCalled(3);
}

[Test]
public void Resolve_AsSingletonFromType_ShouldReturnAlwaysSameInstance()
Expand Down
19 changes: 18 additions & 1 deletion Assets/Reflex.Tests/DisposeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Service Factory(Container container)
}

[Test]
public void Transient_ShouldBeDisposed_WhenOwnerIsDisposed()
public void TransientFromType_ShouldBeDisposed_WhenOwnerIsDisposed()
{
var container = new ContainerDescriptor("")
.AddTransient(typeof(Service), typeof(Service))
Expand All @@ -69,5 +69,22 @@ public void Transient_ShouldBeDisposed_WhenOwnerIsDisposed()
container.Dispose();
service.Disposed.Should().Be(1);
}

[Test]
public void TransientFromFactory_ShouldBeDisposed_WhenOwnerIsDisposed()
{
Service Factory(Container container)
{
return new Service();
}

var container = new ContainerDescriptor("")
.AddTransient(Factory, typeof(Service))
.Build();

var service = container.Single<Service>();
container.Dispose();
service.Disposed.Should().Be(1);
}
}
}
16 changes: 16 additions & 0 deletions Assets/Reflex/Core/ContainerDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,22 @@ public ContainerDescriptor AddTransient(Type concrete)
{
return AddTransient(concrete, concrete);
}

public ContainerDescriptor AddTransient<T>(Func<Container, T> factory, params Type[] contracts)
{
var resolver = new TransientFactoryResolver(Proxy, typeof(T));
return Add(typeof(T), contracts, resolver);

object Proxy(Container container)
{
return factory.Invoke(container);
}
}

public ContainerDescriptor AddTransient<T>(Func<Container, T> factory)
{
return AddTransient(factory, typeof(T));
}

private void Build(out DisposableCollection disposables, out Dictionary<Type, List<Resolver>> resolversByContract, out IEnumerable<Resolver> toStart)
{
Expand Down
25 changes: 25 additions & 0 deletions Assets/Reflex/Resolvers/TransientFactoryResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using Reflex.Core;

namespace Reflex.Resolvers
{
internal sealed class TransientFactoryResolver : Resolver
{
private readonly Func<Container, object> _factory;

public TransientFactoryResolver(Func<Container, object> factory, Type concrete)
{
RegisterCallSite();
_factory = factory;
Concrete = concrete;
}

public override object Resolve(Container container)
{
IncrementResolutions();
var instance = _factory.Invoke(container);
Disposables.TryAdd(instance);
return instance;
}
}
}
3 changes: 3 additions & 0 deletions Assets/Reflex/Resolvers/TransientFactoryResolver.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,17 @@ If object implements `IDisposable` it will be disposed when its parent Container
Theres no need to pass `IDisposable` as contract to have your object disposed, howerver, if you want to retrieve all `IDisposable` by any API `Single<TContract>`, `Resolve<TContract>` or `All<TContract>` then yes, you have to specify it.
> Note that `IStartable` also works for **Transients** but pay attention that any resolve API will create a new instance
### AddTransient (From Factory)
```csharp
ContainerDescriptor::AddTransient(Func<Container, T> factory, params Type[] contracts)
```
Adds a defered object creation based on the given factory and its contracts.
The object will be constructed lazyli, once first request to resolve any of its contracts is called.
Then for any request of any contract, a new object will be created, use this carefully.
If object created by factory implements `IDisposable` it will be disposed when its parent Container are disposed.
Theres no need to pass `IDisposable` as contract to have your object disposed, howerver, if you want to retrieve all `IDisposable` by any API `Single<TContract>`, `Resolve<TContract>` or `All<TContract>` then yes, you have to specify it.
> Note that `IStartable` also works for **Transients** but pay attention that any resolve API will create a new instance
## 🔍 Resolving
### Constructor
If your type is non-mono, and its gonna be created by the container, then the most recommended way to inject dependencies into it its by constructor injection.
Expand Down

0 comments on commit 8d246f1

Please sign in to comment.