Skip to content

Commit

Permalink
Support for ReadOnlyCollection<T> added. Fixes #762
Browse files Browse the repository at this point in the history
  • Loading branch information
dotnetjunkie committed Nov 24, 2019
1 parent 952699e commit f297acc
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 29 deletions.
92 changes: 92 additions & 0 deletions src/SimpleInjector.Tests.Unit/RegisterCollectionTests.Full.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 @@ -115,5 +116,96 @@ public void GetInstance_TypeDependingOnIReadOnlyList_InjectsEmptyListWhenNoInsta
// Assert
Assert.AreEqual(0, list.Count);
}

[TestMethod]
public void GetInstance_TypeDependingOnReadOnlyCollection_InjectsTheRegisteredCollection()
{
// Arrange
var container = ContainerFactory.New();

container.Collection.Register<IPlugin>(new[] { typeof(PluginImpl), typeof(PluginImpl2) });

// Act
ReadOnlyCollection<IPlugin> collection =
container.GetInstance<ClassDependingOn<ReadOnlyCollection<IPlugin>>>().Dependency;

// Assert
Assert.AreEqual(2, collection.Count);
AssertThat.IsInstanceOfType(typeof(PluginImpl), collection.First());
AssertThat.IsInstanceOfType(typeof(PluginImpl2), collection.Second());
}

[TestMethod]
public void GetInstance_TypeDependingOnReadOnlyCollection_InjectsTheRegisteredCollectionOfDecorators()
{
// Arrange
var container = ContainerFactory.New();

container.Collection.Register<IPlugin>(new[] { typeof(PluginImpl), typeof(PluginImpl2) });

container.RegisterDecorator(typeof(IPlugin), typeof(PluginDecorator));

// Act
ReadOnlyCollection<IPlugin> collection =
container.GetInstance<ClassDependingOn<ReadOnlyCollection<IPlugin>>>().Dependency;

// Assert
Assert.AreEqual(2, collection.Count);
AssertThat.IsInstanceOfType(typeof(PluginDecorator), collection.First());
AssertThat.IsInstanceOfType(typeof(PluginDecorator), collection.Second());
}

[TestMethod]
public void GetInstance_TypeDependingOnReadOnlyCollection_InjectsEmptyCollectionWhenNoInstancesRegistered()
{
// Arrange
var container = ContainerFactory.New();

container.Collection.Register<IPlugin>(Type.EmptyTypes);

// Act
ReadOnlyCollection<IPlugin> collection =
container.GetInstance<ClassDependingOn<ReadOnlyCollection<IPlugin>>>().Dependency;

// Assert
Assert.AreEqual(0, collection.Count);
}

[TestMethod]
public void ReadOnlyCollection_WhenInjected_IsAlwaysASingleton()
{
// Arrange
var container = ContainerFactory.New();

container.Collection.Register<IPlugin>(new[] { typeof(PluginImpl) });

// Act
ReadOnlyCollection<IPlugin> collection1 =
container.GetInstance<ClassDependingOn<ReadOnlyCollection<IPlugin>>>().Dependency;

ReadOnlyCollection<IPlugin> collection2 =
container.GetInstance<ClassDependingOn<ReadOnlyCollection<IPlugin>>>().Dependency;

// Assert
Assert.AreSame(collection1, collection2, "ReadOnlyCollection<T> should be a singleton.");
}

[TestMethod]
public void ReadOnlyCollection_WhenIterated_ProducesInstancesAccordingToTheirLifestyle()
{
// Arrange
var container = ContainerFactory.New();

container.Collection.Append<IPlugin, PluginImpl>(Lifestyle.Transient);
container.Collection.Append<IPlugin, PluginImpl2>(Lifestyle.Singleton);

// Act
ReadOnlyCollection<IPlugin> collection =
container.GetInstance<ClassDependingOn<ReadOnlyCollection<IPlugin>>>().Dependency;

// Assert
Assert.AreNotSame(collection.First(), collection.First(), "PluginImpl should be transient.");
Assert.AreSame(collection.Last(), collection.Last(), "PluginImpl2 should be singleton.");
}
}
}
13 changes: 0 additions & 13 deletions src/SimpleInjector/Container.Resolving.cs
Original file line number Diff line number Diff line change
Expand Up @@ -572,19 +572,6 @@ private InstanceProducer BuildMutableCollectionProducerFromUncontrolledCollectio
};
}

private InstanceProducer BuildEmptyCollectionInstanceProducerForEnumerable(Type enumerableType)
{
Type elementType = enumerableType.GetGenericArguments()[0];

var collection = ControlledCollectionHelper.CreateContainerControlledCollection(elementType, this);

var registration = new ExpressionRegistration(Expression.Constant(collection, enumerableType), this);

// Producers for ExpressionRegistration are normally ignored as external producer, but in this
// case the empty collection producer should pop up in the list of GetCurrentRegistrations().
return new InstanceProducer(enumerableType, registration, registerExternalProducer: true);
}

private InstanceProducer? TryBuildInstanceProducerForConcreteUnregisteredType<TConcrete>(
InjectionConsumerInfo context)
where TConcrete : class
Expand Down
34 changes: 19 additions & 15 deletions src/SimpleInjector/Internals/ControlledCollectionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,11 @@ internal static InstanceProducer CreateInstanceProducer<TService>(
internal static Registration CreateRegistration(
this IContainerControlledCollection instance, Type collectionType, Container container)
{
// We need special handling for Collection<T>, because the ContainerControlledCollection does not
// (and can't) inherit from Collection<T>. So we have to wrap that stream into a Collection<T>.
return collectionType.GetGenericTypeDefinition() == typeof(Collection<>)
? CreateRegistrationForCollectionOfT(instance, collectionType, container)
: new ContainerControlledCollectionRegistration(collectionType, instance, container);
// We need special handling for Collection<T> (and ReadOnlyCollection<T>), because the
// ContainerControlledCollection does not (and can't) inherit it. So we have to wrap that
// stream into a Collection<T> or ReadOnlyCollection<T>.
return TryCreateRegistrationForCollectionOfT(collectionType, instance, container)
?? new ContainerControlledCollectionRegistration(collectionType, instance, container);
}

internal static bool IsContainerControlledCollectionExpression(Expression enumerableExpression)
Expand All @@ -125,18 +125,22 @@ internal static bool IsContainerControlledCollection(this Registration registrat
internal static Type GetContainerControlledCollectionElementType(this InstanceProducer producer) =>
((ContainerControlledCollectionRegistration)producer.Registration).ElementType;

private static Registration CreateRegistrationForCollectionOfT(
IContainerControlledCollection controlledCollection, Type collectionType, Container container)
private static Registration? TryCreateRegistrationForCollectionOfT(
Type collectionType, IContainerControlledCollection controlledCollection, Container container)
{
var collection = Activator.CreateInstance(collectionType, controlledCollection);

return new ContainerControlledCollectionRegistration(
collectionType, controlledCollection, container)
if (collectionType.GetGenericTypeDefinition() == typeof(Collection<>)
|| collectionType.GetGenericTypeDefinition() == typeof(ReadOnlyCollection<>))
{
Expression = Expression.Constant(
Activator.CreateInstance(collectionType, controlledCollection),
collectionType),
};
return new ContainerControlledCollectionRegistration(
collectionType, controlledCollection, container)
{
Expression = Expression.Constant(
Activator.CreateInstance(collectionType, controlledCollection),
collectionType),
};
}

return null;
}

private sealed class ContainerControlledCollectionRegistration : Registration
Expand Down
3 changes: 2 additions & 1 deletion src/SimpleInjector/Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ internal static bool IsGenericCollectionType(Type serviceType)
serviceTypeDefinition == typeof(IEnumerable<>) ||
serviceTypeDefinition == typeof(IList<>) ||
serviceTypeDefinition == typeof(ICollection<>) ||
serviceTypeDefinition == typeof(Collection<>);
serviceTypeDefinition == typeof(Collection<>) ||
serviceTypeDefinition == typeof(ReadOnlyCollection<>);
}

// Return a list of all base types T inherits, all interfaces T implements and T itself.
Expand Down

0 comments on commit f297acc

Please sign in to comment.