Skip to content

Commit

Permalink
Bug fix: List<T> and T[] collections could only be resolved when the …
Browse files Browse the repository at this point in the history
…container was set up using the flowing scoped lifestyle. Fixes #948
  • Loading branch information
dotnetjunkie committed Jul 20, 2022
1 parent 6ce06ce commit 06e7be9
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 5 deletions.
100 changes: 100 additions & 0 deletions src/SimpleInjector.Tests.Unit/FlowingScopeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
{
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;

Expand Down Expand Up @@ -293,6 +294,105 @@ public void GetInstanceScope_CalledWithDifferentScopes_ReturnsDifferentScopedIns
Assert.AreNotSame(service1.Dependency, service2.Dependency);
}

[TestMethod]
public void ScopeGetInstance_ResolvingArrayOnASecondScopeAfterTheFirstIsDisposed_ResultsInANewScopedInstance()
{
this.ScopeGetInstance_ResolvingCollectionOnASecondScopeAfterTheFirstIsDisposed_ResultsInANewScopedInstance(
scope => scope.GetInstance<ILogger[]>().Single());
}

[TestMethod]
public void ScopeGetInstance_ResolvingEnumerableOnASecondScopeAfterTheFirstIsDisposed_ResultsInANewScopedInstance()
{
this.ScopeGetInstance_ResolvingCollectionOnASecondScopeAfterTheFirstIsDisposed_ResultsInANewScopedInstance(
scope => scope.GetInstance<IEnumerable<ILogger>>().Single());
}

[TestMethod]
public void ScopeGetInstance_ResolvingReadOnlyCollectionOnASecondScopeAfterTheFirstIsDisposed_ResultsInANewScopedInstance()
{
this.ScopeGetInstance_ResolvingCollectionOnASecondScopeAfterTheFirstIsDisposed_ResultsInANewScopedInstance(
scope => scope.GetInstance<ReadOnlyCollection<ILogger>>().Single());
}

private void ScopeGetInstance_ResolvingCollectionOnASecondScopeAfterTheFirstIsDisposed_ResultsInANewScopedInstance(
Func<Scope, ILogger> loggerResolver)
{
// Arrange
var container = new Container();
container.Options.DefaultScopedLifestyle = ScopedLifestyle.Flowing;
container.Collection.Append<ILogger, NullLogger>(Lifestyle.Scoped);

container.Verify();

ILogger logger1, logger2;

// Act
using (var scope = new Scope(container))
{
logger1 = loggerResolver(scope);
}

using (var scope = new Scope(container))
{
logger2 = scope.GetInstance<ReadOnlyCollection<ILogger>>().Single();
}

// Assert
Assert.AreNotSame(logger1, logger2);
}

[TestMethod]
public void ScopeGetInstance_ResolvingTypeDependingOnArrayOnASecondScopeAfterTheFirstIsDisposed_ResultsInANewScopedInstance()
{
// Arrange
var container = new Container();
container.Options.DefaultScopedLifestyle = ScopedLifestyle.Flowing;
container.Collection.Append<ILogger, NullLogger>(Lifestyle.Scoped);
container.Register<ServiceDependingOn<ILogger[]>>();

ILogger logger1;
ILogger logger2;

using (var scope = new Scope(container))
{
logger1 = scope.GetInstance<ServiceDependingOn<ILogger[]>>().Dependency.Single();
}

using (var scope = new Scope(container))
{
logger2 = scope.GetInstance<ServiceDependingOn<ILogger[]>>().Dependency.Single();
}

// Assert
Assert.AreNotSame(logger1, logger2);
}

[TestMethod]
public void ScopeGetInstance_ScopeRegistrationsBothAvailableThroughCollectionsAsThroughOneToOneMappings_ResolveAsTheSameInstanceWithinASingleScope()
{
// Arrange
var container = new Container();
container.Options.DefaultScopedLifestyle = ScopedLifestyle.Flowing;
container.Register<ILogger, NullLogger>(Lifestyle.Scoped);
container.Collection.Append<ILogger, NullLogger>(Lifestyle.Scoped);

var scope1 = new Scope(container);
var scope2 = new Scope(container);

// Act
var singleInstance1 = scope1.GetInstance<ILogger>();
var collectionInstance1 = scope1.GetInstance<ILogger[]>().Single();

var singleInstance2 = scope2.GetInstance<ILogger>();
var collectionInstance2 = scope2.GetInstance<ILogger[]>().Single();

// Assert
Assert.AreSame(singleInstance1, collectionInstance1);
Assert.AreNotSame(singleInstance1, singleInstance2);
Assert.AreSame(singleInstance2, collectionInstance2);
}

public class ScopedPluginProxy : IPlugin
{
public readonly Func<Scope, IPlugin> Factory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ internal sealed class CollectionInstanceProducerBuilder : IInstanceProducerBuild
private static readonly MethodInfo EnumerableToListMethod =
typeof(Enumerable).GetMethod(nameof(Enumerable.ToList));

private readonly Dictionary<Type, InstanceProducer?> emptyAndRedirectedCollectionRegistrationCache =
new Dictionary<Type, InstanceProducer?>();
private readonly Dictionary<Type, InstanceProducer?> emptyAndRedirectedCollectionRegistrationCache = new();

private readonly Container container;

Expand Down Expand Up @@ -98,9 +97,10 @@ private InstanceProducer BuildMutableCollectionProducerFromControlledCollection(
private Expression BuildMutableCollectionExpressionFromControlledCollection(
Type serviceType, Type elementType)
{
var streamExpression = Expression.Constant(
value: this.container.GetAllInstances(elementType),
type: typeof(IEnumerable<>).MakeGenericType(elementType));
InstanceProducer streamRegistration =
this.container.GetRegistration(typeof(IEnumerable<>).MakeGenericType(elementType))!;

Expression streamExpression = streamRegistration.BuildExpression();

if (serviceType.IsArray)
{
Expand Down

0 comments on commit 06e7be9

Please sign in to comment.