Skip to content
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
84 changes: 84 additions & 0 deletions src/LightInject.Tests/ClosedGenericEnumerableTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;

namespace LightInject.Tests
{
public class ClosedGenericEnumerableTests
{
[Theory]
[InlineData(true)]
[InlineData(false)]
public void ShouldNotDuplicateClosedGenericServicesWhenEnumerableIsResolvedMultipleTimes(bool enableMicrosoftCompatibility)
{
var container = CreateContainer(enableMicrosoftCompatibility);

var handler = container.GetInstance<BehaviorHandler<int>>();
var behaviors = container.GetInstance<IEnumerable<IBehavior<int>>>();

Assert.Equal(new[] { typeof(FirstBehavior<int>), typeof(SecondBehavior<int>) }, handler.Behaviors.Select(b => b.GetType()));
Assert.Equal(new[] { typeof(FirstBehavior<int>), typeof(SecondBehavior<int>) }, behaviors.Select(b => b.GetType()));
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void ShouldNotDuplicateClosedGenericServicesAfterCanGetInstance(bool enableMicrosoftCompatibility)
{
var container = CreateContainer(enableMicrosoftCompatibility);

Assert.True(container.CanGetInstance(typeof(IEnumerable<IBehavior<int>>), string.Empty));
var behaviors = container.GetInstance<IEnumerable<IBehavior<int>>>();

Assert.Equal(new[] { typeof(FirstBehavior<int>), typeof(SecondBehavior<int>) }, behaviors.Select(b => b.GetType()));
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public void ShouldNotDuplicateClosedGenericServiceResolvedAsSingleServiceBeforeEnumerable(bool enableMicrosoftCompatibility)
{
var container = CreateContainer(enableMicrosoftCompatibility);

var behavior = container.GetInstance<IBehavior<int>>(enableMicrosoftCompatibility ? string.Empty : "second");
var behaviors = container.GetInstance<IEnumerable<IBehavior<int>>>();

Assert.IsType<SecondBehavior<int>>(behavior);
Assert.Equal(new[] { typeof(FirstBehavior<int>), typeof(SecondBehavior<int>) }, behaviors.Select(b => b.GetType()));
}

private static ServiceContainer CreateContainer(bool enableMicrosoftCompatibility)
{
var container = new ServiceContainer(new ContainerOptions { EnableMicrosoftCompatibility = enableMicrosoftCompatibility, EnableVariance = false });
container.Register(typeof(IBehavior<>), typeof(FirstBehavior<>));

// Without Microsoft compatibility, registering another default service would replace the first one.
container.Register(typeof(IBehavior<>), typeof(SecondBehavior<>), enableMicrosoftCompatibility ? string.Empty : "second");
container.Register(typeof(BehaviorHandler<>), typeof(BehaviorHandler<>));
return container;
}

public interface IBehavior<T>
{
}

public class FirstBehavior<T> : IBehavior<T>
{
}

public class SecondBehavior<T> : IBehavior<T>
{
}

public class BehaviorHandler<T>
{
public BehaviorHandler(IEnumerable<IBehavior<T>> behaviors)
{
Behaviors = behaviors.ToArray();
}

public IBehavior<T>[] Behaviors { get; }
}
}
}
31 changes: 29 additions & 2 deletions src/LightInject/LightInject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2620,6 +2620,7 @@ public class ServiceContainer : IServiceContainer
private readonly ServiceRegistry<ServiceRegistration> availableServices = new ServiceRegistry<ServiceRegistration>();
private readonly ConcurrentDictionary<ServiceKey, List<ServiceRegistration>> allRegistrations = new ConcurrentDictionary<ServiceKey, List<ServiceRegistration>>();
private readonly ConcurrentDictionary<ServiceKey, List<EmitMethodInfo>> allEmitters = new ConcurrentDictionary<ServiceKey, List<EmitMethodInfo>>();
private readonly ConcurrentDictionary<Type, HashSet<int>> closedGenericRegistrationOrders = new ConcurrentDictionary<Type, HashSet<int>>();

private readonly object lockObject = new object();
private readonly ContainerOptions options;
Expand Down Expand Up @@ -4747,7 +4748,7 @@ private Action<IEmitter> CreateEmitMethodBasedOnClosedGenericServiceRequest(Type
// Ensure that we only add candidates that are assignable to the requested service type.
if (closedGenericServiceType.IsAssignableFrom(closedGenericImplementingTypeCandidate))
{
candidates.Add(openGenericServiceRegistration.ServiceName, new ClosedGenericCandidate(closedGenericImplementingTypeCandidate, openGenericServiceRegistration.Lifetime));
candidates.Add(openGenericServiceRegistration.ServiceName, new ClosedGenericCandidate(closedGenericImplementingTypeCandidate, openGenericServiceRegistration.Lifetime, openGenericServiceRegistration.ServiceName, openGenericServiceRegistration.RegistrationOrder));
}
}
}
Expand Down Expand Up @@ -4799,10 +4800,25 @@ Action<IEmitter> RegisterAndGetEmitMethod()
Lifetime = CloneLifeTime(candidate.Lifetime) ?? DefaultLifetime,
};
Register(serviceRegistration);
if (string.Equals(serviceName, candidate.ServiceName, StringComparison.OrdinalIgnoreCase))
{
TryMarkAsClosed(closedGenericServiceType, candidate.RegistrationOrder);
}

return GetEmitMethod(serviceRegistration.ServiceType, serviceRegistration.ServiceName);
}
}

// Ensures that an open generic registration is only closed once for a given closed generic service type.
private bool TryMarkAsClosed(Type closedGenericServiceType, int openGenericRegistrationOrder)
{
var registrationOrders = closedGenericRegistrationOrders.GetOrAdd(closedGenericServiceType, _ => new HashSet<int>());
lock (registrationOrders)
{
return registrationOrders.Add(openGenericRegistrationOrder);
}
}

private Action<IEmitter> CreateEmitMethodForEnumerableServiceServiceRequest(Type serviceType, string serviceName)
{
Type actualServiceType = TypeHelper.GetElementType(serviceType);
Expand All @@ -4825,6 +4841,11 @@ private Action<IEmitter> CreateEmitMethodForEnumerableServiceServiceRequest(Type

foreach (var constructableOpenGenericService in constructableOpenGenericServices)
{
if (!TryMarkAsClosed(actualServiceType, constructableOpenGenericService.RegistrationOrder))
{
continue;
}

var serviceRegistration = new ServiceRegistration
{
ServiceType = actualServiceType,
Expand Down Expand Up @@ -5288,15 +5309,21 @@ private void RegisterValue(Type serviceType, object value, string serviceName)

private struct ClosedGenericCandidate
{
public ClosedGenericCandidate(Type closedGenericImplementingType, ILifetime lifetime)
public ClosedGenericCandidate(Type closedGenericImplementingType, ILifetime lifetime, string serviceName, int registrationOrder)
{
ClosedGenericImplementingType = closedGenericImplementingType;
Lifetime = lifetime;
ServiceName = serviceName;
RegistrationOrder = registrationOrder;
}

public Type ClosedGenericImplementingType { get; }

public ILifetime Lifetime { get; }

public string ServiceName { get; }

public int RegistrationOrder { get; }
}

private class Storage<T>
Expand Down
Loading