Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow auto-dispose of factory generated services #179

Merged
merged 2 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 170 additions & 11 deletions src/Jab.FunctionalTests.Common/ContainerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,25 @@ public void DisposingScopeDisposesServices()
[Scoped(typeof(IService), typeof(DisposableServiceImplementation))]
internal partial class DisposingScopeDisposesServicesContainer { }

[Fact]
public void DisposingScopeDisposesFactoryGeneratedServices()
{
DisposingScopeDisposesFactoryGeneratedServicesContainer c = new();
var scope = c.CreateScope();
var service = Assert.IsType<DisposableServiceImplementation>(scope.GetService<IService>());

scope.Dispose();

Assert.Equal(1, service.DisposalCount);
}

[ServiceProvider]
[Scoped(typeof(IService), Factory = nameof(CreateDisposableService))]
internal partial class DisposingScopeDisposesFactoryGeneratedServicesContainer
{
private static IService CreateDisposableService() => new DisposableServiceImplementation();
}

#if NETCOREAPP
[Fact]
public async Task DisposingScopeDisposesAsyncServices()
Expand All @@ -572,6 +591,34 @@ public async Task DisposingScopeDisposesAsyncServices()
[ServiceProvider]
[Scoped(typeof(IService), typeof(AsyncDisposableServiceImplementation))]
internal partial class DisposingScopeDisposesAsyncServicesContainer { }

[Fact]
public async Task DisposingScopeDisposesFactoryGeneratedAsyncServices()
{
DisposingScopeDisposesFactoryGeneratedAsyncServicesContainer c = new();
var scope = c.CreateScope();
var service = Assert.IsType<AsyncDisposableServiceImplementation>(scope.GetService<IService>());

await scope.DisposeAsync();

Assert.Equal(1, service.AsyncDisposalCount);
Assert.Equal(0, service.DisposalCount);

scope = c.CreateScope();
service = Assert.IsType<AsyncDisposableServiceImplementation>(scope.GetService<IService>());

scope.Dispose();

Assert.Equal(0, service.AsyncDisposalCount);
Assert.Equal(1, service.DisposalCount);
}

[ServiceProvider]
[Scoped(typeof(IService), Factory = nameof(CreateService))]
internal partial class DisposingScopeDisposesFactoryGeneratedAsyncServicesContainer
{
private static IService CreateService() => new AsyncDisposableServiceImplementation();
}
#endif

[Fact]
Expand All @@ -589,6 +636,24 @@ public void DisposingProviderDisposesRootScopedServices()
[Scoped(typeof(IService), typeof(DisposableServiceImplementation))]
internal partial class DisposingProviderDisposesRootScopedServicesContainer { }

[Fact]
public void DisposingProviderDisposesFactoryGeneratedRootScopedServicesIfInstructedTo()
{
DisposingProviderDisposesFactoryGeneratedRootScopedServicesContainer c = new();
var service = Assert.IsType<DisposableServiceImplementation>(c.GetService<IService>());

c.Dispose();

Assert.Equal(1, service.DisposalCount);
}

[ServiceProvider]
[Scoped(typeof(IService), Factory = nameof(GenerateService))]
internal partial class DisposingProviderDisposesFactoryGeneratedRootScopedServicesContainer
{
private static IService GenerateService() => new DisposableServiceImplementation();
}

[Fact]
public void DisposingProviderDisposesRootSingletonServices()
{
Expand All @@ -604,6 +669,24 @@ public void DisposingProviderDisposesRootSingletonServices()
[Singleton(typeof(IService), typeof(DisposableServiceImplementation))]
internal partial class DisposingProviderDisposesRootSingletonServicesContainer { }

[Fact]
public void DisposingProviderDisposesFactoryGeneratedRootSingletonServices()
{
DisposingProviderDisposesFactoryGeneratedRootSingletonServicesContainer c = new();
var service = Assert.IsType<DisposableServiceImplementation>(c.GetService<IService>());

c.Dispose();

Assert.Equal(1, service.DisposalCount);
}

[ServiceProvider]
[Singleton(typeof(IService), Factory = nameof(GetService))]
internal partial class DisposingProviderDisposesFactoryGeneratedRootSingletonServicesContainer
{
private static IService GetService() => new DisposableServiceImplementation();
}

#if NETCOREAPP
[Fact]
public async Task DisposingProviderDisposesRootSingAsyncServices()
Expand All @@ -620,6 +703,25 @@ public async Task DisposingProviderDisposesRootSingAsyncServices()
[ServiceProvider]
[Scoped(typeof(IService), typeof(AsyncDisposableServiceImplementation))]
internal partial class DisposingProviderDisposesRootSingAsyncServicesContainer { }

[Fact]
public async Task DisposingProviderDisposesFactoryGeneratedRootSingAsyncServices()
{
DisposingProviderDisposesFactoryGeneratedRootSingAsyncServicesContainer c = new();
var service = Assert.IsType<AsyncDisposableServiceImplementation>(c.GetService<IService>());

await c.DisposeAsync();

Assert.Equal(1, service.AsyncDisposalCount);
Assert.Equal(0, service.DisposalCount);
}

[ServiceProvider]
[Scoped(typeof(IService), Factory = nameof(CreateService))]
internal partial class DisposingProviderDisposesFactoryGeneratedRootSingAsyncServicesContainer
{
private static IService CreateService() => new AsyncDisposableServiceImplementation();
}
#endif

#if NETCOREAPP
Expand All @@ -643,6 +745,30 @@ public async Task DisposingProviderDisposesAllSingletonEnumerableServices()
[Scoped(typeof(IService), typeof(DisposableServiceImplementation))]
[Scoped(typeof(IService), typeof(DisposableServiceImplementation))]
internal partial class DisposingProviderDisposesAllSingletonEnumerableServicesContainer { }

[Fact]
public async Task DisposingProviderDisposesAllFactoryGeneratedSingletonEnumerableServicesWhenInstructedTo()
{
DisposingProviderDisposesAllFactoryGeneratedSingletonEnumerableServicesContainer c = new();
var services = Assert.IsType<IService[]>(c.GetService<IEnumerable<IService>>());

await c.DisposeAsync();

foreach (var service in services)
{
var disposableService = Assert.IsType<DisposableServiceImplementation>(service);
Assert.Equal(1, disposableService.DisposalCount);
}
}

[ServiceProvider]
[Scoped(typeof(IService), Factory = nameof(CreateService))]
[Scoped(typeof(IService), Factory = nameof(CreateService))]
[Scoped(typeof(IService), Factory = nameof(CreateService))]
internal partial class DisposingProviderDisposesAllFactoryGeneratedSingletonEnumerableServicesContainer
{
private static IService CreateService() => new DisposableServiceImplementation();
}
#endif

[Fact]
Expand All @@ -668,6 +794,32 @@ public void DisposingProviderDisposesTransients()
[Transient(typeof(IService), typeof(DisposableServiceImplementation))]
internal partial class DisposingProviderDisposesTransientsContainer { }

[Fact]
public void DisposingProviderDisposesFactoryGeneratedTransients()
{
DisposingProviderDisposesFactoryGeneratedTransientsContainer c = new();
List<IService> services = new();
for (int i = 0; i < 5; i++)
{
services.Add(c.GetService<IService>());
}

c.Dispose();

foreach (var service in services)
{
var disposableService = Assert.IsType<DisposableServiceImplementation>(service);
Assert.Equal(1, disposableService.DisposalCount);
}
}

[ServiceProvider]
[Transient(typeof(IService), Factory = nameof(CreateService))]
internal partial class DisposingProviderDisposesFactoryGeneratedTransientsContainer
{
private static IService CreateService() => new DisposableServiceImplementation();
}

[Fact]
public void DisposingScopeDisposesTransients()
{
Expand All @@ -694,24 +846,31 @@ public void DisposingScopeDisposesTransients()
internal partial class DisposingScopeDisposesTransientsContainer { }

[Fact]
public void DisposingProviderDisposesRootSingletonFactoryServices()
public void DisposingScopeDisposesFactoryGeneratedTransients()
{
DisposingProviderDisposesRootSingletonServicesContainer c = new();
var service = Assert.IsType<DisposableServiceImplementation>(c.GetService<IService>());
DisposingScopeDisposesFactoryGeneratedTransientsContainer c = new();
var scope = c.CreateScope();

c.Dispose();
List<IService> services = new();
for (int i = 0; i < 5; i++)
{
services.Add(scope.GetService<IService>());
}

Assert.Equal(1, service.DisposalCount);
scope.Dispose();

foreach (var service in services)
{
var disposableService = Assert.IsType<DisposableServiceImplementation>(service);
Assert.Equal(1, disposableService.DisposalCount);
}
}

[ServiceProvider]
[Singleton(typeof(IService), Factory = nameof(CreateDisposableServiceImplementation))]
internal partial class DisposingProviderDisposesRootSingletonFactoryServicesContainer
[Transient(typeof(IService), Factory = nameof(CreateService))]
internal partial class DisposingScopeDisposesFactoryGeneratedTransientsContainer
{
internal IService CreateDisposableServiceImplementation()
{
return new DisposableServiceImplementation();
}
private IService CreateService() => new DisposableServiceImplementation();
}

[Fact]
Expand Down
2 changes: 2 additions & 0 deletions src/Jab/Attributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ public SingletonAttribute(Type serviceType, Type implementationType)
class TransientAttribute : Attribute
{
public Type ServiceType { get; }

public string? Name { get; set; }

public Type? ImplementationType { get; }
Expand Down Expand Up @@ -118,6 +119,7 @@ public TransientAttribute(Type serviceType, Type implementationType)
class ScopedAttribute : Attribute
{
public Type ServiceType { get; }

public string? Name { get; set; }

public Type? ImplementationType { get; }
Expand Down
4 changes: 2 additions & 2 deletions src/Jab/ServiceProviderBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ ServiceCallSite BuiltInCallSite(ServiceCallSite callSite)
{
var constructedFactoryMethod = factoryMethod.ConstructedFrom.Construct(genericType.TypeArguments,
genericType.TypeArgumentNullableAnnotations);
callSite = CreateFactoryCallSite(
callSite = CreateFactoryCallSite(
identity,
genericType,
registration.Lifetime,
Expand Down Expand Up @@ -571,7 +571,7 @@ ImmutableArray<IParameterSymbol> GetDelegateParameters(ITypeSymbol type)
parameters.ToArray(),
namedParameters.ToArray(),
lifetime,
false);
true);

return factoryCallSite;
}
Expand Down
12 changes: 1 addition & 11 deletions src/Jab/ServiceRegistration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,6 @@ internal record ServiceRegistration(
ISymbol? InstanceMember,
ISymbol? FactoryMember,
Location? Location,
MemberLocation MemberLocation)
{
public INamedTypeSymbol ServiceType { get; } = ServiceType;
public string? Name { get; } = Name;
public INamedTypeSymbol? ImplementationType { get; } = ImplementationType;
public ISymbol? InstanceMember { get; } = InstanceMember;
public ISymbol? FactoryMember { get; } = FactoryMember;
public ServiceLifetime Lifetime { get; } = Lifetime;
public Location? Location { get; } = Location;
public MemberLocation MemberLocation { get; } = MemberLocation;
}
MemberLocation MemberLocation);

internal record RootService(INamedTypeSymbol Service, Location? Location);