From 0a63717a24e3cc8720fa06916852ffc6ef2b6d8b Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 3 Aug 2019 15:26:16 +0100 Subject: [PATCH 1/8] Updating ReadableExpressions to v2.3.2 --- .../AgileMapper.PerformanceTester.Net45.csproj | 4 ++-- AgileMapper.PerformanceTester.Net45/packages.config | 2 +- .../AgileMapper.UnitTests.Net35.csproj | 4 ++-- AgileMapper.UnitTests.Net35/packages.config | 2 +- .../AgileMapper.UnitTests.NonParallel.csproj | 4 ++-- AgileMapper.UnitTests.NonParallel/packages.config | 2 +- AgileMapper.UnitTests/AgileMapper.UnitTests.csproj | 4 ++-- AgileMapper.UnitTests/packages.config | 2 +- AgileMapper/AgileMapper.csproj | 2 +- 9 files changed, 13 insertions(+), 13 deletions(-) diff --git a/AgileMapper.PerformanceTester.Net45/AgileMapper.PerformanceTester.Net45.csproj b/AgileMapper.PerformanceTester.Net45/AgileMapper.PerformanceTester.Net45.csproj index 32e86bc86..dc1e079ff 100644 --- a/AgileMapper.PerformanceTester.Net45/AgileMapper.PerformanceTester.Net45.csproj +++ b/AgileMapper.PerformanceTester.Net45/AgileMapper.PerformanceTester.Net45.csproj @@ -37,8 +37,8 @@ ..\packages\AgileObjects.NetStandardPolyfills.1.4.0\lib\net40\AgileObjects.NetStandardPolyfills.dll - - ..\packages\AgileObjects.ReadableExpressions.2.3.1\lib\net40\AgileObjects.ReadableExpressions.dll + + ..\packages\AgileObjects.ReadableExpressions.2.3.2\lib\net40\AgileObjects.ReadableExpressions.dll ..\packages\AutoMapper.7.0.1\lib\net45\AutoMapper.dll diff --git a/AgileMapper.PerformanceTester.Net45/packages.config b/AgileMapper.PerformanceTester.Net45/packages.config index 4352c4c8d..24b54cac7 100644 --- a/AgileMapper.PerformanceTester.Net45/packages.config +++ b/AgileMapper.PerformanceTester.Net45/packages.config @@ -1,7 +1,7 @@  - + diff --git a/AgileMapper.UnitTests.Net35/AgileMapper.UnitTests.Net35.csproj b/AgileMapper.UnitTests.Net35/AgileMapper.UnitTests.Net35.csproj index c014778c5..fec9a2f6d 100644 --- a/AgileMapper.UnitTests.Net35/AgileMapper.UnitTests.Net35.csproj +++ b/AgileMapper.UnitTests.Net35/AgileMapper.UnitTests.Net35.csproj @@ -37,8 +37,8 @@ ..\packages\AgileObjects.NetStandardPolyfills.1.4.0\lib\net35\AgileObjects.NetStandardPolyfills.dll - - ..\packages\AgileObjects.ReadableExpressions.2.3.1\lib\net35\AgileObjects.ReadableExpressions.dll + + ..\packages\AgileObjects.ReadableExpressions.2.3.2\lib\net35\AgileObjects.ReadableExpressions.dll ..\packages\DynamicLanguageRuntime.1.1.2\lib\Net35\Microsoft.Dynamic.dll diff --git a/AgileMapper.UnitTests.Net35/packages.config b/AgileMapper.UnitTests.Net35/packages.config index b15618b29..0f40009f4 100644 --- a/AgileMapper.UnitTests.Net35/packages.config +++ b/AgileMapper.UnitTests.Net35/packages.config @@ -1,7 +1,7 @@  - + diff --git a/AgileMapper.UnitTests.NonParallel/AgileMapper.UnitTests.NonParallel.csproj b/AgileMapper.UnitTests.NonParallel/AgileMapper.UnitTests.NonParallel.csproj index 6ad7ae3be..538211964 100644 --- a/AgileMapper.UnitTests.NonParallel/AgileMapper.UnitTests.NonParallel.csproj +++ b/AgileMapper.UnitTests.NonParallel/AgileMapper.UnitTests.NonParallel.csproj @@ -42,8 +42,8 @@ ..\packages\AgileObjects.NetStandardPolyfills.1.4.0\lib\net40\AgileObjects.NetStandardPolyfills.dll - - ..\packages\AgileObjects.ReadableExpressions.2.3.1\lib\net40\AgileObjects.ReadableExpressions.dll + + ..\packages\AgileObjects.ReadableExpressions.2.3.2\lib\net40\AgileObjects.ReadableExpressions.dll diff --git a/AgileMapper.UnitTests.NonParallel/packages.config b/AgileMapper.UnitTests.NonParallel/packages.config index 2a2122151..64734ef6e 100644 --- a/AgileMapper.UnitTests.NonParallel/packages.config +++ b/AgileMapper.UnitTests.NonParallel/packages.config @@ -1,7 +1,7 @@  - + diff --git a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj index 39082fdd9..5f63268b0 100644 --- a/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj +++ b/AgileMapper.UnitTests/AgileMapper.UnitTests.csproj @@ -44,8 +44,8 @@ ..\packages\AgileObjects.NetStandardPolyfills.1.4.0\lib\net40\AgileObjects.NetStandardPolyfills.dll - - ..\packages\AgileObjects.ReadableExpressions.2.3.1\lib\net40\AgileObjects.ReadableExpressions.dll + + ..\packages\AgileObjects.ReadableExpressions.2.3.2\lib\net40\AgileObjects.ReadableExpressions.dll ..\packages\Microsoft.Extensions.Primitives.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll diff --git a/AgileMapper.UnitTests/packages.config b/AgileMapper.UnitTests/packages.config index 09b6deb71..71b5a86a6 100644 --- a/AgileMapper.UnitTests/packages.config +++ b/AgileMapper.UnitTests/packages.config @@ -1,7 +1,7 @@  - + diff --git a/AgileMapper/AgileMapper.csproj b/AgileMapper/AgileMapper.csproj index 9d09ee765..aed85a6be 100644 --- a/AgileMapper/AgileMapper.csproj +++ b/AgileMapper/AgileMapper.csproj @@ -30,7 +30,7 @@ - + From 15e0e7aabda5f9e0063a064e71da6ef3352358c9 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 3 Aug 2019 15:35:25 +0100 Subject: [PATCH 2/8] Tidying --- .../DictionaryMappingConfigurator.cs | 21 ++++++++----------- .../TargetDictionaryMappingConfigurator.cs | 2 +- .../TargetDynamicMappingConfigurator.cs | 2 +- .../Api/Configuration/MappingConfigurator.cs | 20 ++++++++++-------- .../Configuration/ConfiguredIgnoredMember.cs | 6 +++--- .../Configuration/MappingConfigInfo.cs | 2 +- AgileMapper/Configuration/MemberSelector.cs | 2 +- AgileMapper/Constants.cs | 14 ++++++------- .../DataSources/ConfiguredDataSource.cs | 4 ++-- AgileMapper/DataSources/DataSourceSet.cs | 13 +++++++++--- AgileMapper/MappingRuleSet.cs | 7 ++++++- 11 files changed, 52 insertions(+), 41 deletions(-) diff --git a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs index 85ec2477b..81268fe80 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/DictionaryMappingConfigurator.cs @@ -17,12 +17,9 @@ internal class DictionaryMappingConfigurator : ISourceDynamicTargetTypeSelector #endif { - private readonly MappingConfigInfo _configInfo; - internal DictionaryMappingConfigurator(MappingConfigInfo configInfo) : base(configInfo) { - _configInfo = configInfo; } #region Mapping Settings @@ -102,24 +99,24 @@ private DictionaryMappingConfigurator RegisterElementKeyPattern( private MappingConfigInfo GetConfigInfo(DictionaryContext context) { - return (_configInfo.TargetType != typeof(object)) - ? _configInfo.Copy().Set(context) + return (ConfigInfo.TargetType != typeof(object)) + ? ConfigInfo.Copy().Set(context) : GetGlobalConfigInfo(context); } private MappingConfigInfo GetGlobalConfigInfo(DictionaryContext context) - => _configInfo.Copy().ForAllRuleSets().ForAllTargetTypes().Set(context); + => ConfigInfo.Copy().ForAllRuleSets().ForAllTargetTypes().Set(context); #region AndWhenMapping MappingConfigStartingPoint IGlobalDictionarySettings.AndWhenMapping - => new MappingConfigStartingPoint(_configInfo.MapperContext); + => new MappingConfigStartingPoint(ConfigInfo.MapperContext); public ISourceDictionaryTargetTypeSelector AndWhenMapping => this; #if FEATURE_DYNAMIC MappingConfigStartingPoint IGlobalDynamicSettings.AndWhenMapping - => new MappingConfigStartingPoint(_configInfo.MapperContext); + => new MappingConfigStartingPoint(ConfigInfo.MapperContext); ISourceDynamicTargetTypeSelector ISourceDynamicSettings.AndWhenMapping => this; #endif @@ -130,7 +127,7 @@ MappingConfigStartingPoint IGlobalDynamicSettings.AndWhenMapping #region Dictionaries public ISourceDictionaryMappingConfigurator To() - => CreateDictionaryConfigurator(_configInfo.ForAllRuleSets()); + => CreateDictionaryConfigurator(ConfigInfo.ForAllRuleSets()); public ISourceDictionaryMappingConfigurator ToANew() => CreateDictionaryConfigurator(Constants.CreateNew); @@ -143,7 +140,7 @@ public ISourceDictionaryMappingConfigurator Over() private SourceDictionaryMappingConfigurator CreateDictionaryConfigurator( string ruleSetName) - => CreateDictionaryConfigurator(_configInfo.ForRuleSet(ruleSetName)); + => CreateDictionaryConfigurator(ConfigInfo.ForRuleSet(ruleSetName)); private static SourceDictionaryMappingConfigurator CreateDictionaryConfigurator( MappingConfigInfo configInfo) @@ -155,7 +152,7 @@ private static SourceDictionaryMappingConfigurator CreateDictio #region Dynamics ISourceDynamicMappingConfigurator ISourceDynamicTargetTypeSelector.To() - => CreateDynamicConfigurator(_configInfo.ForAllRuleSets()); + => CreateDynamicConfigurator(ConfigInfo.ForAllRuleSets()); ISourceDynamicMappingConfigurator ISourceDynamicTargetTypeSelector.ToANew() => CreateDynamicConfigurator(Constants.CreateNew); @@ -168,7 +165,7 @@ ISourceDynamicMappingConfigurator ISourceDynamicTargetTypeSelector.Over private SourceDynamicMappingConfigurator CreateDynamicConfigurator( string ruleSetName) - => CreateDynamicConfigurator(_configInfo.ForRuleSet(ruleSetName)); + => CreateDynamicConfigurator(ConfigInfo.ForRuleSet(ruleSetName)); private static SourceDynamicMappingConfigurator CreateDynamicConfigurator( MappingConfigInfo configInfo) diff --git a/AgileMapper/Api/Configuration/Dictionaries/TargetDictionaryMappingConfigurator.cs b/AgileMapper/Api/Configuration/Dictionaries/TargetDictionaryMappingConfigurator.cs index e318ae5f9..8963140f9 100644 --- a/AgileMapper/Api/Configuration/Dictionaries/TargetDictionaryMappingConfigurator.cs +++ b/AgileMapper/Api/Configuration/Dictionaries/TargetDictionaryMappingConfigurator.cs @@ -52,7 +52,7 @@ public ICustomTargetDictionaryKeySpecifier MapMember MapMember private QualifiedMember GetSourceMemberOrThrow(LambdaExpression lambda) { - var sourceMember = lambda.Body.ToSourceMember(ConfigInfo.MapperContext); + var sourceMember = lambda.Body.ToSourceMember(MapperContext); if (sourceMember != null) { diff --git a/AgileMapper/Api/Configuration/MappingConfigurator.cs b/AgileMapper/Api/Configuration/MappingConfigurator.cs index 94a20e8a8..757096467 100644 --- a/AgileMapper/Api/Configuration/MappingConfigurator.cs +++ b/AgileMapper/Api/Configuration/MappingConfigurator.cs @@ -37,6 +37,8 @@ public MappingConfigurator(MappingConfigInfo configInfo) protected MapperContext MapperContext => ConfigInfo.MapperContext; + private UserConfigurationSet UserConfigurations => MapperContext.UserConfigurations; + #region IFullMappingInlineConfigurator Members public MappingConfigStartingPoint WhenMapping @@ -96,7 +98,7 @@ public IFullProjectionInlineConfigurator RecurseToDepth(int re { var depthSettings = new RecursionDepthSettings(ConfigInfo, recursionDepth); - ConfigInfo.MapperContext.UserConfigurations.Add(depthSettings); + UserConfigurations.Add(depthSettings); return this; } @@ -213,7 +215,7 @@ private FactorySpecifier CreateFactorySpecifier PassExceptionsTo(Action> callback) { - MapperContext.UserConfigurations.Add(new ExceptionCallback(ConfigInfo, callback.ToConstantExpression())); + UserConfigurations.Add(new ExceptionCallback(ConfigInfo, callback.ToConstantExpression())); return this; } @@ -223,13 +225,13 @@ public IFullMappingSettings PassExceptionsTo(Action SetMappedObjectCaching(bool cache) { - MapperContext.UserConfigurations.Add(new MappedObjectCachingSetting(ConfigInfo, cache)); + UserConfigurations.Add(new MappedObjectCachingSetting(ConfigInfo, cache)); return this; } public IFullMappingSettings MapNullCollectionsToNull() { - MapperContext.UserConfigurations.Add(new NullCollectionsSetting(ConfigInfo)); + UserConfigurations.Add(new NullCollectionsSetting(ConfigInfo)); return this; } @@ -239,7 +241,7 @@ public IFullMappingSettings MapNullCollectionsToNull() private IFullMappingSettings SetEntityKeyMapping(bool mapKeys) { - MapperContext.UserConfigurations.Add(new EntityKeyMappingSetting(ConfigInfo, mapKeys)); + UserConfigurations.Add(new EntityKeyMappingSetting(ConfigInfo, mapKeys)); return this; } @@ -251,7 +253,7 @@ public IFullMappingSettings DoNotAutoReverseConfiguredDataSour private IFullMappingSettings SetDataSourceReversal(bool reverse) { - MapperContext.UserConfigurations.Add(new DataSourceReversalSetting(ConfigInfo, reverse)); + UserConfigurations.Add(new DataSourceReversalSetting(ConfigInfo, reverse)); return this; } @@ -299,7 +301,7 @@ private MappingConfigContinuation IgnoreMembersByFilter( #else var configuredIgnoredMember = new ConfiguredIgnoredMember(ConfigInfo, memberFilter); #endif - MapperContext.UserConfigurations.Add(configuredIgnoredMember); + UserConfigurations.Add(configuredIgnoredMember); return new MappingConfigContinuation(ConfigInfo); } @@ -323,7 +325,7 @@ private MappingConfigContinuation IgnoreMembers( #else var configuredIgnoredMember = new ConfiguredIgnoredMember(ConfigInfo, targetMember); #endif - MapperContext.UserConfigurations.Add(configuredIgnoredMember); + UserConfigurations.Add(configuredIgnoredMember); ConfigInfo.NegateCondition(); } @@ -461,7 +463,7 @@ private MappingConfigContinuation RegisterMapToNullCondition() { var condition = new MapToNullCondition(ConfigInfo); - MapperContext.UserConfigurations.Add(condition); + UserConfigurations.Add(condition); return new MappingConfigContinuation(ConfigInfo); } diff --git a/AgileMapper/Configuration/ConfiguredIgnoredMember.cs b/AgileMapper/Configuration/ConfiguredIgnoredMember.cs index 7bf4c4ae1..e9fbd8189 100644 --- a/AgileMapper/Configuration/ConfiguredIgnoredMember.cs +++ b/AgileMapper/Configuration/ConfiguredIgnoredMember.cs @@ -1,14 +1,14 @@ namespace AgileObjects.AgileMapper.Configuration { using System; - using DataSources; - using Members; - using ReadableExpressions; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using DataSources; + using Members; + using ReadableExpressions; internal class ConfiguredIgnoredMember : UserConfiguredItemBase, diff --git a/AgileMapper/Configuration/MappingConfigInfo.cs b/AgileMapper/Configuration/MappingConfigInfo.cs index ee20152ba..afecc4864 100644 --- a/AgileMapper/Configuration/MappingConfigInfo.cs +++ b/AgileMapper/Configuration/MappingConfigInfo.cs @@ -17,7 +17,7 @@ internal class MappingConfigInfo : ITypePair { - private static readonly MappingRuleSet _allRuleSets = new MappingRuleSet("*", null, null, null, null, null, null); + private static readonly MappingRuleSet _allRuleSets = new MappingRuleSet("*"); public static readonly MappingConfigInfo AllRuleSetsSourceTypesAndTargetTypes = AllRuleSetsAndSourceTypes(null).ForAllTargetTypes(); diff --git a/AgileMapper/Configuration/MemberSelector.cs b/AgileMapper/Configuration/MemberSelector.cs index 13802401f..b45db9d5f 100644 --- a/AgileMapper/Configuration/MemberSelector.cs +++ b/AgileMapper/Configuration/MemberSelector.cs @@ -7,7 +7,7 @@ namespace AgileObjects.AgileMapper.Configuration using NetStandardPolyfills; /// - /// Provides a fluent interface to select members by their characteristics. + /// Provides a fluent interface to select target members by their characteristics. /// public class TargetMemberSelector { diff --git a/AgileMapper/Constants.cs b/AgileMapper/Constants.cs index 64462339d..d7f0161d7 100644 --- a/AgileMapper/Constants.cs +++ b/AgileMapper/Constants.cs @@ -3,13 +3,13 @@ using System; using System.Collections.Generic; using System.Linq; - using Extensions.Internal; - using NetStandardPolyfills; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Extensions.Internal; + using NetStandardPolyfills; internal static class Constants { @@ -23,13 +23,13 @@ internal static class Constants public static readonly Expression EmptyExpression = Expression.Empty(); - public const string CreateNew = "CreateNew"; + public const string CreateNew = nameof(CreateNew); - public const string Merge = "Merge"; + public const string Merge = nameof(Merge); - public const string Overwrite = "Overwrite"; + public const string Overwrite = nameof(Overwrite); - public const string Project = "Project"; + public const string Project = nameof(Project); public const int BeforeLoopExitCheck = 0; @@ -57,7 +57,7 @@ internal static class Constants typeof(ulong) }; - public static readonly Type[] NumericTypes = WholeNumberNumericTypes + public static readonly IList NumericTypes = WholeNumberNumericTypes .Append(new[] { typeof(float), typeof(decimal), typeof(double) }); public static readonly IDictionary NumericTypeMaxValuesByType = GetValuesByType("MaxValue"); diff --git a/AgileMapper/DataSources/ConfiguredDataSource.cs b/AgileMapper/DataSources/ConfiguredDataSource.cs index 319bfd5c7..37edb4f0e 100644 --- a/AgileMapper/DataSources/ConfiguredDataSource.cs +++ b/AgileMapper/DataSources/ConfiguredDataSource.cs @@ -1,12 +1,12 @@ namespace AgileObjects.AgileMapper.DataSources { - using Extensions.Internal; - using Members; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Extensions.Internal; + using Members; internal class ConfiguredDataSource : DataSourceBase, IConfiguredDataSource { diff --git a/AgileMapper/DataSources/DataSourceSet.cs b/AgileMapper/DataSources/DataSourceSet.cs index b66f1a425..73b1cb933 100644 --- a/AgileMapper/DataSources/DataSourceSet.cs +++ b/AgileMapper/DataSources/DataSourceSet.cs @@ -27,7 +27,7 @@ public DataSourceSet(IMemberMapperData mapperData, params IDataSource[] dataSour return; } - var variables = new List(); + var variables = default(List); for (var i = 0; i < dataSources.Length;) { @@ -45,6 +45,11 @@ public DataSourceSet(IMemberMapperData mapperData, params IDataSource[] dataSour if (dataSource.Variables.Any()) { + if (variables == null) + { + variables = new List(); + } + variables.AddRange(dataSource.Variables); } @@ -54,7 +59,9 @@ public DataSourceSet(IMemberMapperData mapperData, params IDataSource[] dataSour } } - Variables = variables; + Variables = (variables != null) + ? (IList)variables + : Enumerable.EmptyArray; } public IMemberMapperData MapperData { get; } @@ -85,7 +92,7 @@ private Expression BuildValueExpression() var dataSourceValue = dataSource.IsConditional ? Expression.Condition( dataSource.Condition, - isFirstDataSource + isFirstDataSource ? dataSource.Value : dataSource.Value.GetConversionTo(value.Type), isFirstDataSource diff --git a/AgileMapper/MappingRuleSet.cs b/AgileMapper/MappingRuleSet.cs index a84ac6611..151b26fa6 100644 --- a/AgileMapper/MappingRuleSet.cs +++ b/AgileMapper/MappingRuleSet.cs @@ -24,8 +24,8 @@ public MappingRuleSet( IMemberPopulationFactory populationFactory, IDataSourceFactory fallbackDataSourceFactory, IRootMapperKeyFactory rootMapperKeyFactory) + : this(name) { - Name = name; Settings = settings; EnumerablePopulationStrategy = enumerablePopulationStrategy; RepeatMappingStrategy = repeatMappingStrategy; @@ -34,6 +34,11 @@ public MappingRuleSet( RootMapperKeyFactory = rootMapperKeyFactory; } + public MappingRuleSet(string name) + { + Name = name; + } + public string Name { get; } public Expression NameConstant => _nameConstant ?? (_nameConstant = Name.ToConstantExpression()); From 4f5c2d7784b5f390b1691460ab96020eff9c6fad Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 3 Aug 2019 15:57:57 +0100 Subject: [PATCH 3/8] Moving member population logic into MemberPopulator from DataSourceSet and MemberPopulationFactory --- AgileMapper/DataSources/DataSourceSet.cs | 55 ++----- AgileMapper/MappingRuleSet.cs | 6 +- AgileMapper/MappingRuleSetCollection.cs | 11 +- AgileMapper/MappingRuleSetSettings.cs | 4 + .../DefaultMemberPopulationFactory.cs | 40 ----- .../Population/IMemberPopulationContext.cs | 5 - ...nFactory.cs => IPopulationGuardFactory.cs} | 4 +- ...s => MemberMergePopulationGuardFactory.cs} | 7 +- .../Population/MemberPopulationFactoryBase.cs | 86 ---------- .../Members/Population/MemberPopulator.cs | 151 +++++++++++++++--- .../Population/MemberPopulatorFactory.cs | 10 +- .../NullMemberPopulationGuardFactory.cs | 14 ++ .../DictionaryPopulationBuilder.cs | 8 +- 13 files changed, 177 insertions(+), 224 deletions(-) delete mode 100644 AgileMapper/Members/Population/DefaultMemberPopulationFactory.cs rename AgileMapper/Members/Population/{IMemberPopulationFactory.cs => IPopulationGuardFactory.cs} (57%) rename AgileMapper/Members/Population/{MemberMergePopulationFactory.cs => MemberMergePopulationGuardFactory.cs} (80%) delete mode 100644 AgileMapper/Members/Population/MemberPopulationFactoryBase.cs create mode 100644 AgileMapper/Members/Population/NullMemberPopulationGuardFactory.cs diff --git a/AgileMapper/DataSources/DataSourceSet.cs b/AgileMapper/DataSources/DataSourceSet.cs index 73b1cb933..18006ddc8 100644 --- a/AgileMapper/DataSources/DataSourceSet.cs +++ b/AgileMapper/DataSources/DataSourceSet.cs @@ -78,6 +78,8 @@ public DataSourceSet(IMemberMapperData mapperData, params IDataSource[] dataSour public IDataSource this[int index] => _dataSources[index]; + public int Count => _dataSources.Count; + public Expression ValueExpression => _value ?? (_value = BuildValueExpression()); private Expression BuildValueExpression() @@ -106,67 +108,30 @@ private Expression BuildValueExpression() return value; } - public Expression GetPopulationExpression() - { - var fallbackValue = GetFallbackValueOrNull(); - var excludeFallback = fallbackValue == null; - - Expression population = null; - - for (var i = _dataSources.Count - 1; i >= 0; --i) - { - var dataSource = _dataSources[i]; - - if (i == _dataSources.Count - 1) - { - if (excludeFallback) - { - continue; - } - - population = MapperData.GetTargetMemberPopulation(fallbackValue); - - if (dataSource.IsConditional) - { - population = dataSource.AddCondition(population); - } - - population = dataSource.AddPreCondition(population); - continue; - } - - var memberPopulation = MapperData.GetTargetMemberPopulation(dataSource.Value); - - population = dataSource.AddCondition(memberPopulation, population); - population = dataSource.AddPreCondition(population); - } - - return population; - } - - private Expression GetFallbackValueOrNull() + public Expression GetFinalValueOrNull() { var finalDataSource = _dataSources.Last(); - var fallbackValue = finalDataSource.Value; + var finalValue = finalDataSource.Value; if (finalDataSource.IsConditional || _dataSources.HasOne()) { - return fallbackValue; + return finalValue; } - if (fallbackValue.NodeType == ExpressionType.Coalesce) + if (finalValue.NodeType == ExpressionType.Coalesce) { - return ((BinaryExpression)fallbackValue).Right; + // Coalesce between the existing target member value and the fallback: + return ((BinaryExpression)finalValue).Right; } var targetMemberAccess = MapperData.GetTargetMemberAccess(); - if (ExpressionEvaluation.AreEqual(fallbackValue, targetMemberAccess)) + if (ExpressionEvaluation.AreEqual(finalValue, targetMemberAccess)) { return null; } - return fallbackValue; + return finalValue; } #region IEnumerable Members diff --git a/AgileMapper/MappingRuleSet.cs b/AgileMapper/MappingRuleSet.cs index 151b26fa6..c173768cb 100644 --- a/AgileMapper/MappingRuleSet.cs +++ b/AgileMapper/MappingRuleSet.cs @@ -21,7 +21,7 @@ public MappingRuleSet( MappingRuleSetSettings settings, IEnumerablePopulationStrategy enumerablePopulationStrategy, IRepeatMappingStrategy repeatMappingStrategy, - IMemberPopulationFactory populationFactory, + IPopulationGuardFactory populationGuardFactory, IDataSourceFactory fallbackDataSourceFactory, IRootMapperKeyFactory rootMapperKeyFactory) : this(name) @@ -29,7 +29,7 @@ public MappingRuleSet( Settings = settings; EnumerablePopulationStrategy = enumerablePopulationStrategy; RepeatMappingStrategy = repeatMappingStrategy; - PopulationFactory = populationFactory; + PopulationGuardFactory = populationGuardFactory; FallbackDataSourceFactory = fallbackDataSourceFactory; RootMapperKeyFactory = rootMapperKeyFactory; } @@ -49,7 +49,7 @@ public MappingRuleSet(string name) public IRepeatMappingStrategy RepeatMappingStrategy { get; } - public IMemberPopulationFactory PopulationFactory { get; } + public IPopulationGuardFactory PopulationGuardFactory { get; } public IDataSourceFactory FallbackDataSourceFactory { get; } diff --git a/AgileMapper/MappingRuleSetCollection.cs b/AgileMapper/MappingRuleSetCollection.cs index ab377af88..d30d50d94 100644 --- a/AgileMapper/MappingRuleSetCollection.cs +++ b/AgileMapper/MappingRuleSetCollection.cs @@ -19,7 +19,7 @@ internal class MappingRuleSetCollection MappingRuleSetSettings.ForInMemoryMapping(allowCloneEntityKeyMapping: true), default(CopySourceEnumerablePopulationStrategy), default(MapRepeatedCallRepeatMappingStrategy), - DefaultMemberPopulationFactory.Instance, + default(NullMemberPopulationGuardFactory), default(ExistingOrDefaultValueDataSourceFactory), default(RootMapperKeyFactory)); @@ -28,7 +28,7 @@ internal class MappingRuleSetCollection MappingRuleSetSettings.ForInMemoryMapping(rootHasPopulatedTarget: true), default(OverwriteEnumerablePopulationStrategy), default(MapRepeatedCallRepeatMappingStrategy), - DefaultMemberPopulationFactory.Instance, + default(NullMemberPopulationGuardFactory), default(DefaultValueDataSourceFactory), default(RootMapperKeyFactory)); @@ -40,22 +40,23 @@ internal class MappingRuleSetCollection UseSingleRootMappingExpression = true, AllowEntityKeyMapping = true, AllowCloneEntityKeyMapping = true, + AllowGuardedBindings = true, GuardAccessTo = value => value.Type.IsComplex(), ExpressionIsSupported = value => value.CanBeProjected(), AllowEnumerableAssignment = true }, default(ProjectSourceEnumerablePopulationStrategy), default(MapToDepthRepeatMappingStrategy), - DefaultMemberPopulationFactory.Instance, + default(NullMemberPopulationGuardFactory), default(DefaultValueDataSourceFactory), default(QueryProjectorMapperKeyFactory)); private static readonly MappingRuleSet _merge = new MappingRuleSet( Constants.Merge, - MappingRuleSetSettings.ForInMemoryMapping(rootHasPopulatedTarget: true), + MappingRuleSetSettings.ForInMemoryMapping(rootHasPopulatedTarget: true, allowGuardedBindings: false), default(MergeEnumerablePopulationStrategy), default(MapRepeatedCallRepeatMappingStrategy), - new MemberMergePopulationFactory(), + default(MemberMergePopulationGuardFactory), default(ExistingOrDefaultValueDataSourceFactory), default(RootMapperKeyFactory)); diff --git a/AgileMapper/MappingRuleSetSettings.cs b/AgileMapper/MappingRuleSetSettings.cs index b3acd85b3..45d62c50a 100644 --- a/AgileMapper/MappingRuleSetSettings.cs +++ b/AgileMapper/MappingRuleSetSettings.cs @@ -11,6 +11,7 @@ internal class MappingRuleSetSettings { public static MappingRuleSetSettings ForInMemoryMapping( bool rootHasPopulatedTarget = false, + bool allowGuardedBindings = true, bool allowCloneEntityKeyMapping = false) { return new MappingRuleSetSettings @@ -20,6 +21,7 @@ public static MappingRuleSetSettings ForInMemoryMapping( SourceElementsCouldBeNull = true, UseTryCatch = true, CheckDerivedSourceTypes = true, + AllowGuardedBindings = allowGuardedBindings, AllowCloneEntityKeyMapping = allowCloneEntityKeyMapping, GuardAccessTo = value => true, ExpressionIsSupported = value => true, @@ -44,6 +46,8 @@ public static MappingRuleSetSettings ForInMemoryMapping( public bool CheckDerivedSourceTypes { get; set; } + public bool AllowGuardedBindings { get; set; } + public bool AllowEntityKeyMapping { get; set; } public bool AllowCloneEntityKeyMapping { get; set; } diff --git a/AgileMapper/Members/Population/DefaultMemberPopulationFactory.cs b/AgileMapper/Members/Population/DefaultMemberPopulationFactory.cs deleted file mode 100644 index c1c8206e5..000000000 --- a/AgileMapper/Members/Population/DefaultMemberPopulationFactory.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace AgileObjects.AgileMapper.Members.Population -{ - using Extensions.Internal; -#if NET35 - using Microsoft.Scripting.Ast; -#else - using System.Linq.Expressions; -#endif - - internal class DefaultMemberPopulationFactory : MemberPopulationFactoryBase - { - public static readonly IMemberPopulationFactory Instance = new DefaultMemberPopulationFactory(); - - protected override Expression GetPopulationGuard(IMemberPopulationContext context) - => context.PopulateCondition; - - protected override Expression GetGuardedBindingValue(Expression bindingValue, Expression populationGuard) - { - if (populationGuard == null) - { - return bindingValue; - } - - return Expression.Condition( - populationGuard, - bindingValue, - bindingValue.Type.ToDefaultExpression()); - } - - public override Expression GetGuardedPopulation( - Expression population, - Expression populationGuard, - bool useSingleExpression) - { - return useSingleExpression - ? population - : base.GetGuardedPopulation(population, populationGuard, false); - } - } -} \ No newline at end of file diff --git a/AgileMapper/Members/Population/IMemberPopulationContext.cs b/AgileMapper/Members/Population/IMemberPopulationContext.cs index 277554294..de9ff6443 100644 --- a/AgileMapper/Members/Population/IMemberPopulationContext.cs +++ b/AgileMapper/Members/Population/IMemberPopulationContext.cs @@ -5,16 +5,11 @@ namespace AgileObjects.AgileMapper.Members.Population #else using System.Linq.Expressions; #endif - using DataSources; internal interface IMemberPopulationContext { IMemberMapperData MapperData { get; } - bool IsSuccessful { get; } - - DataSourceSet DataSources { get; } - Expression PopulateCondition { get; } } } \ No newline at end of file diff --git a/AgileMapper/Members/Population/IMemberPopulationFactory.cs b/AgileMapper/Members/Population/IPopulationGuardFactory.cs similarity index 57% rename from AgileMapper/Members/Population/IMemberPopulationFactory.cs rename to AgileMapper/Members/Population/IPopulationGuardFactory.cs index 0d1582192..d76c8ded5 100644 --- a/AgileMapper/Members/Population/IMemberPopulationFactory.cs +++ b/AgileMapper/Members/Population/IPopulationGuardFactory.cs @@ -6,8 +6,8 @@ namespace AgileObjects.AgileMapper.Members.Population using System.Linq.Expressions; #endif - internal interface IMemberPopulationFactory + internal interface IPopulationGuardFactory { - Expression GetPopulation(IMemberPopulationContext context); + Expression GetPopulationGuard(IMemberPopulationContext context); } } \ No newline at end of file diff --git a/AgileMapper/Members/Population/MemberMergePopulationFactory.cs b/AgileMapper/Members/Population/MemberMergePopulationGuardFactory.cs similarity index 80% rename from AgileMapper/Members/Population/MemberMergePopulationFactory.cs rename to AgileMapper/Members/Population/MemberMergePopulationGuardFactory.cs index f81f14429..b021ee35d 100644 --- a/AgileMapper/Members/Population/MemberMergePopulationFactory.cs +++ b/AgileMapper/Members/Population/MemberMergePopulationGuardFactory.cs @@ -6,9 +6,9 @@ namespace AgileObjects.AgileMapper.Members.Population using System.Linq.Expressions; #endif - internal class MemberMergePopulationFactory : MemberPopulationFactoryBase + internal struct MemberMergePopulationGuardFactory : IPopulationGuardFactory { - protected override Expression GetPopulationGuard(IMemberPopulationContext context) + public Expression GetPopulationGuard(IMemberPopulationContext context) { var mapperData = context.MapperData; var populateCondition = context.PopulateCondition; @@ -51,8 +51,5 @@ private static bool SkipPopulationGuarding(IBasicMapperData mapperData) return skipObjectValueGuarding; } - - protected override Expression GetGuardedBindingValue(Expression bindingValue, Expression populationGuard) - => bindingValue; } } \ No newline at end of file diff --git a/AgileMapper/Members/Population/MemberPopulationFactoryBase.cs b/AgileMapper/Members/Population/MemberPopulationFactoryBase.cs deleted file mode 100644 index ec8b55d77..000000000 --- a/AgileMapper/Members/Population/MemberPopulationFactoryBase.cs +++ /dev/null @@ -1,86 +0,0 @@ -namespace AgileObjects.AgileMapper.Members.Population -{ - using System.Collections.Generic; -#if NET35 - using Microsoft.Scripting.Ast; -#else - using System.Linq.Expressions; -#endif - using Extensions.Internal; - - internal abstract class MemberPopulationFactoryBase : IMemberPopulationFactory - { - public Expression GetPopulation(IMemberPopulationContext context) - { - if (!context.IsSuccessful) - { - return context.DataSources.ValueExpression; - } - - var useSingleExpression = context.MapperData.UseMemberInitialisations(); - var populationGuard = GetPopulationGuard(context); - - var population = useSingleExpression - ? GetBinding(context, populationGuard) - : context.MapperData.TargetMember.IsReadOnly - ? GetReadOnlyMemberPopulation(context) - : context.DataSources.GetPopulationExpression(); - - if (context.DataSources.Variables.Any()) - { - population = GetPopulationWithVariables(population, context.DataSources.Variables); - } - - return GetGuardedPopulation(population, populationGuard, useSingleExpression); - } - - private static Expression GetPopulationWithVariables(Expression population, IList variables) - { - if (population.NodeType != ExpressionType.Block) - { - return Expression.Block(variables, population); - } - - var populationBlock = (BlockExpression)population; - - if (populationBlock.Variables.Any()) - { - variables = variables.Append(populationBlock.Variables); - } - - return populationBlock.Update(variables, populationBlock.Expressions); - } - - protected abstract Expression GetPopulationGuard(IMemberPopulationContext context); - - private Expression GetBinding(IMemberPopulationContext context, Expression populationGuard) - { - var bindingValue = context.DataSources.ValueExpression; - var guardedBindingValue = GetGuardedBindingValue(bindingValue, populationGuard); - var binding = context.MapperData.GetTargetMemberPopulation(guardedBindingValue); - - return binding; - } - - protected abstract Expression GetGuardedBindingValue(Expression bindingValue, Expression populationGuard); - - private static Expression GetReadOnlyMemberPopulation(IMemberPopulationContext context) - { - var dataSourcesValue = context.DataSources.ValueExpression; - var targetMemberAccess = context.MapperData.GetTargetMemberAccess(); - var targetMemberNotNull = targetMemberAccess.GetIsNotDefaultComparison(); - - return Expression.IfThen(targetMemberNotNull, dataSourcesValue); - } - - public virtual Expression GetGuardedPopulation( - Expression population, - Expression populationGuard, - bool useSingleExpression) - { - return (populationGuard != null) - ? Expression.IfThen(populationGuard, population) - : population; - } - } -} \ No newline at end of file diff --git a/AgileMapper/Members/Population/MemberPopulator.cs b/AgileMapper/Members/Population/MemberPopulator.cs index 91cd82338..3776cee57 100644 --- a/AgileMapper/Members/Population/MemberPopulator.cs +++ b/AgileMapper/Members/Population/MemberPopulator.cs @@ -2,44 +2,39 @@ namespace AgileObjects.AgileMapper.Members.Population { using System; using System.Linq; - using Configuration; - using DataSources; - using ReadableExpressions; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Configuration; + using DataSources; + using Extensions.Internal; + using ReadableExpressions; internal class MemberPopulator : IMemberPopulationContext, IMemberPopulator { + private readonly DataSourceSet _dataSources; + private MemberPopulator(DataSourceSet dataSources, Expression populateCondition = null) { - DataSources = dataSources; + _dataSources = dataSources; PopulateCondition = populateCondition; } #region Factory Methods - public static IMemberPopulator WithRegistration( - IChildMemberMappingData mappingData, - DataSourceSet dataSources, - Expression populateCondition) + public static IMemberPopulator WithRegistration(DataSourceSet dataSources, Expression populateCondition) { - var memberPopulation = WithoutRegistration(mappingData, dataSources, populateCondition); + var memberPopulation = WithoutRegistration(dataSources, populateCondition); memberPopulation.MapperData.RegisterTargetMemberDataSourcesIfRequired(dataSources); return memberPopulation; } - public static IMemberPopulator WithoutRegistration( - IChildMemberMappingData mappingData, - DataSourceSet dataSources, - Expression populateCondition = null) - { - return new MemberPopulator(dataSources, populateCondition); - } + public static IMemberPopulator WithoutRegistration(DataSourceSet dataSources, Expression populateCondition = null) + => new MemberPopulator(dataSources, populateCondition); public static IMemberPopulator Unmappable(IMemberMapperData mapperData, string reason) => CreateNullMemberPopulation(mapperData, targetMember => $"No way to populate {targetMember.Name} ({reason})"); @@ -82,18 +77,126 @@ private static DataSourceSet CreateNullDataSourceSet( #endregion - public IMemberMapperData MapperData => DataSources.MapperData; - - public bool IsSuccessful => CanPopulate; + public IMemberMapperData MapperData => _dataSources.MapperData; - public bool CanPopulate => DataSources.HasValue; - - public DataSourceSet DataSources { get; } + public bool CanPopulate => _dataSources.HasValue; public Expression PopulateCondition { get; } public Expression GetPopulation() - => MapperData.RuleSet.PopulationFactory.GetPopulation(this); + { + if (!CanPopulate) + { + return _dataSources.ValueExpression; + } + + var populationGuard = MapperData + .RuleSet + .PopulationGuardFactory + .GetPopulationGuard(this); + + var useSingleExpression = MapperData.UseMemberInitialisations(); + + var population = useSingleExpression + ? GetBinding(populationGuard) + : MapperData.TargetMember.IsReadOnly + ? GetReadOnlyMemberPopulation() + : GetPopulationExpression(); + + if (_dataSources.Variables.Any()) + { + population = GetPopulationWithVariables(population); + } + + if (useSingleExpression && MapperData.RuleSet.Settings.AllowGuardedBindings) + { + return population; + } + + return (populationGuard != null) + ? Expression.IfThen(populationGuard, population) + : population; + } + + private Expression GetBinding(Expression populationGuard) + { + var bindingValue = _dataSources.ValueExpression; + + if (MapperData.RuleSet.Settings.AllowGuardedBindings && (populationGuard != null)) + { + bindingValue = Expression.Condition( + populationGuard, + bindingValue, + bindingValue.Type.ToDefaultExpression()); + } + + return MapperData.GetTargetMemberPopulation(bindingValue); + } + + private Expression GetReadOnlyMemberPopulation() + { + var targetMemberAccess = MapperData.GetTargetMemberAccess(); + var targetMemberNotNull = targetMemberAccess.GetIsNotDefaultComparison(); + + return Expression.IfThen(targetMemberNotNull, _dataSources.ValueExpression); + } + + private Expression GetPopulationExpression() + { + var finalValue = _dataSources.GetFinalValueOrNull(); + var excludeFinalValue = finalValue == null; + var finalDataSourceIndex = _dataSources.Count - 1; + + Expression population = null; + + for (var i = finalDataSourceIndex; i >= 0; --i) + { + var dataSource = _dataSources[i]; + + if (i == finalDataSourceIndex) + { + if (excludeFinalValue) + { + continue; + } + + population = MapperData.GetTargetMemberPopulation(finalValue); + + if (dataSource.IsConditional) + { + population = dataSource.AddCondition(population); + } + + population = dataSource.AddPreCondition(population); + continue; + } + + var memberPopulation = MapperData.GetTargetMemberPopulation(dataSource.Value); + + population = dataSource.AddCondition(memberPopulation, population); + population = dataSource.AddPreCondition(population); + } + + return population; + } + + private Expression GetPopulationWithVariables(Expression population) + { + if (population.NodeType != ExpressionType.Block) + { + return Expression.Block(_dataSources.Variables, population); + } + + var populationBlock = (BlockExpression)population; + var variables = _dataSources.Variables; + + if (Enumerable.Any(populationBlock.Variables)) + { + variables = variables.Append(populationBlock.Variables); + } + + return Expression.Block(variables, populationBlock.Expressions); + } #region ExcludeFromCodeCoverage #if DEBUG @@ -101,6 +204,6 @@ public Expression GetPopulation() #endif #endregion public override string ToString() - => $"{MapperData.TargetMember} ({DataSources.Count()} data source(s))"; + => $"{MapperData.TargetMember} ({_dataSources.Count()} data source(s))"; } } \ No newline at end of file diff --git a/AgileMapper/Members/Population/MemberPopulatorFactory.cs b/AgileMapper/Members/Population/MemberPopulatorFactory.cs index 731696cd4..74e2ef23b 100644 --- a/AgileMapper/Members/Population/MemberPopulatorFactory.cs +++ b/AgileMapper/Members/Population/MemberPopulatorFactory.cs @@ -20,7 +20,7 @@ internal class MemberPopulatorFactory GlobalContext.Instance .MemberCache .GetTargetMembers(mapperData.TargetType) - .ProjectToArray(tm => mapperData.TargetMember.Append(tm))); + .ProjectToArray(mapperData.TargetMember.Append)); private readonly Func> _targetMembersFactory; @@ -35,12 +35,12 @@ public IEnumerable Create(IObjectMappingData mappingData) .Invoke(mappingData.MapperData) .Project(tm => { - var memberPopulation = Create(tm, mappingData); + var memberPopulator = Create(tm, mappingData); - if (memberPopulation.CanPopulate || + if (memberPopulator.CanPopulate || mappingData.MappingContext.AddUnsuccessfulMemberPopulations) { - return memberPopulation; + return memberPopulator; } return null; @@ -73,7 +73,7 @@ private static IMemberPopulator Create(QualifiedMember targetMember, IObjectMapp return MemberPopulator.NoDataSource(childMapperData); } - return MemberPopulator.WithRegistration(childMappingData, dataSources, populateCondition); + return MemberPopulator.WithRegistration(dataSources, populateCondition); } private static bool TargetMemberIsUnmappable( diff --git a/AgileMapper/Members/Population/NullMemberPopulationGuardFactory.cs b/AgileMapper/Members/Population/NullMemberPopulationGuardFactory.cs new file mode 100644 index 000000000..9baa3ea45 --- /dev/null +++ b/AgileMapper/Members/Population/NullMemberPopulationGuardFactory.cs @@ -0,0 +1,14 @@ +namespace AgileObjects.AgileMapper.Members.Population +{ +#if NET35 + using Microsoft.Scripting.Ast; +#else + using System.Linq.Expressions; +#endif + + internal partial struct NullMemberPopulationGuardFactory : IPopulationGuardFactory + { + public Expression GetPopulationGuard(IMemberPopulationContext context) + => context.PopulateCondition; + } +} \ No newline at end of file diff --git a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs index e4a51e34e..2423119b4 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs @@ -237,7 +237,7 @@ private Expression GetPopulation( { var elementMapping = loopData.GetElementMapping(dictionaryMappingData); - if (dictionaryEntryMember.HasKey && + if (dictionaryEntryMember.HasKey && dictionaryEntryMember.CheckExistingElementValue && dictionaryMappingData.MapperData.TargetCouldBePopulated()) { @@ -256,14 +256,14 @@ private Expression GetPopulation( IObjectMappingData mappingData) { var elementMapperData = new ChildMemberMapperData(dictionaryEntryMember, MapperData); - var elementMappingData = mappingData.GetChildMappingData(elementMapperData); var sourceMember = mappingData.MapperData.SourceMember; var mappingDataSource = new AdHocDataSource(sourceMember, elementMapping); var mappingDataSources = new DataSourceSet(elementMapperData, mappingDataSource); - var memberPopulation = MemberPopulator.WithoutRegistration(elementMappingData, mappingDataSources); - var populationExpression = memberPopulation.GetPopulation(); + var populationExpression = MemberPopulator + .WithoutRegistration(mappingDataSources) + .GetPopulation(); return populationExpression; } From 980e47e1efedff71a75abd7e404766be9aaadf9d Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 3 Aug 2019 16:01:43 +0100 Subject: [PATCH 4/8] Optimising Array Prepend + Append calls --- .../Enumerables/Dictionaries/DictionaryPopulationBuilder.cs | 2 +- .../Dictionaries/SourceObjectDictionaryPopulationLoopData.cs | 4 ++-- .../Enumerables/EnumerablePopulationBuilder.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs index 2423119b4..df9c9545a 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/DictionaryPopulationBuilder.cs @@ -98,7 +98,7 @@ private Expression AssignDictionaryEntryFromKeyValuePair( var targetEntryAssignmentBlock = (BlockExpression)targetEntryAssignment; return Expression.Block( - targetEntryAssignmentBlock.Variables.Prepend(keyVariable), + targetEntryAssignmentBlock.Variables.Append(keyVariable), targetEntryAssignmentBlock.Expressions.Prepend(keyAssignment)); } diff --git a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryPopulationLoopData.cs b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryPopulationLoopData.cs index fe7240217..cc40bd781 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryPopulationLoopData.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/Dictionaries/SourceObjectDictionaryPopulationLoopData.cs @@ -73,8 +73,8 @@ public Expression Adapt(LoopExpression loop) DisposeEnumeratorIfNecessary); return Expression.Block( - new[] { _sourceEnumerableFound }.Append(enumerableLoopBlock.Variables), - new[] { assignSourceEnumerableFound }.Append(enumerableLoopBlock.Expressions)); + enumerableLoopBlock.Variables.Append(_sourceEnumerableFound), + enumerableLoopBlock.Expressions.Prepend(assignSourceEnumerableFound)); } public static BinaryExpression GetSourceEnumerableFoundTest( diff --git a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs index 04bc0a6f8..933a8e2d2 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/EnumerablePopulationBuilder.cs @@ -615,7 +615,7 @@ private static Expression GetValueCheckedElementMapping( var mappingTryCatchBodyBlock = (BlockExpression)mappingTryCatch.Body; mappingTryCatchBody = Expression.Block( - mappingTryCatchBodyBlock.Variables.Prepend(valueVariable), + mappingTryCatchBodyBlock.Variables.Append(valueVariable), existingElementValueCheck.Expressions.Append(mappingTryCatchBodyBlock.Expressions)); } From bb8cc599eef7fd836cf3d30d8f410b05f343d56f Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 3 Aug 2019 16:16:17 +0100 Subject: [PATCH 5/8] Organising fallback data sources --- AgileMapper/DataSources/DataSourceBase.cs | 2 + AgileMapper/DataSources/DataSourceSet.cs | 2 +- .../DataSources/DefaultValueDataSource.cs | 12 ---- .../DefaultValueDataSourceFactory.cs | 10 ---- .../DefaultValueFallbackDataSourceFactory.cs | 20 +++++++ .../ExistingMemberValueOrDefaultDataSource.cs | 50 ---------------- ...ExistingOrDefaultValueDataSourceFactory.cs | 10 ---- ...OrDefaultValueFallbackDataSourceFactory.cs | 60 +++++++++++++++++++ .../DataSources/Finders/DataSourceFinder.cs | 18 +++--- .../Finders/SourceMemberDataSourceFinder.cs | 3 +- AgileMapper/DataSources/IDataSource.cs | 2 + ...ctory.cs => IFallbackDataSourceFactory.cs} | 2 +- AgileMapper/MappingRuleSet.cs | 4 +- AgileMapper/MappingRuleSetCollection.cs | 8 +-- 14 files changed, 103 insertions(+), 100 deletions(-) delete mode 100644 AgileMapper/DataSources/DefaultValueDataSource.cs delete mode 100644 AgileMapper/DataSources/DefaultValueDataSourceFactory.cs create mode 100644 AgileMapper/DataSources/DefaultValueFallbackDataSourceFactory.cs delete mode 100644 AgileMapper/DataSources/ExistingMemberValueOrDefaultDataSource.cs delete mode 100644 AgileMapper/DataSources/ExistingOrDefaultValueDataSourceFactory.cs create mode 100644 AgileMapper/DataSources/ExistingOrDefaultValueFallbackDataSourceFactory.cs rename AgileMapper/DataSources/{IDataSourceFactory.cs => IFallbackDataSourceFactory.cs} (73%) diff --git a/AgileMapper/DataSources/DataSourceBase.cs b/AgileMapper/DataSources/DataSourceBase.cs index dca0ee6d9..06d27f183 100644 --- a/AgileMapper/DataSources/DataSourceBase.cs +++ b/AgileMapper/DataSources/DataSourceBase.cs @@ -185,6 +185,8 @@ private static bool IsNotOptionalEntityMemberId(IMemberMapperData mapperData) public bool IsConditional => Condition != null; + public virtual bool IsFallback => false; + public virtual Expression Condition { get; } public ICollection Variables { get; } diff --git a/AgileMapper/DataSources/DataSourceSet.cs b/AgileMapper/DataSources/DataSourceSet.cs index 18006ddc8..e52c97457 100644 --- a/AgileMapper/DataSources/DataSourceSet.cs +++ b/AgileMapper/DataSources/DataSourceSet.cs @@ -113,7 +113,7 @@ public Expression GetFinalValueOrNull() var finalDataSource = _dataSources.Last(); var finalValue = finalDataSource.Value; - if (finalDataSource.IsConditional || _dataSources.HasOne()) + if (!finalDataSource.IsFallback) { return finalValue; } diff --git a/AgileMapper/DataSources/DefaultValueDataSource.cs b/AgileMapper/DataSources/DefaultValueDataSource.cs deleted file mode 100644 index 38d1f8191..000000000 --- a/AgileMapper/DataSources/DefaultValueDataSource.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace AgileObjects.AgileMapper.DataSources -{ - using Members; - - internal class DefaultValueDataSource : DataSourceBase - { - public DefaultValueDataSource(IMemberMapperData mapperData) - : base(mapperData.SourceMember, mapperData.GetTargetMemberDefault()) - { - } - } -} \ No newline at end of file diff --git a/AgileMapper/DataSources/DefaultValueDataSourceFactory.cs b/AgileMapper/DataSources/DefaultValueDataSourceFactory.cs deleted file mode 100644 index de2890765..000000000 --- a/AgileMapper/DataSources/DefaultValueDataSourceFactory.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace AgileObjects.AgileMapper.DataSources -{ - using Members; - - internal struct DefaultValueDataSourceFactory : IDataSourceFactory - { - public IDataSource Create(IMemberMapperData mapperData) - => new DefaultValueDataSource(mapperData); - } -} \ No newline at end of file diff --git a/AgileMapper/DataSources/DefaultValueFallbackDataSourceFactory.cs b/AgileMapper/DataSources/DefaultValueFallbackDataSourceFactory.cs new file mode 100644 index 000000000..bc41dd2f7 --- /dev/null +++ b/AgileMapper/DataSources/DefaultValueFallbackDataSourceFactory.cs @@ -0,0 +1,20 @@ +namespace AgileObjects.AgileMapper.DataSources +{ + using Members; + + internal struct DefaultValueFallbackDataSourceFactory : IFallbackDataSourceFactory + { + public IDataSource Create(IMemberMapperData mapperData) + => new DefaultValueDataSource(mapperData); + + private class DefaultValueDataSource : DataSourceBase + { + public DefaultValueDataSource(IMemberMapperData mapperData) + : base(mapperData.SourceMember, mapperData.GetTargetMemberDefault()) + { + } + + public override bool IsFallback => true; + } + } +} \ No newline at end of file diff --git a/AgileMapper/DataSources/ExistingMemberValueOrDefaultDataSource.cs b/AgileMapper/DataSources/ExistingMemberValueOrDefaultDataSource.cs deleted file mode 100644 index 4525eb024..000000000 --- a/AgileMapper/DataSources/ExistingMemberValueOrDefaultDataSource.cs +++ /dev/null @@ -1,50 +0,0 @@ -namespace AgileObjects.AgileMapper.DataSources -{ - using Members; - using Members.Dictionaries; -#if NET35 - using Microsoft.Scripting.Ast; -#else - using System.Linq.Expressions; -#endif - - internal class ExistingMemberValueOrDefaultDataSource : DataSourceBase - { - public ExistingMemberValueOrDefaultDataSource(IMemberMapperData mapperData) - : base(mapperData.SourceMember, GetValue(mapperData), mapperData) - { - } - - private static Expression GetValue(IMemberMapperData mapperData) - { - if (mapperData.TargetMember.IsEnumerable) - { - return FallbackToCollection(mapperData) - ? mapperData.GetFallbackCollectionValue() - : mapperData.GetTargetMemberDefault(); - } - - if (mapperData.TargetMember.IsReadable && !mapperData.UseMemberInitialisations()) - { - return mapperData.GetTargetMemberAccess(); - } - - return mapperData.GetTargetMemberDefault(); - } - - private static bool FallbackToCollection(IBasicMapperData mapperData) - { - if (mapperData.TargetMember.IsDictionary) - { - return true; - } - - if (!(mapperData.TargetMember is DictionaryTargetMember dictionaryTargetMember)) - { - return true; - } - - return dictionaryTargetMember.HasEnumerableEntries; - } - } -} \ No newline at end of file diff --git a/AgileMapper/DataSources/ExistingOrDefaultValueDataSourceFactory.cs b/AgileMapper/DataSources/ExistingOrDefaultValueDataSourceFactory.cs deleted file mode 100644 index c1335c979..000000000 --- a/AgileMapper/DataSources/ExistingOrDefaultValueDataSourceFactory.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace AgileObjects.AgileMapper.DataSources -{ - using Members; - - internal struct ExistingOrDefaultValueDataSourceFactory : IDataSourceFactory - { - public IDataSource Create(IMemberMapperData mapperData) - => new ExistingMemberValueOrDefaultDataSource(mapperData); - } -} \ No newline at end of file diff --git a/AgileMapper/DataSources/ExistingOrDefaultValueFallbackDataSourceFactory.cs b/AgileMapper/DataSources/ExistingOrDefaultValueFallbackDataSourceFactory.cs new file mode 100644 index 000000000..95f6e6a0f --- /dev/null +++ b/AgileMapper/DataSources/ExistingOrDefaultValueFallbackDataSourceFactory.cs @@ -0,0 +1,60 @@ +namespace AgileObjects.AgileMapper.DataSources +{ +#if NET35 + using Microsoft.Scripting.Ast; +#else + using System.Linq.Expressions; +#endif + using Members; + using Members.Dictionaries; + + internal struct ExistingOrDefaultValueFallbackDataSourceFactory : IFallbackDataSourceFactory + { + public IDataSource Create(IMemberMapperData mapperData) + => new ExistingMemberValueOrDefaultDataSource(mapperData); + + private class ExistingMemberValueOrDefaultDataSource : DataSourceBase + { + public ExistingMemberValueOrDefaultDataSource(IMemberMapperData mapperData) + : base(mapperData.SourceMember, GetValue(mapperData), mapperData) + { + } + + private static Expression GetValue(IMemberMapperData mapperData) + { + if (mapperData.TargetMember.IsEnumerable) + { + return FallbackToCollection(mapperData) + ? mapperData.GetFallbackCollectionValue() + : mapperData.GetTargetMemberDefault(); + } + + if (mapperData.TargetMember.IsReadable && !mapperData.UseMemberInitialisations()) + { + return mapperData.TargetMember.IsSimple + ? Constants.EmptyExpression + : mapperData.GetTargetMemberAccess(); + } + + return mapperData.GetTargetMemberDefault(); + } + + private static bool FallbackToCollection(IBasicMapperData mapperData) + { + if (mapperData.TargetMember.IsDictionary) + { + return true; + } + + if (!(mapperData.TargetMember is DictionaryTargetMember dictionaryTargetMember)) + { + return true; + } + + return dictionaryTargetMember.HasEnumerableEntries; + } + + public override bool IsFallback => true; + } + } +} \ No newline at end of file diff --git a/AgileMapper/DataSources/Finders/DataSourceFinder.cs b/AgileMapper/DataSources/Finders/DataSourceFinder.cs index cf2cab9ca..e0b08824d 100644 --- a/AgileMapper/DataSources/Finders/DataSourceFinder.cs +++ b/AgileMapper/DataSources/Finders/DataSourceFinder.cs @@ -6,6 +6,14 @@ internal struct DataSourceFinder { + private static readonly IDataSourceFinder[] _finders = + { + default(ConfiguredDataSourceFinder), + default(MaptimeDataSourceFinder), + default(SourceMemberDataSourceFinder), + default(MetaMemberDataSourceFinder) + }; + public static DataSourceSet FindFor(IChildMemberMappingData childMappingData) { var findContext = new DataSourceFindContext(childMappingData); @@ -16,7 +24,7 @@ public static DataSourceSet FindFor(IChildMemberMappingData childMappingData) private static IEnumerable EnumerateDataSources(DataSourceFindContext context) { - foreach (var finder in EnumerateFinders()) + foreach (var finder in _finders) { foreach (var dataSource in finder.FindFor(context)) { @@ -39,13 +47,5 @@ private static IEnumerable EnumerateDataSources(DataSourceFindConte } } } - - private static IEnumerable EnumerateFinders() - { - yield return default(ConfiguredDataSourceFinder); - yield return default(MaptimeDataSourceFinder); - yield return default(SourceMemberDataSourceFinder); - yield return default(MetaMemberDataSourceFinder); - } } } \ No newline at end of file diff --git a/AgileMapper/DataSources/Finders/SourceMemberDataSourceFinder.cs b/AgileMapper/DataSources/Finders/SourceMemberDataSourceFinder.cs index 0ec0b02da..988260d43 100644 --- a/AgileMapper/DataSources/Finders/SourceMemberDataSourceFinder.cs +++ b/AgileMapper/DataSources/Finders/SourceMemberDataSourceFinder.cs @@ -32,7 +32,8 @@ public IEnumerable FindFor(DataSourceFindContext context) yield return context.GetFallbackDataSource(); } - if (matchingSourceMemberDataSource.SourceMember == null) + if (hasUseableSourceMember || + (matchingSourceMemberDataSource.SourceMember == null)) { yield break; } diff --git a/AgileMapper/DataSources/IDataSource.cs b/AgileMapper/DataSources/IDataSource.cs index 218c1249b..98c72d870 100644 --- a/AgileMapper/DataSources/IDataSource.cs +++ b/AgileMapper/DataSources/IDataSource.cs @@ -19,6 +19,8 @@ internal interface IDataSource : IConditionallyChainable bool IsConditional { get; } + bool IsFallback { get; } + ICollection Variables { get; } Expression AddPreCondition(Expression population); diff --git a/AgileMapper/DataSources/IDataSourceFactory.cs b/AgileMapper/DataSources/IFallbackDataSourceFactory.cs similarity index 73% rename from AgileMapper/DataSources/IDataSourceFactory.cs rename to AgileMapper/DataSources/IFallbackDataSourceFactory.cs index 7701f870b..d3d4c2ddf 100644 --- a/AgileMapper/DataSources/IDataSourceFactory.cs +++ b/AgileMapper/DataSources/IFallbackDataSourceFactory.cs @@ -2,7 +2,7 @@ namespace AgileObjects.AgileMapper.DataSources { using Members; - internal interface IDataSourceFactory + internal interface IFallbackDataSourceFactory { IDataSource Create(IMemberMapperData mapperData); } diff --git a/AgileMapper/MappingRuleSet.cs b/AgileMapper/MappingRuleSet.cs index c173768cb..9d015972e 100644 --- a/AgileMapper/MappingRuleSet.cs +++ b/AgileMapper/MappingRuleSet.cs @@ -22,7 +22,7 @@ public MappingRuleSet( IEnumerablePopulationStrategy enumerablePopulationStrategy, IRepeatMappingStrategy repeatMappingStrategy, IPopulationGuardFactory populationGuardFactory, - IDataSourceFactory fallbackDataSourceFactory, + IFallbackDataSourceFactory fallbackDataSourceFactory, IRootMapperKeyFactory rootMapperKeyFactory) : this(name) { @@ -51,7 +51,7 @@ public MappingRuleSet(string name) public IPopulationGuardFactory PopulationGuardFactory { get; } - public IDataSourceFactory FallbackDataSourceFactory { get; } + public IFallbackDataSourceFactory FallbackDataSourceFactory { get; } public IRootMapperKeyFactory RootMapperKeyFactory { get; } } diff --git a/AgileMapper/MappingRuleSetCollection.cs b/AgileMapper/MappingRuleSetCollection.cs index d30d50d94..8b9b91372 100644 --- a/AgileMapper/MappingRuleSetCollection.cs +++ b/AgileMapper/MappingRuleSetCollection.cs @@ -20,7 +20,7 @@ internal class MappingRuleSetCollection default(CopySourceEnumerablePopulationStrategy), default(MapRepeatedCallRepeatMappingStrategy), default(NullMemberPopulationGuardFactory), - default(ExistingOrDefaultValueDataSourceFactory), + default(ExistingOrDefaultValueFallbackDataSourceFactory), default(RootMapperKeyFactory)); private static readonly MappingRuleSet _overwrite = new MappingRuleSet( @@ -29,7 +29,7 @@ internal class MappingRuleSetCollection default(OverwriteEnumerablePopulationStrategy), default(MapRepeatedCallRepeatMappingStrategy), default(NullMemberPopulationGuardFactory), - default(DefaultValueDataSourceFactory), + default(DefaultValueFallbackDataSourceFactory), default(RootMapperKeyFactory)); private static readonly MappingRuleSet _project = new MappingRuleSet( @@ -48,7 +48,7 @@ internal class MappingRuleSetCollection default(ProjectSourceEnumerablePopulationStrategy), default(MapToDepthRepeatMappingStrategy), default(NullMemberPopulationGuardFactory), - default(DefaultValueDataSourceFactory), + default(DefaultValueFallbackDataSourceFactory), default(QueryProjectorMapperKeyFactory)); private static readonly MappingRuleSet _merge = new MappingRuleSet( @@ -57,7 +57,7 @@ internal class MappingRuleSetCollection default(MergeEnumerablePopulationStrategy), default(MapRepeatedCallRepeatMappingStrategy), default(MemberMergePopulationGuardFactory), - default(ExistingOrDefaultValueDataSourceFactory), + default(ExistingOrDefaultValueFallbackDataSourceFactory), default(RootMapperKeyFactory)); public static readonly MappingRuleSetCollection Default = From 793218bdb188f1124c9226a54c3b596c916ae33b Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 3 Aug 2019 16:19:43 +0100 Subject: [PATCH 6/8] Moving logic into EnumerableTypeHelper --- .../Finders/MetaMemberDataSourceFinder.cs | 13 ++----------- .../Enumerables/EnumerableTypeHelper.cs | 18 ++++++++++++++---- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/AgileMapper/DataSources/Finders/MetaMemberDataSourceFinder.cs b/AgileMapper/DataSources/Finders/MetaMemberDataSourceFinder.cs index 8d0421445..6974810d2 100644 --- a/AgileMapper/DataSources/Finders/MetaMemberDataSourceFinder.cs +++ b/AgileMapper/DataSources/Finders/MetaMemberDataSourceFinder.cs @@ -371,16 +371,7 @@ private static Expression GetHasEnumerableCheck(Expression enumerableAccess) return helper.IsEnumerableOrQueryable ? GetLinqMethodCall(nameof(Enumerable.Any), enumerableAccess, helper) - : GetEnumerableCountCheck(enumerableAccess, helper); - } - - public static Expression GetEnumerableCountCheck(Expression enumerableAccess, EnumerableTypeHelper helper) - { - var enumerableCount = helper.GetCountFor(enumerableAccess); - var zero = ToNumericConverter.Zero.GetConversionTo(enumerableCount.Type); - var countGreaterThanZero = Expression.GreaterThan(enumerableCount, zero); - - return countGreaterThanZero; + : helper.GetNonZeroCountCheck(enumerableAccess); } } @@ -447,7 +438,7 @@ public override Expression GetAccess(Expression parentInstance) private Expression GetCondition(Expression enumerableAccess, EnumerableTypeHelper helper) { - var enumerableCheck = HasMetaMemberPart.GetEnumerableCountCheck(enumerableAccess, helper); + var enumerableCheck = helper.GetNonZeroCountCheck(enumerableAccess); if (MapperData.RuleSet.Settings.GuardAccessTo(enumerableAccess)) { diff --git a/AgileMapper/ObjectPopulation/Enumerables/EnumerableTypeHelper.cs b/AgileMapper/ObjectPopulation/Enumerables/EnumerableTypeHelper.cs index d5241e0e6..c5d5abc41 100644 --- a/AgileMapper/ObjectPopulation/Enumerables/EnumerableTypeHelper.cs +++ b/AgileMapper/ObjectPopulation/Enumerables/EnumerableTypeHelper.cs @@ -4,15 +4,16 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.Enumerables using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; - using Extensions.Internal; - using Members; - using NetStandardPolyfills; - using ReadableExpressions.Extensions; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Extensions.Internal; + using Members; + using NetStandardPolyfills; + using ReadableExpressions.Extensions; + using TypeConversion; internal class EnumerableTypeHelper { @@ -198,6 +199,15 @@ private static bool ValueIsNotEnumerableInterface(Expression instance) public Expression GetCountFor(Expression instance, Type countType = null) => instance.GetCount(countType, exp => CollectionInterfaceType); + public Expression GetNonZeroCountCheck(Expression enumerableAccess) + { + var enumerableCount = GetCountFor(enumerableAccess); + var zero = ToNumericConverter.Zero.GetConversionTo(enumerableCount.Type); + var countGreaterThanZero = Expression.GreaterThan(enumerableCount, zero); + + return countGreaterThanZero; + } + public Type GetEmptyInstanceCreationFallbackType() { if (IsArray) From c84dc103da7b937f373f82856ce34eaaff125e36 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 3 Aug 2019 16:27:00 +0100 Subject: [PATCH 7/8] Avoiding yielding null expressions from population factory --- .../PopulationExpressionFactoryBase.cs | 34 ++++++++++++------- .../DictionaryMappingExpressionFactory.cs | 29 ++++++++++------ .../MappingExpressionFactoryBase.cs | 5 +-- 3 files changed, 42 insertions(+), 26 deletions(-) diff --git a/AgileMapper/ObjectPopulation/ComplexTypes/PopulationExpressionFactoryBase.cs b/AgileMapper/ObjectPopulation/ComplexTypes/PopulationExpressionFactoryBase.cs index 133cfa07c..14eb6020c 100644 --- a/AgileMapper/ObjectPopulation/ComplexTypes/PopulationExpressionFactoryBase.cs +++ b/AgileMapper/ObjectPopulation/ComplexTypes/PopulationExpressionFactoryBase.cs @@ -26,16 +26,26 @@ public IEnumerable GetPopulation(MappingCreationContext context) if (context.InstantiateLocalVariable && mapperData.Context.UseLocalVariable) { - yield return preCreationCallback; - - var assignCreatedObject = postCreationCallback != null; + if (preCreationCallback != null) + { + yield return preCreationCallback; + } + + var hasPostCreationCallback = postCreationCallback != null; + var assignCreatedObject = hasPostCreationCallback; yield return GetLocalVariableInstantiation(assignCreatedObject, populationsAndCallbacks, mappingData); - yield return postCreationCallback; + if (hasPostCreationCallback) + { + yield return postCreationCallback; + } } - yield return GetObjectRegistrationCallOrNull(mapperData); + if (IncludeObjectRegistration(mapperData)) + { + yield return GetObjectRegistrationCall(mapperData); + } foreach (var population in populationsAndCallbacks) { @@ -113,15 +123,15 @@ protected virtual Expression GetTargetObjectCreation( #region Object Registration - private static Expression GetObjectRegistrationCallOrNull(ObjectMapperData mapperData) + private static bool IncludeObjectRegistration(ObjectMapperData mapperData) { - if (mapperData.TargetTypeWillNotBeMappedAgain || - !mapperData.CacheMappedObjects || - !mapperData.RuleSet.Settings.AllowObjectTracking) - { - return null; - } + return mapperData.CacheMappedObjects && + mapperData.RuleSet.Settings.AllowObjectTracking && + !mapperData.TargetTypeWillNotBeMappedAgain; + } + private static Expression GetObjectRegistrationCall(ObjectMapperData mapperData) + { var registerMethod = typeof(IObjectMappingDataUntyped) .GetPublicInstanceMethod(nameof(IObjectMappingDataUntyped.Register)) .MakeGenericMethod(mapperData.SourceType, mapperData.TargetType); diff --git a/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs b/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs index 19f249bbc..f84553f4c 100644 --- a/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs +++ b/AgileMapper/ObjectPopulation/DictionaryMappingExpressionFactory.cs @@ -56,7 +56,7 @@ private static IEnumerable GetAllTargetMembers(ObjectMapperData return allTargetMembers; } - private static IEnumerable EnumerateAllTargetMembers(ObjectMapperData mapperData) + private static IEnumerable EnumerateAllTargetMembers(ObjectMapperData mapperData) { var sourceMembers = GlobalContext.Instance.MemberCache.GetSourceMembers(mapperData.SourceType); var targetDictionaryMember = (DictionaryTargetMember)mapperData.TargetMember; @@ -207,16 +207,15 @@ private static IEnumerable GetNestedFlattenedMembers( .ToArray(); } - private static DictionaryTargetMember[] GetConfiguredTargetMembers( + private static QualifiedMember[] GetConfiguredTargetMembers( IEnumerable configuredDataSourceFactories, - IList targetMembersFromSource) + IList targetMembersFromSource) { return configuredDataSourceFactories .GroupBy(dsf => dsf.TargetDictionaryEntryMember.Name) .Project(group => { - var factory = group.First(); - var targetMember = factory.TargetDictionaryEntryMember; + QualifiedMember targetMember = group.First().TargetDictionaryEntryMember; targetMember.IsCustom = targetMembersFromSource.None( sourceMember => sourceMember.RegistrationName == targetMember.Name); @@ -277,10 +276,12 @@ protected override Expression GetNullMappingFallbackValue(IMemberMapperData mapp protected override IEnumerable GetObjectPopulation(MappingCreationContext context) { + Expression population; + if (!context.MapperData.TargetMember.IsDictionary) { - yield return GetDictionaryPopulation(context.MappingData); - yield break; + population = GetDictionaryPopulation(context.MappingData); + goto ReturnPopulation; } var assignmentFactory = GetDictionaryAssignmentFactoryOrNull(context, out var useAssignmentOnly); @@ -291,11 +292,19 @@ protected override IEnumerable GetObjectPopulation(MappingCreationCo yield break; } - var population = GetDictionaryPopulation(context.MappingData); + population = GetDictionaryPopulation(context.MappingData); var assignment = assignmentFactory?.Invoke(context.MappingData); - yield return assignment; - yield return population; + if (assignment != null) + { + yield return assignment; + } + + ReturnPopulation: + if (population != null) + { + yield return population; + } } private static Func GetDictionaryAssignmentFactoryOrNull( diff --git a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs index 430382e11..a73872320 100644 --- a/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs +++ b/AgileMapper/ObjectPopulation/MappingExpressionFactoryBase.cs @@ -103,14 +103,11 @@ protected virtual IEnumerable GetShortCircuitReturns(GotoExpression private void AddPopulationsAndCallbacks(MappingCreationContext context) { context.MappingExpressions.AddUnlessNullOrEmpty(context.PreMappingCallback); - context.MappingExpressions.AddRange(GetNonNullObjectPopulation(context)); + context.MappingExpressions.AddRange(GetObjectPopulation(context)); context.MappingExpressions.AddRange(GetConfiguredToTargetDataSourceMappings(context)); context.MappingExpressions.AddUnlessNullOrEmpty(context.PostMappingCallback); } - private IEnumerable GetNonNullObjectPopulation(MappingCreationContext context) - => GetObjectPopulation(context).WhereNotNull(); - protected abstract IEnumerable GetObjectPopulation(MappingCreationContext context); private IEnumerable GetConfiguredToTargetDataSourceMappings(MappingCreationContext context) From 37d7b31d3275f986143ce8a69b2550bc8226e473 Mon Sep 17 00:00:00 2001 From: Steve Wilkes Date: Sat, 3 Aug 2019 16:56:28 +0100 Subject: [PATCH 8/8] Tidying --- .../DefaultValueFallbackDataSourceFactory.cs | 6 +- .../DataSources/DictionaryEntryDataSource.cs | 2 +- .../EnumerableMappingDataSource.cs | 10 +- ...OrDefaultValueFallbackDataSourceFactory.cs | 6 +- .../Finders/DataSourceFindContext.cs | 4 +- .../Internal/EnumerableExtensions.cs | 11 +- .../Internal/StringExpressionExtensions.cs | 22 +- AgileMapper/Members/ExpressionInfoFinder.cs | 8 +- .../ComplexTypeMappingExpressionFactory.cs | 11 +- ...ltiStatementPopulationExpressionFactory.cs | 4 +- .../PopulationExpressionFactoryBase.cs | 2 +- .../DerivedComplexTypeMappingsFactory.cs | 189 +++++++++++------- .../MappingCreationContext.cs | 6 +- AgileMapper/TypeConversion/ToBoolConverter.cs | 4 +- .../TypeConversion/ToCharacterConverter.cs | 7 +- AgileMapper/TypeConversion/ToEnumConverter.cs | 21 +- .../ToFormattedStringConverter.cs | 8 +- .../TypeConversion/ToNumericConverter.cs | 6 +- 18 files changed, 181 insertions(+), 146 deletions(-) diff --git a/AgileMapper/DataSources/DefaultValueFallbackDataSourceFactory.cs b/AgileMapper/DataSources/DefaultValueFallbackDataSourceFactory.cs index bc41dd2f7..84a7d58cc 100644 --- a/AgileMapper/DataSources/DefaultValueFallbackDataSourceFactory.cs +++ b/AgileMapper/DataSources/DefaultValueFallbackDataSourceFactory.cs @@ -5,11 +5,11 @@ namespace AgileObjects.AgileMapper.DataSources internal struct DefaultValueFallbackDataSourceFactory : IFallbackDataSourceFactory { public IDataSource Create(IMemberMapperData mapperData) - => new DefaultValueDataSource(mapperData); + => new DefaultValueFallbackDataSource(mapperData); - private class DefaultValueDataSource : DataSourceBase + private class DefaultValueFallbackDataSource : DataSourceBase { - public DefaultValueDataSource(IMemberMapperData mapperData) + public DefaultValueFallbackDataSource(IMemberMapperData mapperData) : base(mapperData.SourceMember, mapperData.GetTargetMemberDefault()) { } diff --git a/AgileMapper/DataSources/DictionaryEntryDataSource.cs b/AgileMapper/DataSources/DictionaryEntryDataSource.cs index d6b6d76b0..9783ea227 100644 --- a/AgileMapper/DataSources/DictionaryEntryDataSource.cs +++ b/AgileMapper/DataSources/DictionaryEntryDataSource.cs @@ -1,11 +1,11 @@ namespace AgileObjects.AgileMapper.DataSources { - using Extensions.Internal; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Extensions.Internal; internal class DictionaryEntryDataSource : DataSourceBase { diff --git a/AgileMapper/DataSources/EnumerableMappingDataSource.cs b/AgileMapper/DataSources/EnumerableMappingDataSource.cs index 059f1d13a..939a6b48f 100644 --- a/AgileMapper/DataSources/EnumerableMappingDataSource.cs +++ b/AgileMapper/DataSources/EnumerableMappingDataSource.cs @@ -2,16 +2,16 @@ { using System; using System.Linq; - using Extensions; - using Extensions.Internal; - using Members; - using ObjectPopulation; - using ObjectPopulation.Enumerables; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Extensions; + using Extensions.Internal; + using Members; + using ObjectPopulation; + using ObjectPopulation.Enumerables; internal class EnumerableMappingDataSource : DataSourceBase { diff --git a/AgileMapper/DataSources/ExistingOrDefaultValueFallbackDataSourceFactory.cs b/AgileMapper/DataSources/ExistingOrDefaultValueFallbackDataSourceFactory.cs index 95f6e6a0f..f136fd257 100644 --- a/AgileMapper/DataSources/ExistingOrDefaultValueFallbackDataSourceFactory.cs +++ b/AgileMapper/DataSources/ExistingOrDefaultValueFallbackDataSourceFactory.cs @@ -11,11 +11,11 @@ namespace AgileObjects.AgileMapper.DataSources internal struct ExistingOrDefaultValueFallbackDataSourceFactory : IFallbackDataSourceFactory { public IDataSource Create(IMemberMapperData mapperData) - => new ExistingMemberValueOrDefaultDataSource(mapperData); + => new ExistingValueOrDefaultFallbackDataSource(mapperData); - private class ExistingMemberValueOrDefaultDataSource : DataSourceBase + private class ExistingValueOrDefaultFallbackDataSource : DataSourceBase { - public ExistingMemberValueOrDefaultDataSource(IMemberMapperData mapperData) + public ExistingValueOrDefaultFallbackDataSource(IMemberMapperData mapperData) : base(mapperData.SourceMember, GetValue(mapperData), mapperData) { } diff --git a/AgileMapper/DataSources/Finders/DataSourceFindContext.cs b/AgileMapper/DataSources/Finders/DataSourceFindContext.cs index 5103d28cd..96f485294 100644 --- a/AgileMapper/DataSources/Finders/DataSourceFindContext.cs +++ b/AgileMapper/DataSources/Finders/DataSourceFindContext.cs @@ -26,7 +26,7 @@ public DataSourceFindContext(IChildMemberMappingData childMappingData) GetConfiguredDataSources(originalChildMapperData)); } - private IList GetConfiguredDataSources(IMemberMapperData mapperData) + private IList GetConfiguredDataSources(IMemberMapperData mapperData) => MapperContext.UserConfigurations.GetDataSources(mapperData); public IChildMemberMappingData ChildMappingData { get; } @@ -46,7 +46,7 @@ public IDataSource GetFallbackDataSource() public IDataSource GetFinalDataSource(IDataSource foundDataSource) => GetFinalDataSource(foundDataSource, ChildMappingData); - + public IDataSource GetFinalDataSource(IDataSource foundDataSource, IChildMemberMappingData mappingData) { var childTargetMember = mappingData.MapperData.TargetMember; diff --git a/AgileMapper/Extensions/Internal/EnumerableExtensions.cs b/AgileMapper/Extensions/Internal/EnumerableExtensions.cs index cb57c3633..0d5d4fa60 100644 --- a/AgileMapper/Extensions/Internal/EnumerableExtensions.cs +++ b/AgileMapper/Extensions/Internal/EnumerableExtensions.cs @@ -39,7 +39,6 @@ public static void AddRange(this List items, IEnu } } #endif - [DebuggerStepThrough] public static T First(this IList items) => items[0]; @@ -221,12 +220,12 @@ private static Expression Chain( itemValueFactory.Invoke); } - public static void CopyTo(this IList sourceList, List targetList, int startIndex = 0) + public static void CopyTo(this IList sourceList, List targetList) => targetList.AddRange(sourceList); public static void CopyFrom(this IList targetList, IList sourceList, int startIndex = 0) { - for (var i = 0; i < sourceList.Count; i++) + for (var i = 0; i < sourceList.Count && i < targetList.Count; i++) { targetList[i + startIndex] = sourceList[i]; } @@ -270,16 +269,16 @@ public static T[] Append(this IList array, T extraItem) } } - public static T[] Append(this IList array, IList extraItems) + public static IList Append(this IList array, IList extraItems) { if (extraItems.Count == 0) { - return array.CopyToArray(); + return array; } if (array.Count == 0) { - return extraItems.CopyToArray(); + return extraItems; } if (extraItems.Count == 1) diff --git a/AgileMapper/Extensions/Internal/StringExpressionExtensions.cs b/AgileMapper/Extensions/Internal/StringExpressionExtensions.cs index d1d22f324..09e9b767a 100644 --- a/AgileMapper/Extensions/Internal/StringExpressionExtensions.cs +++ b/AgileMapper/Extensions/Internal/StringExpressionExtensions.cs @@ -13,7 +13,7 @@ internal static class StringExpressionExtensions { - public static readonly Expression EmptyString = Expression.Field(null, typeof(string), "Empty"); + public static readonly Expression EmptyString = Expression.Field(null, typeof(string), nameof(string.Empty)); public static readonly Expression Underscore = "_".ToConstantExpression(); private static readonly MethodInfo _stringJoinMethod; @@ -23,7 +23,7 @@ static StringExpressionExtensions() { var stringMethods = typeof(string) .GetPublicStaticMethods() - .Filter(m => m.Name == "Join" || m.Name == "Concat") + .Filter(m => m.Name == nameof(string.Join) || m.Name == nameof(string.Concat)) .Project(m => new { Method = m, @@ -33,18 +33,30 @@ static StringExpressionExtensions() .ToArray(); _stringJoinMethod = stringMethods.First(m => - (m.Method.Name == "Join") && + (m.Method.Name == nameof(string.Join)) && (m.Parameters.Length == 2) && (m.Parameters[0].ParameterType == typeof(string)) && (m.Parameters[1].ParameterType == typeof(string[]))).Method; _stringConcatMethods = stringMethods - .Filter(m => (m.Method.Name == "Concat") && (m.FirstParameterType == typeof(string))) + .Filter(m => (m.Method.Name == nameof(string.Concat)) && (m.FirstParameterType == typeof(string))) .OrderBy(m => m.Parameters.Length) .Project(m => m.Method) .ToArray(); } + public static Expression GetIsNullOrWhiteSpaceCall(Expression stringValue) + { + return Expression.Call( +#if NET35 + typeof(StringExtensions) +#else + typeof(string) +#endif + .GetPublicStaticMethod("IsNullOrWhiteSpace"), + stringValue); + } + public static MethodInfo GetConcatMethod(int parameterCount) => _stringConcatMethods.First(m => m.GetParameters().Length == parameterCount); @@ -52,7 +64,7 @@ public static Expression GetStringConcatCall(this IList expressions) { if (expressions.None()) { - return string.Empty.ToConstantExpression(); + return EmptyString; } if (expressions.HasOne()) diff --git a/AgileMapper/Members/ExpressionInfoFinder.cs b/AgileMapper/Members/ExpressionInfoFinder.cs index 796a96b0b..85387e83a 100644 --- a/AgileMapper/Members/ExpressionInfoFinder.cs +++ b/AgileMapper/Members/ExpressionInfoFinder.cs @@ -3,10 +3,6 @@ namespace AgileObjects.AgileMapper.Members using System; using System.Collections.Generic; using System.Linq; - using Extensions; - using Extensions.Internal; - using NetStandardPolyfills; - using ReadableExpressions.Extensions; #if NET35 using Microsoft.Scripting.Ast; using static Microsoft.Scripting.Ast.ExpressionType; @@ -14,6 +10,10 @@ namespace AgileObjects.AgileMapper.Members using System.Linq.Expressions; using static System.Linq.Expressions.ExpressionType; #endif + using Extensions; + using Extensions.Internal; + using NetStandardPolyfills; + using ReadableExpressions.Extensions; using static Member; internal class ExpressionInfoFinder diff --git a/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeMappingExpressionFactory.cs b/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeMappingExpressionFactory.cs index a11e5a2cf..a6cd8fbc2 100644 --- a/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeMappingExpressionFactory.cs +++ b/AgileMapper/ObjectPopulation/ComplexTypes/ComplexTypeMappingExpressionFactory.cs @@ -48,7 +48,7 @@ protected override bool TargetCannotBeMapped(IObjectMappingData mappingData, out var targetType = mappingData.MapperData.TargetType; - if (targetType.IsAbstract() && DerivedTypesExistForTarget(mappingData)) + if (targetType.IsAbstract() && DerivedTypesExistForTarget(mappingData.MapperData)) { return base.TargetCannotBeMapped(mappingData, out reason); } @@ -57,17 +57,16 @@ protected override bool TargetCannotBeMapped(IObjectMappingData mappingData, out return true; } - private static bool DerivedTypesExistForTarget(IObjectMappingData mappingData) + private static bool DerivedTypesExistForTarget(IMemberMapperData mapperData) { - var configuredImplementationTypePairs = mappingData - .MapperData + var configuredImplementationTypePairs = mapperData .MapperContext .UserConfigurations .DerivedTypes - .GetImplementationTypePairsFor(mappingData.MapperData, mappingData.MapperData.MapperContext); + .GetImplementationTypePairsFor(mapperData, mapperData.MapperContext); return configuredImplementationTypePairs.Any() || - mappingData.MapperData.GetDerivedTargetTypes().Any(); + mapperData.GetDerivedTargetTypes().Any(); } #region Short-Circuits diff --git a/AgileMapper/ObjectPopulation/ComplexTypes/MultiStatementPopulationExpressionFactory.cs b/AgileMapper/ObjectPopulation/ComplexTypes/MultiStatementPopulationExpressionFactory.cs index a9e8cdb3e..91830fe5a 100644 --- a/AgileMapper/ObjectPopulation/ComplexTypes/MultiStatementPopulationExpressionFactory.cs +++ b/AgileMapper/ObjectPopulation/ComplexTypes/MultiStatementPopulationExpressionFactory.cs @@ -1,13 +1,13 @@ namespace AgileObjects.AgileMapper.ObjectPopulation.ComplexTypes { using System.Collections.Generic; - using Members; - using Members.Population; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Members; + using Members.Population; using static CallbackPosition; internal class MultiStatementPopulationExpressionFactory : PopulationExpressionFactoryBase diff --git a/AgileMapper/ObjectPopulation/ComplexTypes/PopulationExpressionFactoryBase.cs b/AgileMapper/ObjectPopulation/ComplexTypes/PopulationExpressionFactoryBase.cs index 14eb6020c..60d57499e 100644 --- a/AgileMapper/ObjectPopulation/ComplexTypes/PopulationExpressionFactoryBase.cs +++ b/AgileMapper/ObjectPopulation/ComplexTypes/PopulationExpressionFactoryBase.cs @@ -18,7 +18,7 @@ internal abstract class PopulationExpressionFactoryBase public IEnumerable GetPopulation(MappingCreationContext context) { var mappingData = context.MappingData; - var mapperData = context.MapperData; + var mapperData = mappingData.MapperData; GetCreationCallbacks(context, out var preCreationCallback, out var postCreationCallback); diff --git a/AgileMapper/ObjectPopulation/DerivedComplexTypeMappingsFactory.cs b/AgileMapper/ObjectPopulation/DerivedComplexTypeMappingsFactory.cs index d3e5663a9..450fc6790 100644 --- a/AgileMapper/ObjectPopulation/DerivedComplexTypeMappingsFactory.cs +++ b/AgileMapper/ObjectPopulation/DerivedComplexTypeMappingsFactory.cs @@ -5,14 +5,17 @@ namespace AgileObjects.AgileMapper.ObjectPopulation using System.Linq; #if NET35 using Microsoft.Scripting.Ast; + using static Microsoft.Scripting.Ast.Expression; #else using System.Linq.Expressions; + using static System.Linq.Expressions.Expression; #endif using Configuration; using Extensions; using Extensions.Internal; using Members; using NetStandardPolyfills; + using static Constants; internal static class DerivedComplexTypeMappingsFactory { @@ -22,57 +25,69 @@ public static Expression CreateFor(IObjectMappingData declaredTypeMappingData) if (DoNotMapDerivedTypes(declaredTypeMapperData)) { - return Constants.EmptyExpression; + return EmptyExpression; } - var derivedSourceTypes = declaredTypeMapperData.RuleSet.Settings.CheckDerivedSourceTypes - ? declaredTypeMapperData.GetDerivedSourceTypes() - : Constants.EmptyTypeArray; + var derivedSourceTypes = GetDerivedSourceTypesIfNecessary(declaredTypeMapperData); + var hasDerivedSourceTypes = derivedSourceTypes.Any(); + var hasNoDerivedSourceTypes = !hasDerivedSourceTypes; + + var derivedTargetTypes = GetDerivedTargetTypesIfNecessary(declaredTypeMapperData); + var hasDerivedTargetTypes = derivedTargetTypes.Any(); - var derivedTargetTypes = GetDerivedTargetTypesIfNecessary(declaredTypeMappingData); var derivedTypePairs = GetTypePairsFor(declaredTypeMapperData, declaredTypeMapperData); + var hasDerivedTypePairs = derivedTypePairs.Any(); - if (derivedSourceTypes.None() && derivedTargetTypes.None() && derivedTypePairs.None()) + if (hasNoDerivedSourceTypes && !hasDerivedTargetTypes && !hasDerivedTypePairs) { - return Constants.EmptyExpression; + return EmptyExpression; } var derivedTypeMappingExpressions = new List(); - AddDeclaredSourceTypeMappings( - derivedTypePairs, - declaredTypeMappingData, - derivedTypeMappingExpressions, - out var declaredTypeHasUnconditionalTypePair); - - if (declaredTypeHasUnconditionalTypePair && derivedSourceTypes.None()) + if (hasDerivedTypePairs) { - return derivedTypeMappingExpressions.First(); + AddDeclaredSourceTypeMappings( + derivedTypePairs, + declaredTypeMappingData, + derivedTypeMappingExpressions, + out var declaredTypeHasUnconditionalTypePair); + + if (declaredTypeHasUnconditionalTypePair && hasNoDerivedSourceTypes) + { + return derivedTypeMappingExpressions.First(); + } } var typedObjectVariables = new List(); - AddDerivedSourceTypeMappings( - derivedSourceTypes, - declaredTypeMappingData, - typedObjectVariables, - derivedTypeMappingExpressions); + if (hasDerivedSourceTypes) + { + AddDerivedSourceTypeMappings( + derivedSourceTypes, + declaredTypeMappingData, + typedObjectVariables, + derivedTypeMappingExpressions); + } - AddDerivedTargetTypeMappings( - declaredTypeMappingData, - derivedTargetTypes, - derivedTypeMappingExpressions); + if (hasDerivedTargetTypes) + { + AddDerivedTargetTypeMappings( + declaredTypeMappingData, + derivedTargetTypes, + derivedTypeMappingExpressions); + } if (derivedTypeMappingExpressions.None()) { - return Constants.EmptyExpression; + return EmptyExpression; } return typedObjectVariables.Any() - ? Expression.Block(typedObjectVariables, derivedTypeMappingExpressions) + ? Block(typedObjectVariables, derivedTypeMappingExpressions) : derivedTypeMappingExpressions.HasOne() ? derivedTypeMappingExpressions.First() - : Expression.Block(derivedTypeMappingExpressions); + : Block(derivedTypeMappingExpressions); } private static bool DoNotMapDerivedTypes(IMemberMapperData mapperData) @@ -85,14 +100,18 @@ private static bool DoNotMapDerivedTypes(IMemberMapperData mapperData) return mapperData.HasSameSourceAsParent(); } - private static ICollection GetDerivedTargetTypesIfNecessary(IObjectMappingData mappingData) + private static ICollection GetDerivedSourceTypesIfNecessary(IMemberMapperData mapperData) { - if (mappingData.MapperData.TargetIsDefinitelyUnpopulated()) - { - return Constants.EmptyTypeArray; - } + return mapperData.RuleSet.Settings.CheckDerivedSourceTypes + ? mapperData.GetDerivedSourceTypes() + : EmptyTypeArray; + } - return mappingData.MapperData.GetDerivedTargetTypes(); + private static ICollection GetDerivedTargetTypesIfNecessary(IMemberMapperData mapperData) + { + return mapperData.TargetCouldBePopulated() + ? mapperData.GetDerivedTargetTypes() + : EmptyTypeArray; } private static void AddDeclaredSourceTypeMappings( @@ -122,23 +141,22 @@ private static void AddDeclaredSourceTypeMappings( if (sourceValueCondition != null) { - derivedTypeMapping = Expression.Condition( + derivedTypeMapping = Condition( sourceValueCondition, derivedTypeMapping, derivedTypeMapping.Type.ToDefaultExpression()); } - var returnMappingResult = Expression.Return(declaredTypeMapperData.ReturnLabelTarget, derivedTypeMapping); - - declaredTypeHasUnconditionalTypePair = (condition == null); + var returnMappingResult = Return(declaredTypeMapperData.ReturnLabelTarget, derivedTypeMapping); - if (declaredTypeHasUnconditionalTypePair) + if (condition == null) { + declaredTypeHasUnconditionalTypePair = true; derivedTypeMappingExpressions.Add(returnMappingResult); return; } - var ifConditionThenMap = Expression.IfThen(condition, returnMappingResult); + var ifConditionThenMap = IfThen(condition, returnMappingResult); derivedTypeMappingExpressions.Add(ifConditionThenMap); } @@ -157,7 +175,7 @@ private static Expression GetTypePairCondition(DerivedTypePair derivedTypePair, var pairCondition = derivedTypePair.GetConditionOrNull(declaredTypeMapperData); - return (condition != null) ? Expression.AndAlso(pairCondition, condition) : pairCondition; + return (condition != null) ? AndAlso(pairCondition, condition) : pairCondition; } private static Expression GetDerivedTypeSourceValue( @@ -200,16 +218,11 @@ private static Expression GetDerivedTypeSourceValue( } private static void AddDerivedSourceTypeMappings( - ICollection derivedSourceTypes, + IEnumerable derivedSourceTypes, IObjectMappingData declaredTypeMappingData, ICollection typedObjectVariables, IList derivedTypeMappingExpressions) { - if (derivedSourceTypes.None()) - { - return; - } - var declaredTypeMapperData = declaredTypeMappingData.MapperData; var insertionOffset = derivedTypeMappingExpressions.Count; @@ -245,28 +258,19 @@ private static void AddDerivedSourceTypeMappings( continue; } - var groupedTypePairs = derivedTypePairs - .GroupBy(tp => tp.DerivedTargetType) - .Project(group => new TypePairGroup(group)) - .OrderBy(tp => tp.DerivedTargetType, TypeComparer.MostToLeastDerived) - .ToArray(); - - var unconditionalDerivedTargetTypeMapping = groupedTypePairs - .Filter(tpg => tpg.TypePairs.None(tp => tp.HasConfiguredCondition)) - .Project(tpg => new - { - tpg.DerivedTargetType, - TypePairsCondition = GetTargetValidCheckOrNull(tpg.DerivedTargetType, declaredTypeMapperData) - }) - .FirstOrDefault(d => d.TypePairsCondition == null); - - if (unconditionalDerivedTargetTypeMapping != null) + var hasUnconditionalDerivedTargetTypeMapping = HasUnconditionalDerivedTargetTypeMapping( + derivedTypePairs, + declaredTypeMapperData, + out var unconditionalDerivedTargetType, + out var groupedTypePairs); + + if (hasUnconditionalDerivedTargetTypeMapping) { ifSourceVariableIsDerivedTypeThenMap = GetIfConditionThenMapExpression( declaredTypeMappingData, outerCondition, derivedSourceCheck.TypedVariable, - unconditionalDerivedTargetTypeMapping.DerivedTargetType); + unconditionalDerivedTargetType); derivedTypeMappingExpressions.Insert(ifSourceVariableIsDerivedTypeThenMap, insertionOffset); continue; @@ -283,6 +287,38 @@ private static void AddDerivedSourceTypeMappings( } } + private static bool HasUnconditionalDerivedTargetTypeMapping( + IEnumerable derivedTypePairs, + IMemberMapperData declaredTypeMapperData, + out Type unconditionalDerivedTargetType, + out TypePairGroup[] groupedTypePairs) + { + groupedTypePairs = derivedTypePairs + .GroupBy(tp => tp.DerivedTargetType) + .Project(group => new TypePairGroup(group)) + .OrderBy(tp => tp.DerivedTargetType, TypeComparer.MostToLeastDerived) + .ToArray(); + + var unconditionalTypePairs = groupedTypePairs + .Filter(tpg => tpg.TypePairs.None(tp => tp.HasConfiguredCondition)); + + foreach (var unconditionalTypePair in unconditionalTypePairs) + { + var typePairsCondition = GetTargetValidCheckOrNull( + unconditionalTypePair.DerivedTargetType, + declaredTypeMapperData); + + if (typePairsCondition == null) + { + unconditionalDerivedTargetType = unconditionalTypePair.DerivedTargetType; + return true; + } + } + + unconditionalDerivedTargetType = null; + return false; + } + private static void AddDerivedTargetTypeMappings( IObjectMappingData declaredTypeMappingData, IEnumerable derivedTargetTypes, @@ -315,12 +351,12 @@ private static Expression GetIfConditionThenMapExpression( { var returnMappingResult = GetReturnMappingResultExpression(mappingData, sourceValue, targetType); - if (returnMappingResult == Constants.EmptyExpression) + if (returnMappingResult == EmptyExpression) { - return Constants.EmptyExpression; + return EmptyExpression; } - var ifConditionThenMap = Expression.IfThen(condition, returnMappingResult); + var ifConditionThenMap = IfThen(condition, returnMappingResult); return ifConditionThenMap; } @@ -332,12 +368,12 @@ private static Expression GetReturnMappingResultExpression( { var mapping = DerivedMappingFactory.GetDerivedTypeMapping(mappingData, sourceValue, targetType); - if (mapping == Constants.EmptyExpression) + if (mapping == EmptyExpression) { - return Constants.EmptyExpression; + return EmptyExpression; } - var returnMappingResult = Expression.Return(mappingData.MapperData.ReturnLabelTarget, mapping); + var returnMappingResult = Return(mappingData.MapperData.ReturnLabelTarget, mapping); return returnMappingResult; } @@ -371,13 +407,12 @@ private static Expression GetMapFromConditionOrDefaultExpression( mappingExpressions.Add(mapToDeclaredTargetType); - var ifSourceVariableIsDerivedTypeThenMap = Expression - .IfThen(condition, Expression.Block(mappingExpressions)); + var ifSourceVariableIsDerivedTypeThenMap = IfThen(condition, Block(mappingExpressions)); return ifSourceVariableIsDerivedTypeThenMap; } - private static IList GetTypePairsFor( + private static ICollection GetTypePairsFor( Type derivedSourceType, Type targetType, IMemberMapperData mapperData) @@ -392,7 +427,7 @@ private static IList GetTypePairsFor( return GetTypePairsFor(pairTestMapperData, mapperData); } - private static IList GetTypePairsFor(IBasicMapperData pairTestMapperData, IMemberMapperData mapperData) + private static ICollection GetTypePairsFor(IBasicMapperData pairTestMapperData, IMemberMapperData mapperData) { var derivedTypePairs = mapperData.MapperContext.UserConfigurations .DerivedTypes @@ -411,7 +446,7 @@ private static Expression GetTypePairsCondition( var pairConditions = conditionalPairs.Chain( firstPair => firstPair.GetConditionOrNull(mapperData), - (conditionSoFar, pair) => Expression.OrElse( + (conditionSoFar, pair) => OrElse( conditionSoFar, pair.GetConditionOrNull(mapperData))); @@ -435,7 +470,7 @@ private static Expression AppendTargetValidCheckIfAppropriate( return condition; } - condition = Expression.AndAlso(condition, targetIsValid); + condition = AndAlso(condition, targetIsValid); return condition; } @@ -455,13 +490,13 @@ private static Expression GetTargetValidCheckOrNull(Type targetType, IMemberMapp } var targetIsNull = mapperData.TargetObject.GetIsDefaultComparison(); - var targetIsValid = Expression.OrElse(targetIsNull, targetIsOfDerivedType); + var targetIsValid = OrElse(targetIsNull, targetIsOfDerivedType); return targetIsValid; } private static Expression GetTargetIsDerivedTypeCheck(Type targetType, IMemberMapperData mapperData) - => Expression.TypeIs(mapperData.TargetObject, targetType); + => TypeIs(mapperData.TargetObject, targetType); private static void Insert(this IList mappingExpressions, Expression mapping, int insertionOffset) { diff --git a/AgileMapper/ObjectPopulation/MappingCreationContext.cs b/AgileMapper/ObjectPopulation/MappingCreationContext.cs index 43a388755..0e943913b 100644 --- a/AgileMapper/ObjectPopulation/MappingCreationContext.cs +++ b/AgileMapper/ObjectPopulation/MappingCreationContext.cs @@ -17,7 +17,7 @@ namespace AgileObjects.AgileMapper.ObjectPopulation internal class MappingCreationContext { private bool _mapperDataHasRootEnumerableVariables; - private List _memberMappingExpressions; + private IList _memberMappingExpressions; public MappingCreationContext(IObjectMappingData mappingData) { @@ -62,11 +62,11 @@ private static Expression GetMapToNullConditionOrNull(IMemberMapperData mapperDa public bool InstantiateLocalVariable { get; set; } - public List GetMemberMappingExpressions() + public IList GetMemberMappingExpressions() { if (_memberMappingExpressions?.Count == MappingExpressions.Count) { - return _memberMappingExpressions ?? new List(0); + return _memberMappingExpressions ?? Enumerable.EmptyArray; } return _memberMappingExpressions = MappingExpressions.Filter(IsMemberMapping).ToList(); diff --git a/AgileMapper/TypeConversion/ToBoolConverter.cs b/AgileMapper/TypeConversion/ToBoolConverter.cs index 406fa4a83..32da2d8cd 100644 --- a/AgileMapper/TypeConversion/ToBoolConverter.cs +++ b/AgileMapper/TypeConversion/ToBoolConverter.cs @@ -4,13 +4,13 @@ namespace AgileObjects.AgileMapper.TypeConversion using System.Collections.Generic; using System.Globalization; using System.Linq; - using Extensions.Internal; - using ReadableExpressions.Extensions; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Extensions.Internal; + using ReadableExpressions.Extensions; internal struct ToBoolConverter : IValueConverter { diff --git a/AgileMapper/TypeConversion/ToCharacterConverter.cs b/AgileMapper/TypeConversion/ToCharacterConverter.cs index 25a56f2ae..d147ac657 100644 --- a/AgileMapper/TypeConversion/ToCharacterConverter.cs +++ b/AgileMapper/TypeConversion/ToCharacterConverter.cs @@ -1,15 +1,14 @@ namespace AgileObjects.AgileMapper.TypeConversion { using System; - using System.Linq; - using Extensions.Internal; - using NetStandardPolyfills; - using ReadableExpressions.Extensions; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Extensions.Internal; + using NetStandardPolyfills; + using ReadableExpressions.Extensions; internal struct ToCharacterConverter : IValueConverter { diff --git a/AgileMapper/TypeConversion/ToEnumConverter.cs b/AgileMapper/TypeConversion/ToEnumConverter.cs index 4ae1e743f..08d1de9ce 100644 --- a/AgileMapper/TypeConversion/ToEnumConverter.cs +++ b/AgileMapper/TypeConversion/ToEnumConverter.cs @@ -4,16 +4,16 @@ using System.Collections; using System.Collections.Generic; using System.Linq; - using Configuration; - using Extensions; - using Extensions.Internal; - using NetStandardPolyfills; - using ReadableExpressions.Extensions; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Configuration; + using Extensions; + using Extensions.Internal; + using NetStandardPolyfills; + using ReadableExpressions.Extensions; internal class ToEnumConverter : IValueConverter { @@ -495,17 +495,8 @@ private static Expression GetStringValueConversion( numericConversion, nameMatchingConversion); - var valueIsNullOrEmpty = Expression.Call( -#if NET35 - typeof(StringExtensions) -#else - typeof(string) -#endif - .GetPublicStaticMethod("IsNullOrWhiteSpace"), - sourceValue); - var convertedValueOrDefault = Expression.Condition( - valueIsNullOrEmpty, + StringExpressionExtensions.GetIsNullOrWhiteSpaceCall(sourceValue), fallbackValue, numericOrNameConversion); diff --git a/AgileMapper/TypeConversion/ToFormattedStringConverter.cs b/AgileMapper/TypeConversion/ToFormattedStringConverter.cs index ab9f2fbab..58ec61490 100644 --- a/AgileMapper/TypeConversion/ToFormattedStringConverter.cs +++ b/AgileMapper/TypeConversion/ToFormattedStringConverter.cs @@ -2,14 +2,14 @@ { using System; using System.Reflection; - using Configuration; - using Extensions.Internal; - using ReadableExpressions.Extensions; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Configuration; + using Extensions.Internal; + using ReadableExpressions.Extensions; internal class ToFormattedStringConverter : IValueConverter { @@ -28,7 +28,7 @@ public ToFormattedStringConverter(Type sourceValueType, string formattingString) } _sourceValueType = sourceValueType; - _formattingString = formattingString.ToConstantExpression(typeof(string)); + _formattingString = formattingString.ToConstantExpression(); } public bool CanConvert(Type nonNullableSourceType, Type nonNullableTargetType) diff --git a/AgileMapper/TypeConversion/ToNumericConverter.cs b/AgileMapper/TypeConversion/ToNumericConverter.cs index 785a27b80..6f20a9e4b 100644 --- a/AgileMapper/TypeConversion/ToNumericConverter.cs +++ b/AgileMapper/TypeConversion/ToNumericConverter.cs @@ -2,14 +2,14 @@ { using System; using System.Linq; - using Extensions.Internal; - using NetStandardPolyfills; - using ReadableExpressions.Extensions; #if NET35 using Microsoft.Scripting.Ast; #else using System.Linq.Expressions; #endif + using Extensions.Internal; + using NetStandardPolyfills; + using ReadableExpressions.Extensions; internal class ToNumericConverter : TryParseConverter {