From f2e13e7dbf4863af793b65b07d2db2dacdccf8a7 Mon Sep 17 00:00:00 2001 From: Rory Primrose Date: Sun, 3 May 2020 00:03:00 +1000 Subject: [PATCH] Updated EnumerableTypeCreator to support more enumerable types and strong type handling on ReadOnly types (#88) Fixed bug in TypeCreatorBase which evaluated the wrong type on Create Added performance fix on DefaultTypeResolver to not search for types when a List or List scenario could be supported Closes #71 --- .../ModelBuilder.Synchronous.UnitTests.csproj | 2 +- .../DefaultTypeResolverTests.cs | 5 +- .../ModelBuilder.UnitTests.csproj | 4 +- .../Models/IInterfaceCollection.cs | 8 + .../Models/MultipleGenericArguments.cs | 6 + .../Scenarios/ScenarioTests.cs | 46 ++-- .../EnumerableTypeCreatorTests.cs | 207 +++++++----------- ModelBuilder/DefaultTypeResolver.cs | 78 ++++--- .../TypeCreators/DefaultTypeCreator.cs | 3 - .../TypeCreators/EnumerableTypeCreator.cs | 193 ++++++++++------ ModelBuilder/TypeCreators/TypeCreatorBase.cs | 2 +- 11 files changed, 296 insertions(+), 258 deletions(-) create mode 100644 ModelBuilder.UnitTests/Models/IInterfaceCollection.cs create mode 100644 ModelBuilder.UnitTests/Models/MultipleGenericArguments.cs diff --git a/ModelBuilder.Synchronous.UnitTests/ModelBuilder.Synchronous.UnitTests.csproj b/ModelBuilder.Synchronous.UnitTests/ModelBuilder.Synchronous.UnitTests.csproj index cd72682..a4383ca 100644 --- a/ModelBuilder.Synchronous.UnitTests/ModelBuilder.Synchronous.UnitTests.csproj +++ b/ModelBuilder.Synchronous.UnitTests/ModelBuilder.Synchronous.UnitTests.csproj @@ -12,7 +12,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + all diff --git a/ModelBuilder.UnitTests/DefaultTypeResolverTests.cs b/ModelBuilder.UnitTests/DefaultTypeResolverTests.cs index c4e0546..e94429d 100644 --- a/ModelBuilder.UnitTests/DefaultTypeResolverTests.cs +++ b/ModelBuilder.UnitTests/DefaultTypeResolverTests.cs @@ -3,7 +3,6 @@ using System; using System.Collections; using System.Collections.Generic; - using System.Collections.ObjectModel; using System.IO; using FluentAssertions; using ModelBuilder.UnitTests.Models; @@ -25,9 +24,9 @@ public DefaultTypeResolverTests(ITestOutputHelper output) [InlineData(typeof(ICollection), typeof(List))] [InlineData(typeof(IList), typeof(List))] [InlineData(typeof(IEnumerable), typeof(List))] - [InlineData(typeof(ICollection), typeof(Collection))] + [InlineData(typeof(ICollection), typeof(List))] [InlineData(typeof(IList), typeof(List))] - [InlineData(typeof(IReadOnlyCollection), typeof(ReadOnlyCollection))] + [InlineData(typeof(IReadOnlyCollection), typeof(List))] [InlineData(typeof(IDictionary), typeof(Dictionary))] public void CanCreateEnumerableTypesFromInterfacesWithExpectedResolutionPriority(Type requestedType, Type expectedType) diff --git a/ModelBuilder.UnitTests/ModelBuilder.UnitTests.csproj b/ModelBuilder.UnitTests/ModelBuilder.UnitTests.csproj index 1dfb07a..5ffab22 100644 --- a/ModelBuilder.UnitTests/ModelBuilder.UnitTests.csproj +++ b/ModelBuilder.UnitTests/ModelBuilder.UnitTests.csproj @@ -11,9 +11,9 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + all diff --git a/ModelBuilder.UnitTests/Models/IInterfaceCollection.cs b/ModelBuilder.UnitTests/Models/IInterfaceCollection.cs new file mode 100644 index 0000000..1cbd6da --- /dev/null +++ b/ModelBuilder.UnitTests/Models/IInterfaceCollection.cs @@ -0,0 +1,8 @@ +namespace ModelBuilder.UnitTests.Models +{ + using System.Collections.Generic; + + public interface IInterfaceCollection : IList + { + } +} \ No newline at end of file diff --git a/ModelBuilder.UnitTests/Models/MultipleGenericArguments.cs b/ModelBuilder.UnitTests/Models/MultipleGenericArguments.cs new file mode 100644 index 0000000..8a1a6ae --- /dev/null +++ b/ModelBuilder.UnitTests/Models/MultipleGenericArguments.cs @@ -0,0 +1,6 @@ +namespace ModelBuilder.UnitTests.Models +{ + public class MultipleGenericArguments + { + } +} \ No newline at end of file diff --git a/ModelBuilder.UnitTests/Scenarios/ScenarioTests.cs b/ModelBuilder.UnitTests/Scenarios/ScenarioTests.cs index ce15d2c..b8b31f1 100644 --- a/ModelBuilder.UnitTests/Scenarios/ScenarioTests.cs +++ b/ModelBuilder.UnitTests/Scenarios/ScenarioTests.cs @@ -69,8 +69,34 @@ public void CanCreateCustomBuildConfigurationToCreateModels() Guid.Parse(actual.Address.AddressLine1).Should().NotBeEmpty(); } + [Theory] + [InlineData(typeof(IEnumerable))] + [InlineData(typeof(ICollection))] + [InlineData(typeof(Collection))] + [InlineData(typeof(IList))] + [InlineData(typeof(List))] + [InlineData(typeof(LinkedList))] + [InlineData(typeof(InheritedGenericCollection))] + [InlineData(typeof(HashSet))] + [InlineData(typeof(IList>))] + [InlineData(typeof(List>))] + [InlineData(typeof(List>))] + [InlineData(typeof(IList>))] + [InlineData(typeof(Collection>))] + [InlineData(typeof(ICollection>))] + [InlineData(typeof(IReadOnlyCollection))] + [InlineData(typeof(IReadOnlyList))] + [InlineData(typeof(IDictionary))] + [InlineData(typeof(Dictionary))] + public void CanCreateEnumerableTypes(Type type) + { + var actual = Model.Create(type); + + actual.As().Should().NotBeEmpty(); + } + [Fact] - public void CanCreateEnumerableTypes() + public void CanCreateInstanceWithEnumerablePropertyTypes() { var actual = Model.Create(); @@ -81,22 +107,6 @@ public void CanCreateEnumerableTypes() actual.List.Should().NotBeEmpty(); } - [Theory] - [InlineData(typeof(IEnumerable))] - [InlineData(typeof(ICollection))] - [InlineData(typeof(IList))] - [InlineData(typeof(IEnumerable))] - [InlineData(typeof(ICollection))] - [InlineData(typeof(IList))] - [InlineData(typeof(IReadOnlyCollection))] - [InlineData(typeof(IDictionary))] - public void CanCreateEnumerableTypesFromInterfaces(Type type) - { - var actual = Model.Create(type); - - actual.As().Should().NotBeEmpty(); - } - [Fact] public void CanCreateInstanceWithoutProperties() { @@ -110,7 +120,7 @@ public void CanCreateReadOnlyCollection() { var actual = Model.Create>(); - actual.Should().BeOfType>(); + actual.Should().BeOfType>(); actual.Should().NotBeEmpty(); } diff --git a/ModelBuilder.UnitTests/TypeCreators/EnumerableTypeCreatorTests.cs b/ModelBuilder.UnitTests/TypeCreators/EnumerableTypeCreatorTests.cs index 2f996b4..fbabead 100644 --- a/ModelBuilder.UnitTests/TypeCreators/EnumerableTypeCreatorTests.cs +++ b/ModelBuilder.UnitTests/TypeCreators/EnumerableTypeCreatorTests.cs @@ -36,12 +36,12 @@ public void AutoPopulateReturnsFalse() [InlineData(typeof(string), false)] [InlineData(typeof(Stream), false)] [InlineData(typeof(int), false)] - [InlineData(typeof(ReadOnlyCollection), false)] - [InlineData(typeof(IDictionary), false)] + [InlineData(typeof(int?), false)] [InlineData(typeof(Tuple), false)] + [InlineData(typeof(ICollection), false)] + [InlineData(typeof(IList), false)] + [InlineData(typeof(ArrayList), false)] [InlineData(typeof(AbstractCollection), false)] - [InlineData(typeof(IReadOnlyCollection), false)] - [InlineData(typeof(IReadOnlyList), false)] [InlineData(typeof(ArraySegment), false)] [InlineData(typeof(IPAddressCollection), false)] [InlineData(typeof(GatewayIPAddressInformationCollection), false)] @@ -50,25 +50,33 @@ public void AutoPopulateReturnsFalse() [InlineData(typeof(UnicastIPAddressInformationCollection), false)] [InlineData(typeof(Dictionary<,>.KeyCollection), false)] [InlineData(typeof(Dictionary<,>.ValueCollection), false)] - [InlineData(typeof(Dictionary), true)] + [InlineData(typeof(ReadOnlyDictionary), false)] + [InlineData(typeof(ReadOnlyCollection), false)] [InlineData(typeof(SortedDictionary<,>.KeyCollection), false)] [InlineData(typeof(SortedDictionary<,>.ValueCollection), false)] + [InlineData(typeof(IInterfaceCollection), false)] [InlineData(typeof(IEnumerable), true)] [InlineData(typeof(ICollection), true)] - [InlineData(typeof(Collection), true)] [InlineData(typeof(IList), true)] + [InlineData(typeof(IList>), true)] + [InlineData(typeof(List>), true)] + [InlineData(typeof(List>), true)] + [InlineData(typeof(IList>), true)] + [InlineData(typeof(Collection>), true)] + [InlineData(typeof(IReadOnlyCollection), true)] + [InlineData(typeof(IReadOnlyList), true)] + [InlineData(typeof(IDictionary), true)] + [InlineData(typeof(Collection), true)] [InlineData(typeof(List), true)] [InlineData(typeof(HashSet), true)] + [InlineData(typeof(Dictionary), true)] [InlineData(typeof(LinkedList), true)] [InlineData(typeof(InheritedGenericCollection), true)] public void CanCreateReturnsWhetherTypeIsSupportedTest(Type type, bool supported) { var executeStrategy = Substitute.For(); - var typeResolver = Substitute.For(); var configuration = Substitute.For(); - configuration.TypeResolver.Returns(typeResolver); - typeResolver.GetBuildType(configuration, Arg.Any()).Returns(x => x.Arg()); executeStrategy.Configuration.Returns(configuration); var sut = new EnumerableTypeCreator(); @@ -82,11 +90,8 @@ public void CanCreateReturnsWhetherTypeIsSupportedTest(Type type, bool supported public void CanCreateThrowsExceptionWithNullParameter() { var executeStrategy = Substitute.For(); - var typeResolver = Substitute.For(); var configuration = Substitute.For(); - configuration.TypeResolver.Returns(typeResolver); - typeResolver.GetBuildType(configuration, Arg.Any()).Returns(x => x.Arg()); executeStrategy.Configuration.Returns(configuration); var sut = new EnumerableTypeCreator(); @@ -100,11 +105,8 @@ public void CanCreateThrowsExceptionWithNullParameter() public void CanCreateThrowsExceptionWithNullProperty() { var executeStrategy = Substitute.For(); - var typeResolver = Substitute.For(); var configuration = Substitute.For(); - configuration.TypeResolver.Returns(typeResolver); - typeResolver.GetBuildType(configuration, Arg.Any()).Returns(x => x.Arg()); executeStrategy.Configuration.Returns(configuration); var sut = new EnumerableTypeCreator(); @@ -118,11 +120,8 @@ public void CanCreateThrowsExceptionWithNullProperty() public void CanCreateThrowsExceptionWithNullType() { var executeStrategy = Substitute.For(); - var typeResolver = Substitute.For(); var configuration = Substitute.For(); - configuration.TypeResolver.Returns(typeResolver); - typeResolver.GetBuildType(configuration, Arg.Any()).Returns(x => x.Arg()); executeStrategy.Configuration.Returns(configuration); var sut = new EnumerableTypeCreator(); @@ -161,13 +160,14 @@ public void CanCreateThrowsExceptionWithNullType() [InlineData(typeof(HashSet), true)] [InlineData(typeof(LinkedList), true)] [InlineData(typeof(InheritedGenericCollection), true)] + [InlineData(typeof(IList>), true)] + [InlineData(typeof(List>), true)] + [InlineData(typeof(List>), true)] + [InlineData(typeof(IList>), true)] + [InlineData(typeof(Collection>), true)] public void CanPopulateReturnsWhetherTypeIsSupportedTest(Type type, bool supported) { var configuration = Substitute.For(); - var typeResolver = Substitute.For(); - - typeResolver.GetBuildType(configuration, Arg.Any()).Returns(x => x.Arg()); - configuration.TypeResolver.Returns(typeResolver); var sut = new EnumerableTypeCreator(); @@ -220,146 +220,90 @@ public void CanPopulateThrowsExceptionWithNullType() } [Fact] - public void CreateChildItemThrowsExceptionWithNullExecuteStrategy() + public void CreateByNameThrowsExceptionWithNullExecuteStrategy() { - var person = new Person(); - var sut = new EnumerableTypeCreatorWrapper(); - Action action = () => sut.CreateItem(typeof(Person), null, person); + Action action = () => sut.CreateByName(null, typeof(string), null, null); action.Should().Throw(); } [Fact] - public void CreateDoesNotPopulateList() + public void CreateByNameThrowsExceptionWithNullType() { - var buildChain = new BuildHistory(); - var executeStrategy = Substitute.For(); - var typeResolver = Substitute.For(); - var configuration = Substitute.For(); - - configuration.TypeResolver.Returns(typeResolver); - typeResolver.GetBuildType(configuration, Arg.Any()).Returns(x => x.Arg()); - executeStrategy.BuildChain.Returns(buildChain); - executeStrategy.Configuration.Returns(configuration); - var sut = new IncrementingEnumerableTypeCreator(); + var sut = new EnumerableTypeCreatorWrapper(); - var result = (IList) sut.Create(executeStrategy, typeof(IList)); + Action action = () => sut.CreateByName(executeStrategy, null, null, null); - result.Should().BeEmpty(); + action.Should().Throw(); } - [Theory] - [InlineData(typeof(Dictionary))] - [InlineData(typeof(IEnumerable))] - [InlineData(typeof(ICollection))] - [InlineData(typeof(Collection))] - [InlineData(typeof(IList))] - [InlineData(typeof(List))] - [InlineData(typeof(LinkedList))] - [InlineData(typeof(InheritedGenericCollection))] - public void CreateReturnsInstanceTest(Type type) + [Fact] + public void CreateChildItemThrowsExceptionWithNullExecuteStrategy() { - var buildChain = new BuildHistory(); - - var executeStrategy = Substitute.For(); - var typeResolver = Substitute.For(); - var configuration = Substitute.For(); - - configuration.TypeResolver.Returns(typeResolver); - typeResolver.GetBuildType(configuration, Arg.Any()).Returns(x => x.Arg()); - executeStrategy.BuildChain.Returns(buildChain); - executeStrategy.Configuration.Returns(configuration); + var person = new Person(); - var sut = new EnumerableTypeCreator(); + var sut = new EnumerableTypeCreatorWrapper(); - var actual = sut.Create(executeStrategy, type); + Action action = () => sut.CreateItem(typeof(Person), null, person); - actual.Should().NotBeNull(); + action.Should().Throw(); } - [Theory] - [InlineData(typeof(IEnumerable))] - [InlineData(typeof(ICollection))] - [InlineData(typeof(IList))] - public void CreateReturnsNewListOfSpecifiedTypeTest(Type targetType) + [Fact] + public void CreateInstanceThrowsExceptionWithNullType() { - var buildChain = new BuildHistory(); - var executeStrategy = Substitute.For(); - var typeResolver = Substitute.For(); - var configuration = Substitute.For(); - - configuration.TypeResolver.Returns(typeResolver); - typeResolver.GetBuildType(configuration, Arg.Any()).Returns(x => x.Arg()); - executeStrategy.BuildChain.Returns(buildChain); - executeStrategy.Configuration.Returns(configuration); - var sut = new EnumerableTypeCreator(); + var sut = new EnumerableTypeCreatorWrapper(); - var actual = sut.Create(executeStrategy, targetType); + Action action = () => sut.CreateValue(executeStrategy, null, null, null); - actual.Should().BeOfType>(); - actual.As>().Should().BeEmpty(); + action.Should().Throw(); } [Theory] - [InlineData(typeof(string), false)] - [InlineData(typeof(Stream), false)] - [InlineData(typeof(int), false)] - [InlineData(typeof(ReadOnlyCollection), false)] - [InlineData(typeof(IDictionary), false)] - [InlineData(typeof(Tuple), false)] - [InlineData(typeof(AbstractCollection), false)] - [InlineData(typeof(IReadOnlyCollection), false)] - [InlineData(typeof(IReadOnlyList), false)] - [InlineData(typeof(ArraySegment), false)] - [InlineData(typeof(IPAddressCollection), false)] - [InlineData(typeof(GatewayIPAddressInformationCollection), false)] - [InlineData(typeof(IPAddressInformationCollection), false)] - [InlineData(typeof(MulticastIPAddressInformationCollection), false)] - [InlineData(typeof(UnicastIPAddressInformationCollection), false)] - [InlineData(typeof(Dictionary<,>.KeyCollection), false)] - [InlineData(typeof(Dictionary<,>.ValueCollection), false)] - [InlineData(typeof(SortedDictionary<,>.KeyCollection), false)] - [InlineData(typeof(SortedDictionary<,>.ValueCollection), false)] - [InlineData(typeof(Dictionary), true)] - [InlineData(typeof(IEnumerable), true)] - [InlineData(typeof(ICollection), true)] - [InlineData(typeof(Collection), true)] - [InlineData(typeof(IList), true)] - [InlineData(typeof(List), true)] - [InlineData(typeof(LinkedList), true)] - [InlineData(typeof(HashSet), true)] - [InlineData(typeof(InheritedGenericCollection), true)] - public void CreateValidatesWhetherTypeIsSupportedTest(Type type, bool supported) + [InlineData(typeof(IEnumerable), typeof(List))] + [InlineData(typeof(ICollection), typeof(List))] + [InlineData(typeof(Collection), typeof(Collection))] + [InlineData(typeof(IList), typeof(List))] + [InlineData(typeof(List), typeof(List))] + [InlineData(typeof(LinkedList), typeof(LinkedList))] + [InlineData(typeof(InheritedGenericCollection), typeof(InheritedGenericCollection))] + [InlineData(typeof(HashSet), typeof(HashSet))] + [InlineData(typeof(IList>), typeof(List>))] + [InlineData(typeof(List>), typeof(List>))] + [InlineData(typeof(List>), + typeof(List>))] + [InlineData(typeof(IList>), + typeof(List>))] + [InlineData(typeof(Collection>), typeof(Collection>))] + [InlineData(typeof(ICollection>), typeof(Dictionary))] + [InlineData(typeof(IReadOnlyCollection), typeof(List))] + [InlineData(typeof(IReadOnlyList), typeof(List))] + [InlineData(typeof(IDictionary), typeof(Dictionary))] + [InlineData(typeof(Dictionary), typeof(Dictionary))] + public void CreateReturnsInstanceOfMostAppropriateTypeTest(Type requestedType, Type expectedType) { var buildChain = new BuildHistory(); var executeStrategy = Substitute.For(); - var typeResolver = Substitute.For(); var configuration = Substitute.For(); - configuration.TypeResolver.Returns(typeResolver); - typeResolver.GetBuildType(configuration, Arg.Any()).Returns(x => x.Arg()); executeStrategy.BuildChain.Returns(buildChain); executeStrategy.Configuration.Returns(configuration); var sut = new EnumerableTypeCreator(); - Action action = () => sut.Create(executeStrategy, type); + var actual = sut.Create(executeStrategy, requestedType); - if (supported) - { - action.Should().NotThrow(); - } - else - { - action.Should().Throw(); - } + actual.Should().NotBeNull(); + actual.Should().BeOfType(expectedType); + actual.Should().BeAssignableTo(requestedType); + actual.As().Should().BeEmpty(); } [Fact] @@ -369,11 +313,8 @@ public void PopulateAddsItemsToCollectionFromExecuteStrategy() var buildChain = new BuildHistory(); var executeStrategy = Substitute.For(); - var typeResolver = Substitute.For(); var configuration = Substitute.For(); - configuration.TypeResolver.Returns(typeResolver); - typeResolver.GetBuildType(configuration, Arg.Any()).Returns(x => x.Arg()); executeStrategy.BuildChain.Returns(buildChain); executeStrategy.Configuration.Returns(configuration); @@ -432,10 +373,7 @@ public void PopulateAddsItemsToListFromExecuteStrategy() var buildChain = new BuildHistory(); var executeStrategy = Substitute.For(); var configuration = Substitute.For(); - var typeResolver = Substitute.For(); - typeResolver.GetBuildType(configuration, Arg.Any()).Returns(x => x.Arg()); - configuration.TypeResolver.Returns(typeResolver); executeStrategy.Configuration.Returns(configuration); executeStrategy.BuildChain.Returns(buildChain); executeStrategy.Create(typeof(Guid)).Returns(Guid.NewGuid()); @@ -483,10 +421,7 @@ public void PopulateThrowsExceptionWithUnsupportedType() var buildChain = new BuildHistory(); var executeStrategy = Substitute.For(); var configuration = Substitute.For(); - var typeResolver = Substitute.For(); - typeResolver.GetBuildType(configuration, Arg.Any()).Returns(x => x.Arg()); - configuration.TypeResolver.Returns(typeResolver); executeStrategy.Configuration.Returns(configuration); executeStrategy.BuildChain.Returns(buildChain); @@ -508,10 +443,22 @@ public void PriorityReturnsHigherThanDefaultTypeCreator() private class EnumerableTypeCreatorWrapper : EnumerableTypeCreator { + public object CreateByName(IExecuteStrategy executeStrategy, Type type, string referenceName, + params object[] args) + { + return Create(executeStrategy, type, referenceName, args); + } + public void CreateItem(Type type, IExecuteStrategy executeStrategy, object item) { CreateChildItem(type, executeStrategy, item); } + + public object CreateValue(IExecuteStrategy executeStrategy, Type type, string referenceName, + params object[] args) + { + return CreateInstance(executeStrategy, type, referenceName, args); + } } } } \ No newline at end of file diff --git a/ModelBuilder/DefaultTypeResolver.cs b/ModelBuilder/DefaultTypeResolver.cs index 29fb4b1..a0816ec 100644 --- a/ModelBuilder/DefaultTypeResolver.cs +++ b/ModelBuilder/DefaultTypeResolver.cs @@ -1,6 +1,7 @@ namespace ModelBuilder { using System; + using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -11,7 +12,7 @@ /// public class DefaultTypeResolver : ITypeResolver { - private Dictionary _typeCache = new Dictionary(); + private readonly Dictionary _typeCache = new Dictionary(); /// public Type GetBuildType(IBuildConfiguration configuration, Type requestedType) @@ -43,7 +44,7 @@ public Type GetBuildType(IBuildConfiguration configuration, Type requestedType) return _typeCache[requestedType]; } - var resolvedType = AutoResolveBuildType(requestedType); + var resolvedType = AutoResolveBuildType(requestedType); _typeCache[requestedType] = resolvedType; @@ -55,6 +56,45 @@ public Type GetBuildType(IBuildConfiguration configuration, Type requestedType) private static Type AutoResolveBuildType(Type requestedType) { + // Before search for matching types and finding a good match, check if this is an IEnumerable that would support a list type + if (requestedType.IsInterface + && typeof(IEnumerable).IsAssignableFrom(requestedType)) + { + var objectListType = typeof(List); + + if (requestedType.IsGenericType == false + && requestedType.IsAssignableFrom(objectListType)) + { + // This is an enumerable type that supports List (like ICollection and IList) + // At this point we will just take ArrayList + // The reason for checking compatibility for List is that there are many IEnumerable types other than List which are + // a little too specific in their purpose whereas List is generic + return objectListType; + } + + if (requestedType.IsGenericType) + { + try + { + var genericParameters = requestedType.GetGenericArguments(); + var genericListDefinition = typeof(List<>); + var listType = genericListDefinition.MakeGenericType(genericParameters); + + if (requestedType.IsAssignableFrom(listType)) + { + // This is an enumerable type that supports List + // We will use List in preference of other types for the same reason as List above + return listType; + } + } + catch (ArgumentException) + { + // We couldn't create a type with the generic parameters + // This is not a matching type + } + } + } + // Automatically resolve a derived type within the same assembly var assemblyTypes = requestedType.GetTypeInfo().Assembly.GetTypes(); var possibleTypes = from x in assemblyTypes @@ -120,40 +160,6 @@ private static Type AutoResolveBuildType(Type requestedType) { return interfaceNameMatchingType; } - - var arrayListType = typeof(List); - - if (requestedType.IsGenericType == false - && requestedType.IsAssignableFrom(arrayListType)) - { - // This is an enumerable type that supports ArrayList - // At this point we will just take ArrayList - // The reason for checking compatibility for ArrayList is that there are many IEnumerable types other than ArrayList which are - // a little to specific in their purpose whereas ArrayList is generic - return arrayListType; - } - - if (requestedType.IsGenericType) - { - try - { - var genericParameters = requestedType.GetGenericArguments(); - var genericListDefinition = typeof(List<>); - var listType = genericListDefinition.MakeGenericType(genericParameters); - - if (requestedType.IsAssignableFrom(listType)) - { - // This is an enumerable type that supports List - // We will use List in preference of other types for the same reason as ArrayList above - return listType; - } - } - catch (ArgumentException) - { - // We couldn't create a type with the generic parameters - // This is not a matching type - } - } } // else if instead of if in a discrete block because interfaces are also abstract else if (requestedType.IsAbstract) diff --git a/ModelBuilder/TypeCreators/DefaultTypeCreator.cs b/ModelBuilder/TypeCreators/DefaultTypeCreator.cs index 1521156..44426c9 100644 --- a/ModelBuilder/TypeCreators/DefaultTypeCreator.cs +++ b/ModelBuilder/TypeCreators/DefaultTypeCreator.cs @@ -24,9 +24,6 @@ protected override object CreateInstance(IExecuteStrategy executeStrategy, { Debug.Assert(type != null, "type != null"); - // TODO: Resolve the type to build - // buildLog.MappedType(requestedType, typeMappingRule.TargetType); - if (args == null) { return Activator.CreateInstance(type); diff --git a/ModelBuilder/TypeCreators/EnumerableTypeCreator.cs b/ModelBuilder/TypeCreators/EnumerableTypeCreator.cs index 47792cd..e3fd5f5 100644 --- a/ModelBuilder/TypeCreators/EnumerableTypeCreator.cs +++ b/ModelBuilder/TypeCreators/EnumerableTypeCreator.cs @@ -2,10 +2,10 @@ { using System; using System.Collections.Generic; + using System.Collections.ObjectModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Net.NetworkInformation; - using System.Reflection; /// /// The @@ -37,44 +37,28 @@ protected override bool CanCreate(IBuildConfiguration configuration, throw new ArgumentNullException(nameof(type)); } - var targetType = ResolveBuildType(configuration, type); - - if (targetType.IsClass - && targetType.IsAbstract) + if (type.IsClass + && type.IsAbstract) { // This is an abstract class so we can't create it return false; } - if (IsReadOnlyType(targetType) == false) - { - return false; - } - - var internalType = FindEnumerableTypeArgument(targetType); + var typeToCreate = DetermineTypeToCreate(type); - if (internalType == null) + if (typeToCreate == null) { - // The type does not implement IEnumerable + // We don't know how to create this type return false; } - if (targetType.IsInterface) + if (typeToCreate.IsInterface) { - var listGenericType = typeof(List).GetGenericTypeDefinition(); - var listType = listGenericType.MakeGenericType(internalType); - - if (targetType.IsAssignableFrom(listType)) - { - // The instance is assignable from List and therefore can be both created and populated by this type creator - return true; - } - + // We couldn't identify that the type could be created as either List<> or Dictionary<,> return false; } - // This is a class that we can presumably create so we need to check if it can be populated - if (CanPopulate(configuration, buildChain, targetType, referenceName) == false) + if (CanPopulate(configuration, buildChain, typeToCreate, referenceName) == false) { // There is no point trying to create something that we can't populate return false; @@ -98,30 +82,30 @@ protected override bool CanPopulate(IBuildConfiguration configuration, throw new ArgumentNullException(nameof(configuration)); } - var targetType = ResolveBuildType(configuration, type); - - if (IsReadOnlyType(targetType) == false) + if (IsReadOnlyType(type)) { + // We can't populate read-only types here + // We can however let DefaultTypeCreator handle it because the type should have a constructor parameter that it can support return false; } - var internalType = FindEnumerableTypeArgument(targetType); - - if (internalType == null) + if (IsUnsupportedType(type)) { - // The type does not implement IEnumerable return false; } - if (IsUnsupportedType(targetType)) + var genericParameterType = FindEnumerableTypeArgument(type); + + if (genericParameterType == null) { + // The type does not implement IEnumerable return false; } - var genericTypeDefinition = typeof(ICollection).GetGenericTypeDefinition(); - var genericType = genericTypeDefinition.MakeGenericType(internalType); + var genericTypeDefinition = typeof(ICollection<>); + var genericType = genericTypeDefinition.MakeGenericType(genericParameterType); - if (genericType.IsAssignableFrom(targetType)) + if (genericType.IsAssignableFrom(type)) { // The instance is ICollection and therefore can be populated by this type creator return true; @@ -149,25 +133,39 @@ protected virtual object CreateChildItem(Type type, IExecuteStrategy executeStra } /// - [SuppressMessage( - "Microsoft.Design", - "CA1062:Validate arguments of public methods", - MessageId = "0", - Justification = "Type is validated by the base class")] + /// The parameter is null. + /// The parameter is null. + protected override object Create(IExecuteStrategy executeStrategy, Type type, string referenceName, params object[] args) + { + if (executeStrategy == null) + { + throw new ArgumentNullException(nameof(executeStrategy)); + } + + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (type.IsInterface) + { + var typeToCreate = DetermineTypeToCreate(type); + + return CreateInstance(executeStrategy, typeToCreate, referenceName, args); + } + + return CreateInstance(executeStrategy, type, referenceName, args); + } + + /// protected override object CreateInstance(IExecuteStrategy executeStrategy, Type type, string referenceName, params object[] args) { - Debug.Assert(type != null, "type != null"); - - if (type.IsInterface) + if (type == null) { - var internalType = FindEnumerableTypeArgument(type); - var genericTypeDefinition = typeof(List).GetGenericTypeDefinition(); - var genericType = genericTypeDefinition.MakeGenericType(internalType); - - return Activator.CreateInstance(genericType); + throw new ArgumentNullException(nameof(type)); } return Activator.CreateInstance(type, args); @@ -186,7 +184,7 @@ protected override object PopulateInstance(IExecuteStrategy executeStrategy, obj var type = instance.GetType(); var internalType = FindEnumerableTypeArgument(type); - var collectionGenericTypeDefinition = typeof(ICollection).GetGenericTypeDefinition(); + var collectionGenericTypeDefinition = typeof(ICollection<>); var collectionType = collectionGenericTypeDefinition.MakeGenericType(internalType); // Get the Add method @@ -215,6 +213,56 @@ protected override object PopulateInstance(IExecuteStrategy executeStrategy, obj return instance; } + private static Type DetermineTypeToCreate(Type type) + { + if (type.IsInterface == false) + { + return type; + } + + // We need to check if the type is compatible with either List or Dictionary so we can use those types to create the instance + var genericParameterType = FindEnumerableTypeArgument(type); + + if (genericParameterType == null) + { + // The type does not implement IEnumerable + return null; + } + + // Check if the generic parameter type is a KeyValuePair + if (genericParameterType.IsGenericType) + { + // The T in IEnumerable is also a generic type itself + // We need to try to match this against KeyValuePair + var nestedGenericTypeDefinition = genericParameterType.GetGenericTypeDefinition(); + + if (nestedGenericTypeDefinition == typeof(KeyValuePair<,>)) + { + // Looks like this may be a dictionary + var typeArguments = genericParameterType.GenericTypeArguments; + + var dictionaryType = GetSupportedGenericType(type, typeof(Dictionary<,>), typeArguments); + + if (dictionaryType != null) + { + // This is a Dictionary type + return dictionaryType; + } + } + } + + // At this point we have a type that we can check to see if it will be supported by List + var listType = GetSupportedGenericType(type, typeof(List<>), genericParameterType); + + if (listType != null) + { + // This is an interface that can't be satisfied by either List or Dictionary + return listType; + } + + return type; + } + private static Type FindEnumerableTypeArgument(Type type) { // The type may implement multiple interfaces including IEnumerable where the type generic definition of the type itself is not the same as the IEnumerable definition @@ -251,7 +299,7 @@ private static Type GetEnumerableTypeArgument(Type type) var genericInternalType = type.GetGenericTypeDefinition(); - var enumerableType = typeof(IEnumerable).GetGenericTypeDefinition(); + var enumerableType = typeof(IEnumerable<>); if (genericInternalType != enumerableType) { @@ -262,24 +310,41 @@ private static Type GetEnumerableTypeArgument(Type type) return type.GetGenericArguments()[0]; } - private static bool IsReadOnlyType(MemberInfo type) + private static Type GetSupportedGenericType(Type type, Type genericTypeDefinition, + params Type[] genericParameterTypes) + { + var potentialType = genericTypeDefinition.MakeGenericType(genericParameterTypes); + + if (type.IsAssignableFrom(potentialType)) + { + // The instance is assignable from the generic type and therefore can be both created and populated by this type creator + return potentialType; + } + + return null; + } + + private static bool IsReadOnlyType(Type type) { - // Check if the type is a ReadOnly type - // We can't check for the implementation of IReadOnlyCollection because this was introduced in .Net 4.5 - // however this library targets 4.0 -#if NETSTANDARD2_0 - if (type.Name.Contains("ReadOnly")) -#else - if (type.Name.Contains("ReadOnly", StringComparison.OrdinalIgnoreCase)) -#endif + if (type.IsGenericType == false) { - // Looks like this is read only type - // This covers ReadOnlyCollection in .Net 4.0 and above - // and also covers IReadOnlyCollection and IReadOnlyList in .net 4.5 and above + // The readonly types we are looking for are generic types return false; } - return true; + var definition = type.GetGenericTypeDefinition(); + + if (definition == typeof(ReadOnlyCollection<>)) + { + return true; + } + + if (definition == typeof(ReadOnlyDictionary<,>)) + { + return true; + } + + return false; } private static bool IsUnsupportedType(Type type) diff --git a/ModelBuilder/TypeCreators/TypeCreatorBase.cs b/ModelBuilder/TypeCreators/TypeCreatorBase.cs index b61d492..e2ab781 100644 --- a/ModelBuilder/TypeCreators/TypeCreatorBase.cs +++ b/ModelBuilder/TypeCreators/TypeCreatorBase.cs @@ -230,7 +230,7 @@ protected virtual object Create(IExecuteStrategy executeStrategy, var buildType = ResolveBuildType(executeStrategy.Configuration, type); - if (CanCreate(executeStrategy.Configuration, executeStrategy.BuildChain, type, referenceName) == false) + if (CanCreate(executeStrategy.Configuration, executeStrategy.BuildChain, buildType, referenceName) == false) { var message = string.Format( CultureInfo.CurrentCulture,