├── .editorconfig ├── .gitattributes ├── .gitignore ├── LICENSE.md ├── README.md ├── UltraMapper.Benchmarks ├── Program.cs └── UltraMapper.Benchmarks.csproj ├── UltraMapper.Tests ├── ArrayTest.cs ├── BuiltInTypeTests.cs ├── CircularReferenceType.cs ├── CollectionTests.cs ├── CollectionUpdateTests.cs ├── ConfigurationInheritance │ └── ConfigurationInheritanceTests.cs ├── DateTimeTests.cs ├── DictionaryTests.cs ├── DirectMappings.cs ├── EnumTests.cs ├── GetterExpressionBuildingTests.cs ├── InheritTypeMappingTests.cs ├── InstanceFactoryTests.cs ├── InterfacesTests.cs ├── MappingConventions │ ├── ComplexProjectionConventionTests.cs │ ├── MappingConventionTests.cs │ ├── MatchingRuleTests.cs │ ├── NamingConventions.cs │ ├── ProjectionNestedMethodCalls.cs │ └── SimpleProjectionConventionTests.cs ├── MemberExtractionTests.cs ├── ObjectReferenceTests.cs ├── OtherTests.cs ├── ProjectionTests.cs ├── ReadOnlyCollectionTests.cs ├── RealworldBugs │ ├── RealworldBug1.cs │ └── RealworldBug2.cs ├── Records.cs ├── ReferenceTypeTests.cs ├── StringSplitterTests.cs ├── StructTests.cs ├── TypeMapperTestExtensions.cs ├── UltraMapper.Tests.csproj ├── UltraMapper.Tests.csproj.old ├── UnwritableTargetMembers.cs ├── ValuesAccessTests.cs └── packages.config ├── UltraMapper.sln ├── UltraMapper ├── AssemblyOptions.cs ├── CodeGeneration │ ├── ExpressionLoops.cs │ ├── ExpressionParameterReplacer.cs │ ├── GetterSetterExpressionBuilders.cs │ ├── MappingExpressionBuilders │ │ ├── AbstractMappingExpressionBuilder.cs │ │ ├── BaseMappingExpressionBuilder.cs │ │ ├── Contexts │ │ │ ├── CollectionMapperContext.cs │ │ │ ├── CollectionMapperViaTempCollectionContext.cs │ │ │ ├── CustomConverterContext.cs │ │ │ ├── DictionaryMapperContext.cs │ │ │ ├── MapperContext.cs │ │ │ ├── MemberMappingContext.cs │ │ │ └── ReferenceMapperContext.cs │ │ ├── CustomConverterExpressionBuilder.cs │ │ ├── IMappingExpressionBuilder.cs │ │ ├── PrimitiveTypes │ │ │ ├── BuiltInTypesMapper.cs │ │ │ ├── ConvertMapper.cs │ │ │ ├── EnumMapper.cs │ │ │ ├── NullableMapper.cs │ │ │ ├── PrimitiveMapperBase.cs │ │ │ ├── StringToEnumMapper.cs │ │ │ └── StructMapper.cs │ │ └── ReferenceTypes │ │ │ ├── CollectionTypes │ │ │ ├── CollectionMapper.cs │ │ │ ├── CollectionMapperViaTempCollection.cs │ │ │ ├── CollectionToArrayMapper.cs │ │ │ ├── DictionaryMapper.cs │ │ │ ├── EnumerableIteratorToArrayMapper.cs │ │ │ ├── LinkedListMapper.cs │ │ │ ├── QueueMapper.cs │ │ │ ├── ReadOnlyCollectionMapper.cs │ │ │ ├── SortedSetMapper.cs │ │ │ └── StackMapper.cs │ │ │ ├── MemberMapper.cs │ │ │ ├── ReferenceMapper.cs │ │ │ └── ReferenceToStructMapper.cs │ ├── MemberAccessPathExtensions.cs │ └── ReferenceTrackingExpression.cs ├── Configuration │ ├── BuiltInConversionConverters.cs │ ├── Conventions │ │ ├── ConventionResolvers │ │ │ ├── DefaultConventionResolver.cs │ │ │ └── IConventionResolver.cs │ │ └── MappingConventions │ │ │ ├── DefaultConvention.cs │ │ │ ├── IMappingConvention.cs │ │ │ ├── MatchingRuleEvaluators │ │ │ ├── DefaultMatchingRuleEvaluator.cs │ │ │ └── IMatchingRulesEvaluator.cs │ │ │ ├── MatchingRules │ │ │ ├── IMatchingRule.cs │ │ │ ├── NameMatching │ │ │ │ ├── ExactNameMatching.cs │ │ │ │ ├── INameMatchingRule.cs │ │ │ │ ├── MethodNameMatching.cs │ │ │ │ ├── PrefixMatching.cs │ │ │ │ └── SuffixMatching.cs │ │ │ └── TypeMatching │ │ │ │ ├── ITypeMatchingRule.cs │ │ │ │ ├── MethodTypeMatching.cs │ │ │ │ └── TypeMatching.cs │ │ │ ├── MemberProviders │ │ │ ├── IMemberProvider.cs │ │ │ ├── SourceMemberProviders │ │ │ │ ├── ISourceMemberProvider.cs │ │ │ │ └── SourceMemberProvider.cs │ │ │ └── TargetMemberProviders │ │ │ │ ├── ITargetMemberProvider.cs │ │ │ │ └── TargetMemberProvider.cs │ │ │ ├── ProjectionConvention.cs │ │ │ └── StringSplitting │ │ │ ├── IStringSplitter.cs │ │ │ ├── SplittingRules │ │ │ ├── IStringSplittingRule.cs │ │ │ ├── RelayStringSplittingRule.cs │ │ │ └── StringSplittingRules.cs │ │ │ └── StringSplitter.cs │ ├── GeneratedExpressionCache.cs │ ├── GlobalConfiguration.cs │ ├── Inheritance │ │ ├── ConfigInheritanceNode.cs │ │ └── ConfigInheritanceTree.cs │ ├── Mapping │ │ ├── IMapping.cs │ │ ├── Mapping.cs │ │ ├── MappingPoints │ │ │ ├── IMappingPoint.cs │ │ │ ├── MappingPoint.cs │ │ │ ├── MappingSource │ │ │ │ ├── IMappingSource.cs │ │ │ │ └── MappingSource.cs │ │ │ └── MappingTarget │ │ │ │ ├── IMappingTarget.cs │ │ │ │ └── MappingTarget.cs │ │ ├── MemberMapping.cs │ │ └── TypeMapping.cs │ ├── MappingResolution.cs │ ├── MemberConfigurator.cs │ ├── Options │ │ ├── CollectionBehaviors.cs │ │ ├── IMappingOptions.cs │ │ ├── IMemberMappingOptions.cs │ │ ├── ITypeMappingOptions.cs │ │ ├── MappingOptionsComparer.cs │ │ └── ReferenceBehaviors.cs │ └── TypeConfigurator.cs ├── Internals │ ├── CompilerServices.cs │ ├── CustomDelegates.cs │ ├── ExtensionMethods │ │ ├── DictionaryExtensions.cs │ │ ├── ExpressionExtensions.cs │ │ ├── LinqExtensions.cs │ │ ├── MemberInfoExtensions.cs │ │ ├── MethodInfoExtensions.cs │ │ └── TypeExtensions.cs │ ├── InstanceFactory.cs │ ├── MemberAccessPath.cs │ ├── MemberPair.cs │ ├── SpecializedCollections │ │ ├── OrderedTypeSet.cs │ │ └── TypeSet.cs │ └── TypePair.cs ├── Note.txt ├── ReferenceTracking │ └── ReferenceTracker.cs ├── UltraMapper.cs ├── UltraMapper.csproj ├── UltraMapper.nuspec └── UltraMapperExtensionMethods.cs ├── _config.yml ├── appveyor.yml ├── graph0.PNG └── graph1.PNG /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | 143 | # TODO: Un-comment the next line if you do not want to checkin 144 | # your web deploy settings because they may include unencrypted 145 | # passwords 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | # Microsoft Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Microsoft Azure Emulator 166 | ecf/ 167 | rcf/ 168 | 169 | # Windows Store app package directory 170 | AppPackages/ 171 | BundleArtifacts/ 172 | 173 | # Visual Studio cache files 174 | # files ending in .cache can be ignored 175 | *.[Cc]ache 176 | # but keep track of directories ending in .cache 177 | !*.[Cc]ache/ 178 | 179 | # Others 180 | ClientBin/ 181 | [Ss]tyle[Cc]op.* 182 | ~$* 183 | *~ 184 | *.dbmdl 185 | *.dbproj.schemaview 186 | *.pfx 187 | *.publishsettings 188 | node_modules/ 189 | orleans.codegen.cs 190 | 191 | # RIA/Silverlight projects 192 | Generated_Code/ 193 | 194 | # Backup & report files from converting an old project file 195 | # to a newer Visual Studio version. Backup files are not needed, 196 | # because we have git ;-) 197 | _UpgradeReport_Files/ 198 | Backup*/ 199 | UpgradeLog*.XML 200 | UpgradeLog*.htm 201 | 202 | # SQL Server files 203 | *.mdf 204 | *.ldf 205 | 206 | # Business Intelligence projects 207 | *.rdl.data 208 | *.bim.layout 209 | *.bim_*.settings 210 | 211 | # Microsoft Fakes 212 | FakesAssemblies/ 213 | 214 | # GhostDoc plugin setting file 215 | *.GhostDoc.xml 216 | 217 | # Node.js Tools for Visual Studio 218 | .ntvs_analysis.dat 219 | 220 | # Visual Studio 6 build log 221 | *.plg 222 | 223 | # Visual Studio 6 workspace options file 224 | *.opt 225 | 226 | # Visual Studio LightSwitch build output 227 | **/*.HTMLClient/GeneratedArtifacts 228 | **/*.DesktopClient/GeneratedArtifacts 229 | **/*.DesktopClient/ModelManifest.xml 230 | **/*.Server/GeneratedArtifacts 231 | **/*.Server/ModelManifest.xml 232 | _Pvt_Extensions 233 | 234 | # LightSwitch generated files 235 | GeneratedArtifacts/ 236 | ModelManifest.xml 237 | 238 | # Paket dependency manager 239 | .paket/paket.exe 240 | 241 | # FAKE - F# Make 242 | .fake/ 243 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [Please take a minute to support this project with a small donation!](https://www.paypal.com/donate/?hosted_button_id=MC59U7TDE3KCQ) 2 | [![Paypal](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.paypal.com/donate/?hosted_button_id=MC59U7TDE3KCQ) 3 | 4 | 5 | # UltraMapper 6 | [![Build status](https://ci.appveyor.com/api/projects/status/github/maurosampietro/UltraMapper?svg=true)](https://ci.appveyor.com/project/maurosampietro/ultramapper/branch/master) 7 | [![NuGet](http://img.shields.io/nuget/v/UltraMapper.svg)](https://www.nuget.org/packages/UltraMapper/) 8 | 9 | 10 | A nicely coded object-mapper for .NET 11 | 12 | 13 | 14 | What is UltraMapper? 15 | -------------------------------- 16 | 17 | UltraMapper is a .NET mapper, that is, a tool that avoids you the need to write the code needed to copy values from a source object to a target object. It avoids you the need to manually write the (boring and hard-to-maintain) code that reads the value from the source and instantiate/assign the relative member on the target object. 18 | 19 | It can be used to get deep copies of an object or map an object to another type. 20 | 21 | Consider this simple class: 22 | 23 | ````c# 24 | public class Person 25 | { 26 | public DateTime Birthday { get; set; } 27 | public string FirstName { get; set; } 28 | public string LastName { get; set; } 29 | public string EmailAddress { get; set; } 30 | } 31 | ```` 32 | 33 | If you wanted a copy of an instance of the above class your should write something like this: 34 | 35 | ````c# 36 | var clone = new Person(); 37 | clone.Birthday = person.Birthday 38 | clone.FirstName = person.FirstName 39 | clone.LastName = person.LastName 40 | clone.EmailAddress = person.EmailAddress 41 | ```` 42 | 43 | What if you had hundreds of simple objects like the one above to copy? What if the object was more complex, contained references to other complex objects or collections of other complex objects? 44 | 45 | Would you still map it manually!? 46 | With UltraMapper you can solve this problem efficiently like this: 47 | 48 | ````c# 49 | Mapper ultraMapper = new Mapper(); 50 | Person clone = ultraMapper.Map( person ); 51 | ```` 52 | 53 | Getting started 54 | -------------------------------- 55 | 56 | Check out the [wiki](https://github.com/maurosampietro/UltraMapper/wiki/Getting-started) for more information and advanced scenarios 57 | 58 | Why should I use UltraMapper instead of well-known alternatives? 59 | -------------------------------- 60 | 61 | The answer is ReferenceTracking, Reliability, Performance and Maintainability. 62 | 63 | The ReferenceTracking mechanism of UltraMapper guarantees that the cloned or mapped object **preserve the same reference structure of the source object**: if an instance is referenced twice in the source object, we will create only one new instance for the target, and assign it twice. 64 | 65 | This is something theorically simple but crucial, yet **uncommon among mappers**; in facts other mappers tipically will create new instances on the target even if the same instance is being referenced twice in the source. 66 | 67 | With UltraMapper, any reference object is cached and before creating any new reference a cache lookup is performed to check if that instance has already been mapped. If the reference has already been mapped, the mapped instance is used. 68 | 69 | This technique allows self-references anywhere down the hierarchical tree of the objects involved in the mapping process, avoids StackOverflows and **guarantees that the target object is actually a deep copy or a mapped version of the source and not just a similar object with identical values.** 70 | 71 | ReferenceTracking mechanism is so important that cannot be disabled and offers a huge performance boost in real-world scenarios. 72 | 73 | UltraMapper is just ~1100 lines of code and generates and compiles minimal mapping expressions. 74 | MappingExpressionBuilders are very well structured in a simple object-oriented way. 75 | 76 | 77 | Key features 78 | -------------------------------- 79 | 80 | Implemented features: 81 | 82 | - Powerful reference tracking mechanism 83 | - Powerful type-to-type, type-to-member and member-to-member configuration override mechanism, with configuration inheritance 84 | - Supports self-references and circular references, anywhere down the object hierarchy 85 | - Supports object inheritance 86 | - Supports abstract classes 87 | - Supports interfaces 88 | - Supports mapping by convention 89 | - Supports flattening/projections by convention 90 | - Supports manual flattening/unflattening/projections. 91 | - Supports arrays 92 | - Supports collections (Dictionary, HashSet, List, LinkedList, ObservableCollection, SortedSet, Stack, Queue) 93 | - Supports collection merging/updating 94 | 95 | Moreover UltraMapper is: 96 | - very fast in any scenario (faster than any other .NET mapper i tried). See the [benchmarks](https://github.com/maurosampietro/UltraMapper/wiki/Benchmarks). 97 | - developer-friendly (should be easy to contribute, extend and maintain) 98 | 99 | **ANY FEEDBACK IS WELCOME** 100 | 101 | -------------------------------------------------------------------------------- /UltraMapper.Benchmarks/UltraMapper.Benchmarks.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net50;net60 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /UltraMapper.Tests/CircularReferenceType.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace UltraMapper.Tests 4 | { 5 | [TestClass] 6 | public class CircularReferenceType 7 | { 8 | public class OuterType 9 | { 10 | public class InnerType 11 | { 12 | public string A { get; set; } 13 | public string B { get; set; } 14 | public InnerType Inner { get; set; } 15 | } 16 | 17 | public InnerType Move { get; set; } 18 | } 19 | 20 | [TestMethod] 21 | public void NestingAndReferringTheSameObject() 22 | { 23 | var obj = new OuterType.InnerType 24 | { 25 | A = "a", 26 | B = "b" 27 | }; 28 | 29 | obj.Inner = obj; 30 | 31 | var source = new OuterType() 32 | { 33 | Move = obj 34 | }; 35 | 36 | var ultraMapper = new Mapper( cfg => cfg.IsReferenceTrackingEnabled = true ); 37 | var target = ultraMapper.Map( source ); 38 | 39 | bool isResultOk = ultraMapper.VerifyMapperResult( source, target ); 40 | Assert.IsTrue( isResultOk ); 41 | } 42 | 43 | [TestMethod] 44 | public void NestingAndReferringTheSameType() 45 | { 46 | var source = new OuterType() 47 | { 48 | Move = new OuterType.InnerType 49 | { 50 | A = "a", 51 | B = "b", 52 | Inner = new OuterType.InnerType() 53 | { 54 | A = "c", 55 | B = "b" 56 | } 57 | } 58 | }; 59 | 60 | var ultraMapper = new Mapper(); 61 | var target = ultraMapper.Map( source ); 62 | 63 | bool isResultOk = ultraMapper.VerifyMapperResult( source, target ); 64 | Assert.IsTrue( isResultOk ); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /UltraMapper.Tests/ConfigurationInheritance/ConfigurationInheritanceTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using UltraMapper.Config; 5 | using UltraMapper.Internals; 6 | 7 | namespace UltraMapper.Tests.ConfigurationInheritance 8 | { 9 | [TestClass] 10 | public class ConfigurationInheritanceTests 11 | { 12 | [TestMethod] 13 | public void SimpleTest() 14 | { 15 | // we set as root a typePair different from (object,object) 16 | var falseRoot = new TypeMapping( null, typeof( object ), typeof( string ) ); 17 | var tree = new ConfigInheritanceTree( falseRoot ); 18 | 19 | //we add the real root (object,object) 20 | tree.Add( new TypeMapping( null, typeof( object ), typeof( object ) ) ); 21 | 22 | tree.Add( new TypeMapping( null, typeof( Dictionary ), typeof( Dictionary ) ) ); 23 | tree.Add( new TypeMapping( null, typeof( ObservableCollection ), typeof( IList ) ) ); 24 | tree.Add( new TypeMapping( null, typeof( IList ), typeof( IList ) ) ); 25 | tree.Add( new TypeMapping( null, typeof( ICollection ), typeof( ICollection ) ) ); 26 | tree.Add( new TypeMapping( null, typeof( Collection ), typeof( Collection ) ) ); 27 | tree.Add( new TypeMapping( null, typeof( List ), typeof( List ) ) ); 28 | tree.Add( new TypeMapping( null, typeof( IEnumerable ), typeof( IEnumerable ) ) ); 29 | tree.Add( new TypeMapping( null, typeof( IEnumerable ), typeof( IEnumerable ) ) ); 30 | tree.Add( new TypeMapping( null, typeof( IEnumerable> ), typeof( IEnumerable> ) ) ); 31 | tree.Add( new TypeMapping( null, typeof( object ), typeof( string ) ) ); 32 | tree.Add( new TypeMapping( null, typeof( string ), typeof( string ) ) ); 33 | 34 | //check root is been updated 35 | Assert.IsTrue( tree.Root.Item.Source.EntryType == typeof( object ) ); 36 | Assert.IsTrue( tree.Root.Item.Target.EntryType == typeof( object ) ); 37 | 38 | //no duplicates allowed 39 | var dup1 = new TypeMapping( null, typeof( object ), typeof( object ) ); 40 | var dup2 = new TypeMapping( null, typeof( IEnumerable> ), typeof( IEnumerable> ) ); 41 | var dup3 = new TypeMapping( null, typeof( object ), typeof( string ) ); 42 | 43 | tree.Add( dup1 ); 44 | tree.Add( dup2 ); 45 | tree.Add( dup3 ); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /UltraMapper.Tests/DateTimeTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace UltraMapper.Tests 9 | { 10 | [TestClass] 11 | public partial class DateTimeTests 12 | { 13 | private class Source 14 | { 15 | public DateTime Date { get; set; } 16 | public string DateString { get; set; } 17 | } 18 | 19 | private class Target 20 | { 21 | public DateTime Date { get; set; } 22 | public string DateString { get; set; } 23 | } 24 | 25 | [TestMethod] 26 | public void DateTimeAndString() 27 | { 28 | var source = new Source() 29 | { 30 | Date = new DateTime( 2000, 12, 31 ), 31 | DateString = new DateTime( 2000, 1, 1 ).ToString( "yyyyMMdd" ) 32 | }; 33 | 34 | var target = new Target(); 35 | 36 | var ultraMapper = new Mapper( cfg => 37 | { 38 | cfg.MapTypes() 39 | .MapMember( s => s.Date, t => t.DateString ) 40 | .MapMember( s => s.DateString, t => t.Date ); 41 | 42 | cfg.MapTypes( s => DateTime.ParseExact( s, "yyyyMMdd", cfg.Culture ) ); 43 | } ); 44 | 45 | ultraMapper.Map( source, target ); 46 | 47 | Assert.IsTrue( source.Date.ToString( ultraMapper.Config.Culture ) == target.DateString ); 48 | Assert.IsTrue( DateTime.ParseExact( source.DateString, "yyyyMMdd", ultraMapper.Config.Culture ) == target.Date ); 49 | } 50 | } 51 | 52 | [TestClass] 53 | public class DateTimeTests2 54 | { 55 | private class Source1 56 | { 57 | public DateTime Date1 { get; set; } 58 | public DateTime Date2 { get; set; } 59 | public DateTime Date3 { get; set; } 60 | } 61 | 62 | private class Target 63 | { 64 | public string LongDateString { get; set; } 65 | public string ShortDateString { get; set; } 66 | public string DefaultFormat { get; set; } 67 | } 68 | 69 | private class Source2 70 | { 71 | public DateTime Date1 { get; set; } 72 | public DateTime Date2 { get; set; } 73 | public DateTime Date3 { get; set; } 74 | } 75 | 76 | [TestMethod] 77 | public void DifferentFormatsAndConfigInheritance() 78 | { 79 | var source1 = new Source1() 80 | { 81 | Date1 = new DateTime( 2000, 12, 31 ), 82 | Date2 = new DateTime( 2001, 12, 31 ), 83 | Date3 = new DateTime( 2002, 12, 31 ) 84 | }; 85 | 86 | var source2 = new Source2() 87 | { 88 | Date1 = new DateTime( 2002, 12, 31 ), 89 | Date2 = new DateTime( 2003, 12, 31 ), 90 | Date3 = new DateTime( 2002, 12, 31 ) 91 | }; 92 | 93 | var target = new Target(); 94 | 95 | var ultraMapper = new Mapper( cfg => 96 | { 97 | cfg.MapTypes( s => "default format" ); 98 | 99 | cfg.MapTypes() 100 | .MapTypeToMember( t => t.LongDateString, s => s.ToLongDateString() ) 101 | .MapTypeToMember( t => t.ShortDateString, s => s.ToShortDateString() ) 102 | .MapMember( s => s.Date1, t => t.LongDateString ) 103 | .MapMember( s => s.Date2, t => t.ShortDateString ) 104 | .MapMember( s => s.Date3, t => t.DefaultFormat ); 105 | 106 | cfg.MapTypes() 107 | .MapTypeToMember( t => t.LongDateString, s => "long format" ) 108 | .MapMember( s => s.Date1, t => t.LongDateString ) 109 | .MapMember( s => s.Date2, t => t.ShortDateString, s => "short format" ) 110 | .MapMember( s => s.Date3, t => t.DefaultFormat, s => "default format override" ); 111 | } ); 112 | 113 | ultraMapper.Map( source1, target ); 114 | 115 | Assert.IsTrue( source1.Date1.ToLongDateString() == target.LongDateString ); 116 | Assert.IsTrue( source1.Date2.ToShortDateString() == target.ShortDateString ); 117 | Assert.IsTrue( target.DefaultFormat == "default format" ); 118 | 119 | ultraMapper.Map( source2, target ); 120 | 121 | Assert.IsTrue( target.LongDateString == "long format" ); 122 | Assert.IsTrue( target.ShortDateString == "short format" ); 123 | Assert.IsTrue( target.DefaultFormat == "default format override" ); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /UltraMapper.Tests/EnumTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace UltraMapper.Tests 4 | { 5 | [TestClass] 6 | public class EnumTests 7 | { 8 | public enum Types 9 | { 10 | Value1 = 1, 11 | Value2 = 2, 12 | Value3 = 3 13 | } 14 | 15 | [TestMethod] 16 | public void StringToEnum() 17 | { 18 | var ultraMapper = new Mapper(); 19 | 20 | string source = Types.Value1.ToString(); 21 | Types target = Types.Value3; 22 | 23 | ultraMapper.Map( source, out target ); 24 | Assert.IsTrue( target == Types.Value1 ); 25 | } 26 | 27 | [TestMethod] 28 | public void EnumToEnum() 29 | { 30 | var ultraMapper = new Mapper(); 31 | 32 | string source = Types.Value1.ToString(); 33 | Types target = Types.Value3; 34 | 35 | ultraMapper.Map( Types.Value1, out target ); 36 | Assert.IsTrue( target == Types.Value1 ); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /UltraMapper.Tests/GetterExpressionBuildingTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | using System.Linq.Expressions; 4 | using UltraMapper.Internals; 5 | 6 | namespace UltraMapper.Tests 7 | { 8 | [TestClass] 9 | public class GetterExpressionBuildingTests 10 | { 11 | private class FirstLevel 12 | { 13 | public string A { get; set; } 14 | public string A1 { get; set; } 15 | public string A2 { get; set; } 16 | 17 | public SecondLevel SecondLevel { get; set; } 18 | 19 | public SecondLevel GetSecond() { return SecondLevel; } 20 | } 21 | 22 | private class SecondLevel 23 | { 24 | public string A { get; set; } 25 | public string A1 { get; set; } 26 | public ThirdLevel ThirdLevel { get; set; } 27 | 28 | public ThirdLevel GetThird() { return this.ThirdLevel; } 29 | public ThirdLevel SetThird( ThirdLevel value ) { return this.ThirdLevel = value; } 30 | } 31 | 32 | private class ThirdLevel 33 | { 34 | public string A { get; set; } 35 | public string A1 { get; set; } 36 | 37 | public void SetA( string value ) 38 | { 39 | this.A = value; 40 | } 41 | } 42 | 43 | [TestMethod] 44 | public void BuildGetterWithNullChecks() 45 | { 46 | Expression> selector = t => t.SecondLevel.ThirdLevel.A; 47 | 48 | var accessPath = selector.GetMemberAccessPath(); 49 | var expression = accessPath.GetGetterExpWithNullChecks(); 50 | var functor = (Func)expression.Compile(); 51 | 52 | // LEVEL 1 53 | var source = new FirstLevel(); 54 | var result = functor( source ); 55 | 56 | Assert.IsTrue( result == source?.SecondLevel?.ThirdLevel?.A ); 57 | 58 | // LEVEL 2 59 | source = new FirstLevel() 60 | { 61 | SecondLevel = new SecondLevel() 62 | }; 63 | 64 | result = functor( source ); 65 | Assert.IsTrue( result == source?.SecondLevel?.ThirdLevel?.A ); 66 | 67 | // LEVEL 3 68 | source = new FirstLevel() 69 | { 70 | SecondLevel = new SecondLevel() 71 | { 72 | ThirdLevel = new ThirdLevel() 73 | } 74 | }; 75 | 76 | result = functor( source ); 77 | Assert.IsTrue( result == source?.SecondLevel?.ThirdLevel?.A ); 78 | 79 | // LEVEL 4 80 | source = new FirstLevel() 81 | { 82 | SecondLevel = new SecondLevel() 83 | { 84 | ThirdLevel = new ThirdLevel() 85 | { 86 | A = "Ok" 87 | } 88 | } 89 | }; 90 | 91 | result = functor( source ); 92 | Assert.IsTrue( result == source?.SecondLevel?.ThirdLevel?.A ); 93 | } 94 | 95 | public interface IParsedParam 96 | { 97 | string Name { get; set; } 98 | int Index { get; set; } 99 | } 100 | 101 | public class SimpleParam : IParsedParam 102 | { 103 | public string Name { get; set; } 104 | public int Index { get; set; } 105 | public string Value { get; set; } 106 | } 107 | 108 | [TestMethod] 109 | public void BuildSelectorWithCast() 110 | { 111 | Expression> selector = s => ((SimpleParam)s).Value; 112 | 113 | var accessPath = selector.GetMemberAccessPath(); 114 | var expression = accessPath.GetGetterExp(); 115 | var functor = (Func)expression.Compile(); 116 | 117 | Assert.IsNotNull( functor ); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /UltraMapper.Tests/MappingConventions/ComplexProjectionConventionTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using UltraMapper.Conventions; 3 | 4 | namespace UltraMapper.Tests 5 | { 6 | [TestClass] 7 | ///Flattening taking methods and other member into account 8 | public class ComplexProjectionConventionTests 9 | { 10 | private class OrderDto 11 | { 12 | public string CustomerName { get; set; } 13 | 14 | public string _productName; 15 | public void SetProductName( string productName ) 16 | { 17 | _productName = productName; 18 | } 19 | 20 | public string GetProductName() 21 | { 22 | return _productName; 23 | } 24 | } 25 | 26 | private class Order 27 | { 28 | public Customer Customer { get; set; } 29 | public Product Product { get; set; } 30 | } 31 | 32 | private class Product 33 | { 34 | public decimal Price { get; set; } 35 | public string Name { get; set; } 36 | } 37 | 38 | private class Customer 39 | { 40 | public string Name { get; set; } 41 | } 42 | 43 | [TestMethod] 44 | public void Flattening() 45 | { 46 | var customer = new Customer 47 | { 48 | Name = "George Costanza" 49 | }; 50 | 51 | var product = new Product 52 | { 53 | Name = "Bosco", 54 | Price = 4.99m 55 | }; 56 | 57 | var order = new Order 58 | { 59 | Customer = customer, 60 | Product = product 61 | }; 62 | 63 | var mapper = new Mapper( cfg => 64 | { 65 | cfg.Conventions.GetOrAdd(); 66 | } ); 67 | 68 | var dto = mapper.Map( order ); 69 | 70 | Assert.IsTrue( dto.CustomerName == customer.Name ); 71 | Assert.IsTrue( dto.GetProductName() == product.Name ); 72 | } 73 | 74 | [TestMethod] 75 | public void Unflattening() 76 | { 77 | var dto = new OrderDto() 78 | { 79 | CustomerName = "Johnny", 80 | }; 81 | 82 | dto.SetProductName( "Mobile phone" ); 83 | 84 | var mapper = new Mapper( cfg => 85 | { 86 | cfg.Conventions.GetOrAdd(); 87 | } ); 88 | 89 | //TODO: we need to create instances for nested objects!!! 90 | var order = mapper.Map( dto ); 91 | 92 | Assert.IsTrue( dto.CustomerName == order.Customer.Name ); 93 | Assert.IsTrue( dto.GetProductName() == order.Product.Name ); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /UltraMapper.Tests/MappingConventions/MatchingRuleTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using UltraMapper.Conventions; 3 | using UltraMapper.Internals; 4 | 5 | namespace UltraMapper.Tests 6 | { 7 | [TestClass] 8 | public class MatchingRuleTests 9 | { 10 | public class TestClass 11 | { 12 | public int Property { get; set; } 13 | } 14 | 15 | public class PrefixTestClass 16 | { 17 | public int DtoProperty { get; set; } 18 | } 19 | 20 | public class SuffixTestClass 21 | { 22 | public int PropertyDto { get; set; } 23 | } 24 | 25 | [TestMethod] 26 | public void PrefixMatching() 27 | { 28 | //Property -> DtoProperty 29 | var source2 = new TestClass() { Property = 11 }; 30 | var mapper2 = new Mapper( cfg => 31 | { 32 | cfg.Conventions.GetOrAdd( convention => 33 | { 34 | convention.MatchingRules.Clear(); 35 | convention.MatchingRules.GetOrAdd(); 36 | } ); 37 | } ); 38 | 39 | var result2 = mapper2.Map( source2 ); 40 | Assert.IsTrue( source2.Property == result2.DtoProperty ); 41 | 42 | //DtoProperty -> Property 43 | var source = new PrefixTestClass() { DtoProperty = 11 }; 44 | var mapper = new Mapper( cfg => 45 | { 46 | cfg.Conventions.GetOrAdd( convention => 47 | { 48 | convention.MatchingRules.Clear(); 49 | convention.MatchingRules.GetOrAdd(); 50 | } ); 51 | } ); 52 | 53 | var result = mapper.Map( source ); 54 | Assert.IsTrue( source.DtoProperty == result.Property ); 55 | } 56 | 57 | [TestMethod] 58 | public void SuffixMatching() 59 | { 60 | //Property -> DtoProperty 61 | var source2 = new TestClass() { Property = 11 }; 62 | var mapper2 = new Mapper( cfg => 63 | { 64 | cfg.Conventions.GetOrAdd( convention => 65 | { 66 | convention.MatchingRules.Clear(); 67 | convention.MatchingRules.GetOrAdd(); 68 | } ); 69 | } ); 70 | 71 | var result2 = mapper2.Map( source2 ); 72 | Assert.IsTrue( source2.Property == result2.PropertyDto ); 73 | 74 | //DtoProperty -> Property 75 | var source = new SuffixTestClass() { PropertyDto = 11 }; 76 | var mapper = new Mapper( cfg => 77 | { 78 | cfg.Conventions.GetOrAdd( convention => 79 | { 80 | convention.MatchingRules.Clear(); 81 | convention.MatchingRules.GetOrAdd(); 82 | } ); 83 | } ); 84 | 85 | var result = mapper.Map( source ); 86 | Assert.IsTrue( source.PropertyDto == result.Property ); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /UltraMapper.Tests/MappingConventions/NamingConventions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using UltraMapper.Conventions; 3 | 4 | namespace UltraMapper.Tests 5 | { 6 | [TestClass] 7 | public class ConventionTests 8 | { 9 | public class SourceClass 10 | { 11 | public int A { get; set; } = 31; 12 | public long B { get; set; } = 33; 13 | public int C { get; set; } = 71; 14 | public int D { get; set; } = 73; 15 | public int E { get; set; } = 101; 16 | } 17 | 18 | public class TargetClass 19 | { 20 | public long A { get; set; } 21 | public int B { get; set; } 22 | } 23 | 24 | public class TargetClassDto 25 | { 26 | public long ADto { get; set; } 27 | public int ADataTransferObject { get; set; } 28 | 29 | public long BDataTransferObject { get; set; } 30 | 31 | public int Cdto { get; set; } 32 | public int Ddatatransferobject { get; set; } 33 | 34 | public int E { get; set; } 35 | } 36 | 37 | [TestMethod] 38 | public void ExactNameAndImplicitlyConvertibleTypeConventionTest() 39 | { 40 | var source = new SourceClass(); 41 | var target = new TargetClass(); 42 | 43 | var mapper = new Mapper(); 44 | mapper.Map( source, target ); 45 | 46 | Assert.IsTrue( source.A == target.A ); 47 | Assert.IsTrue( source.B == target.B ); 48 | } 49 | 50 | [TestMethod] 51 | public void ExactNameAndTypeConventionTest() 52 | { 53 | var source = new SourceClass(); 54 | var target = new TargetClass(); 55 | 56 | var config = new Configuration( cfg => 57 | { 58 | cfg.Conventions.GetOrAdd( conventionConfig => 59 | { 60 | conventionConfig.MatchingRules.GetOrAdd( ruleConfig => 61 | { 62 | ruleConfig.AllowImplicitConversions = false; 63 | ruleConfig.AllowExplicitConversions = false; 64 | } ); 65 | } ); 66 | } ); 67 | 68 | var mapper = new Mapper( config ); 69 | mapper.Map( source, target ); 70 | 71 | Assert.IsTrue( source.A != target.A ); 72 | Assert.IsTrue( source.B != target.B ); 73 | } 74 | 75 | [TestMethod] 76 | public void SuffixNameAndTypeConventionTest() 77 | { 78 | var source = new SourceClass(); 79 | var target = new TargetClassDto(); 80 | 81 | var mapper = new Mapper( cfg => 82 | { 83 | cfg.Conventions.GetOrAdd( convention => 84 | { 85 | convention.MatchingRules 86 | .GetOrAdd( ruleConfig => ruleConfig.AllowImplicitConversions = true ) 87 | .GetOrAdd( ruleConfig => ruleConfig.IgnoreCase = true ) 88 | .GetOrAdd( ruleConfig => ruleConfig.IgnoreCase = true ); 89 | } ); 90 | } ); 91 | 92 | mapper.Map( source, target ); 93 | 94 | Assert.IsTrue( source.A == target.ADto ); 95 | Assert.IsTrue( source.B == target.BDataTransferObject ); 96 | Assert.IsTrue( source.C == target.Cdto ); 97 | Assert.IsTrue( source.D == target.Ddatatransferobject ); 98 | Assert.IsTrue( source.E == target.E ); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /UltraMapper.Tests/MappingConventions/ProjectionNestedMethodCalls.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using UltraMapper.Conventions; 3 | 4 | namespace UltraMapper.Tests 5 | { 6 | [TestClass] 7 | public class ProjectionNestedMethodCalls 8 | { 9 | private class OrderDto 10 | { 11 | private string _customerName; 12 | public string GetCustomerName() => _customerName; 13 | public void SetCustomerName( string name ) => _customerName = name; 14 | } 15 | 16 | private class Order 17 | { 18 | private Customer _customer; 19 | public Customer GetCustomer() => _customer; 20 | public void SetCustomer( Customer customer ) => _customer = customer; 21 | } 22 | 23 | private class Customer 24 | { 25 | private string _name; 26 | public void SetName( string name ) => _name = name; 27 | public string GetName() => _name; 28 | } 29 | 30 | [TestMethod] 31 | public void FlatteningNestedMethodCalls() 32 | { 33 | var customer = new Customer(); 34 | customer.SetName( "George Costanza" ); 35 | 36 | var order = new Order(); 37 | order.SetCustomer( customer ); 38 | 39 | var mapper = new Mapper( cfg => 40 | { 41 | cfg.Conventions.GetOrAdd(); 42 | } ); 43 | 44 | var dto = mapper.Map( order ); 45 | Assert.IsTrue( dto.GetCustomerName() == order.GetCustomer().GetName() ); 46 | } 47 | 48 | [TestMethod] 49 | public void UnflatteningNestedMethodCalls() 50 | { 51 | var dto = new OrderDto(); 52 | dto.SetCustomerName( "Johnny" ); 53 | 54 | var mapper = new Mapper( cfg => 55 | { 56 | cfg.Conventions.GetOrAdd(); 57 | } ); 58 | 59 | var order = mapper.Map( dto ); 60 | Assert.IsTrue( dto.GetCustomerName() == order.GetCustomer().GetName() ); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /UltraMapper.Tests/MappingConventions/SimpleProjectionConventionTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using UltraMapper.Conventions; 3 | 4 | namespace UltraMapper.Tests 5 | { 6 | /// 7 | /// Test simple scenarios, only public properties involved 8 | /// 9 | [TestClass] 10 | public class SimpleProjectionConventionTests 11 | { 12 | private class OrderDto 13 | { 14 | public string CustomerName { get; set; } 15 | public string ProductName { get; set; } 16 | } 17 | 18 | private class Order 19 | { 20 | public Customer Customer { get; set; } 21 | public Product Product { get; set; } 22 | } 23 | 24 | private class Product 25 | { 26 | public decimal Price { get; set; } 27 | public string Name { get; set; } 28 | } 29 | 30 | private class Customer 31 | { 32 | public string Name { get; set; } 33 | } 34 | 35 | [TestMethod] 36 | public void Flattening() 37 | { 38 | var customer = new Customer 39 | { 40 | Name = "George Costanza" 41 | }; 42 | 43 | var product = new Product 44 | { 45 | Name = "Bosco", 46 | Price = 4.99m 47 | }; 48 | 49 | var order = new Order 50 | { 51 | Customer = customer, 52 | Product = product 53 | }; 54 | 55 | var mapper = new Mapper( cfg => 56 | { 57 | cfg.Conventions.GetOrAdd(); 58 | } ); 59 | 60 | var dto = mapper.Map( order ); 61 | 62 | Assert.IsTrue( dto.CustomerName == customer.Name ); 63 | Assert.IsTrue( dto.ProductName == product.Name ); 64 | } 65 | 66 | [TestMethod] 67 | public void Unflattening() 68 | { 69 | var dto = new OrderDto() 70 | { 71 | CustomerName = "Johnny", 72 | ProductName = "Mobile phone" 73 | }; 74 | 75 | var mapper = new Mapper( cfg => 76 | { 77 | cfg.Conventions.GetOrAdd(); 78 | } ); 79 | 80 | //TODO: we need to create instances for nested objects!!! 81 | var order = mapper.Map( dto ); 82 | 83 | Assert.IsTrue( dto.CustomerName == order.Customer.Name ); 84 | Assert.IsTrue( dto.ProductName == order.Product.Name ); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /UltraMapper.Tests/ReadOnlyCollectionTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace UltraMapper.Tests 4 | { 5 | [TestClass] 6 | public class ReadOnlyCollectionTests 7 | { 8 | //[TestMethod] 9 | //[TestCategory( "ReadOnlyCollection" )] 10 | //public void CloneReadOnlyCollection() 11 | //{ 12 | // var source = new ReadOnlyCollection( new List() { 1, 2, 3 } ); 13 | 14 | // var ultraMapper = new Mapper(); 15 | // var target = ultraMapper.Map( source ); 16 | 17 | // Assert.IsTrue( source.SequenceEqual( target ) ); 18 | //} 19 | 20 | //[TestMethod] 21 | //[TestCategory( "ReadOnlyCollection" )] 22 | //public void ListToReadOnlyListDifferentElementType() 23 | //{ 24 | // List source = Enumerable.Range( 0, 10 ).ToList(); 25 | // source.Capacity = 100; 26 | 27 | // var ultraMapper = new Mapper(); 28 | // var target = ultraMapper.Map>( source ); 29 | 30 | // Assert.IsTrue( source.SequenceEqual( 31 | // target.Select( item => (int)item ) ) ); 32 | 33 | // bool isResultOk = ultraMapper.VerifyMapperResult( source, target ); 34 | // Assert.IsTrue( isResultOk ); 35 | //} 36 | 37 | //[TestMethod] 38 | //[TestCategory( "ReadOnlyCollection" )] 39 | //public void DirectCollectionToReadOnlyCollection() 40 | //{ 41 | // var source = new List() { 1, 2, 3 }; 42 | 43 | // var ultraMapper = new Mapper(); 44 | // var target = ultraMapper.Map>( source ); 45 | 46 | // Assert.IsTrue( source.SequenceEqual( target ) ); 47 | //} 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /UltraMapper.Tests/RealworldBugs/RealworldBug2.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | 4 | namespace UltraMapper.Tests 5 | { 6 | [TestClass] 7 | public class RealworldBug2 8 | { 9 | public class SourceClass 10 | { 11 | public string Id { get; set; } 12 | } 13 | 14 | public class TargetClass 15 | { 16 | public Guid Id { get; set; } 17 | } 18 | 19 | [TestMethod] 20 | public void StringToGuidTypeMapping() 21 | { 22 | var mapper = new Mapper( cfg => 23 | { 24 | cfg.MapTypes( str => Guid.Parse( str ) ); 25 | } ); 26 | 27 | var source = new SourceClass() { Id = Guid.NewGuid().ToString() }; 28 | var result = mapper.Map( source ); 29 | 30 | bool isResultOk = mapper.VerifyMapperResult( source, result ); 31 | Assert.IsTrue( isResultOk ); 32 | } 33 | 34 | [TestMethod] 35 | public void StringToGuidMemberMapping() 36 | { 37 | var mapper = new Mapper( cfg => 38 | { 39 | cfg.MapTypes() 40 | .MapMember( s => s.Id, t => t.Id, str => Guid.Parse( str ) ); 41 | } ); 42 | 43 | var source = new SourceClass() { Id = Guid.NewGuid().ToString() }; 44 | var result = mapper.Map( source ); 45 | 46 | bool isResultOk = mapper.VerifyMapperResult( source, result ); 47 | Assert.IsTrue( isResultOk ); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /UltraMapper.Tests/Records.cs: -------------------------------------------------------------------------------- 1 | //using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | //using System.ComponentModel; 3 | 4 | //namespace System.Runtime.CompilerServices 5 | //{ 6 | // [EditorBrowsable( EditorBrowsableState.Never )] 7 | // public class IsExternalInit { } 8 | //} 9 | 10 | //namespace UltraMapper.Tests 11 | //{ 12 | // [TestClass] 13 | // public class Records 14 | // { 15 | // public record Record 16 | // { 17 | // public string Value { get; init; } 18 | // } 19 | 20 | // public class OtherObject 21 | // { 22 | // public string Value { get; set; } 23 | // } 24 | 25 | // [TestMethod] 26 | // public void BasicTest() 27 | // { 28 | // var mapper = new Mapper(); 29 | 30 | // var otherObject = new OtherObject() { Value = "ciao" }; 31 | 32 | // var map1 = mapper.Map( otherObject ); 33 | // var map2 = mapper.Map( otherObject ); 34 | 35 | // Assert.IsTrue( map1.Equals( map2 ) ); 36 | 37 | // var r1 = new Record { Value = otherObject.Value }; 38 | // var r2 = new Record { Value = otherObject.Value }; 39 | 40 | // Assert.IsTrue( r1.Equals( r2 ) ); 41 | // } 42 | // } 43 | //} 44 | -------------------------------------------------------------------------------- /UltraMapper.Tests/StringSplitterTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Linq; 3 | using UltraMapper.Conventions; 4 | 5 | namespace UltraMapper.Tests 6 | { 7 | [TestClass] 8 | public class StringSplitterTests 9 | { 10 | [TestMethod] 11 | public void Split1() 12 | { 13 | var splitter = new StringSplitter( StringSplittingRules.PascalCase ); 14 | var result = splitter.Split( "ABCDEFG" ).ToList(); 15 | 16 | var isTrue = result.SequenceEqual( new[] { 17 | "A", "B", "C", "D", "E", "F", "G" } ); 18 | 19 | Assert.IsTrue( isTrue ); 20 | } 21 | 22 | [TestMethod] 23 | public void Split2() 24 | { 25 | var splitter = new StringSplitter( StringSplittingRules.PascalCase ); 26 | var result = splitter.Split( "AxBxCxDxExFxGx" ).ToList(); 27 | 28 | var isTrue = result.SequenceEqual( new[] { 29 | "Ax", "Bx", "Cx", "Dx", "Ex", "Fx", "Gx" } ); 30 | 31 | Assert.IsTrue( isTrue ); 32 | } 33 | 34 | [TestMethod] 35 | public void Split3() 36 | { 37 | var splitter = new StringSplitter( StringSplittingRules.PascalCase ); 38 | var result = splitter.Split( "xAxBxCxDxExFxGxe" ).ToList(); 39 | 40 | var isTrue = result.SequenceEqual( new[] { 41 | "x","Ax", "Bx", "Cx", "Dx", "Ex", "Fx", "Gxe" } ); 42 | 43 | Assert.IsTrue( isTrue ); 44 | } 45 | 46 | [TestMethod] 47 | public void Split4() 48 | { 49 | var splitter = new StringSplitter( StringSplittingRules.SnakeCase ); 50 | var result = splitter.Split( "xAxBxCxDx_ExFxGxe" ).ToList(); 51 | 52 | var isTrue = result.SequenceEqual( new[] { "xAxBxCxDx", "ExFxGxe" } ); 53 | 54 | Assert.IsTrue( isTrue ); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /UltraMapper.Tests/StructTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | using UltraMapper.MappingExpressionBuilders; 4 | 5 | namespace UltraMapper.Tests 6 | { 7 | [TestClass] 8 | public class StructTests 9 | { 10 | private class Test 11 | { 12 | public DateTime DateTime { get; set; } 13 | = new DateTime( 1988, 05, 29 ); 14 | } 15 | 16 | private struct StructTest 17 | { 18 | public DateTime DateTime { get; set; } 19 | } 20 | 21 | [TestMethod] 22 | public void DateTimeDirectTypeMapping() 23 | { 24 | DateTime dateTime = new DateTime( 2017, 03, 13 ); 25 | 26 | var mapper = new Mapper(); 27 | mapper.Map( dateTime, out DateTime clone ); 28 | 29 | var mapping = mapper.Config[ typeof( DateTime ), typeof( DateTime ) ]; 30 | 31 | Assert.IsTrue( dateTime == clone ); 32 | Assert.IsTrue( !Object.ReferenceEquals( dateTime, clone ) ); 33 | Assert.IsTrue( mapping.Mapper is StructMapper ); //convert mapper can get the job done as well but is slower 34 | } 35 | 36 | [TestMethod] 37 | public void DateTimeMemberMapping() 38 | { 39 | var test = new Test(); 40 | var mapper = new Mapper(); 41 | 42 | var clone = mapper.Map( test ); 43 | 44 | Assert.IsTrue( test.DateTime == clone.DateTime ); 45 | Assert.IsTrue( !Object.ReferenceEquals( test.DateTime, clone.DateTime ) ); 46 | } 47 | 48 | [TestMethod] 49 | public void ClassToStructMapping() 50 | { 51 | var mapper = new Mapper(); 52 | 53 | var source = new Test(); 54 | mapper.Map( source, out StructTest target ); 55 | 56 | var result = mapper.VerifyMapperResult( source, target ); 57 | Assert.IsTrue( result ); 58 | } 59 | 60 | [TestMethod] 61 | public void ClassToStructMapping2() 62 | { 63 | var mapper = new Mapper(); 64 | 65 | var source = new Test(); 66 | var target = mapper.Map( source ); 67 | 68 | var result = mapper.VerifyMapperResult( source, target ); 69 | Assert.IsTrue( result ); 70 | } 71 | 72 | [TestMethod] 73 | public void StructToClassMapping() 74 | { 75 | var mapper = new Mapper(); 76 | 77 | var source = new StructTest() 78 | { 79 | DateTime = new DateTime( 2013, 12, 18 ) 80 | }; 81 | 82 | var target = mapper.Map( source ); 83 | 84 | var result = mapper.VerifyMapperResult( source, target ); 85 | Assert.IsTrue( result ); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /UltraMapper.Tests/UltraMapper.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net452;net46;net461;net462;net47;net471;net472;net48;net50;net60 5 | true 6 | true 7 | Mauro Sampietro 8 | 2020 9 | true 10 | AnyCPU 11 | true 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /UltraMapper.Tests/UnwritableTargetMembers.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace UltraMapper.Tests 4 | { 5 | /// 6 | /// Test private fields and properties; 7 | /// getter only properties 8 | /// 9 | [TestClass] 10 | public class NonPublicMembers 11 | { 12 | private class UnreadableMembers 13 | { 14 | private int field = 11; 15 | private int Property1 { get; set; } = 13; 16 | public int Property2 { private get; set; } = 17; 17 | private int Property3 { get; } = 19; 18 | } 19 | 20 | private class UnwritableMembers 21 | { 22 | private int field; 23 | private int Property1 { get; set; } 24 | public int Property2 { private get; set; } 25 | private int Property3 { get; } 26 | public int Property4 { get; } 27 | } 28 | 29 | [TestMethod] 30 | public void ReadNonPublicWriteNonPublic() 31 | { 32 | var source = new UnreadableMembers(); 33 | var target = new UnwritableMembers(); 34 | 35 | var mapper = new Mapper( config => 36 | { 37 | //config.ConventionResolvers.SourceMemberProvider.IgnoreFields = false; 38 | //config.ConventionResolver.SourceMemberProvider.IgnoreNonPublicMembers = false; 39 | 40 | //config.ConventionResolver.TargetMemberProvider.IgnoreFields = false; 41 | //config.ConventionResolver.TargetMemberProvider.IgnoreNonPublicMembers = false; 42 | } ); 43 | 44 | mapper.Map( source, target ); 45 | 46 | var isResultOk = mapper.VerifyMapperResult( source, target ); 47 | Assert.IsTrue( isResultOk ); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /UltraMapper.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /UltraMapper.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.2.32526.322 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UltraMapper", "UltraMapper\UltraMapper.csproj", "{34E9592B-27F8-45BC-BE54-F90BD125B4E2}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1A5DCC8D-E59E-4052-8A69-8CB1362A2A9A}" 9 | ProjectSection(SolutionItems) = preProject 10 | appveyor.yml = appveyor.yml 11 | EndProjectSection 12 | EndProject 13 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UltraMapper.Tests", "UltraMapper.Tests\UltraMapper.Tests.csproj", "{D7703A65-FB14-4FF0-A2C2-C1F592A1499E}" 14 | EndProject 15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UltraMapper.Benchmarks", "UltraMapper.Benchmarks\UltraMapper.Benchmarks.csproj", "{A8B2252F-B9C2-4B4D-BD33-D640763F753E}" 16 | EndProject 17 | Global 18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 19 | Debug|Any CPU = Debug|Any CPU 20 | Release|Any CPU = Release|Any CPU 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {34E9592B-27F8-45BC-BE54-F90BD125B4E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {34E9592B-27F8-45BC-BE54-F90BD125B4E2}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {34E9592B-27F8-45BC-BE54-F90BD125B4E2}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {34E9592B-27F8-45BC-BE54-F90BD125B4E2}.Release|Any CPU.Build.0 = Release|Any CPU 27 | {D7703A65-FB14-4FF0-A2C2-C1F592A1499E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {D7703A65-FB14-4FF0-A2C2-C1F592A1499E}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {D7703A65-FB14-4FF0-A2C2-C1F592A1499E}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {D7703A65-FB14-4FF0-A2C2-C1F592A1499E}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {A8B2252F-B9C2-4B4D-BD33-D640763F753E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {A8B2252F-B9C2-4B4D-BD33-D640763F753E}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {A8B2252F-B9C2-4B4D-BD33-D640763F753E}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {A8B2252F-B9C2-4B4D-BD33-D640763F753E}.Release|Any CPU.Build.0 = Release|Any CPU 35 | EndGlobalSection 36 | GlobalSection(SolutionProperties) = preSolution 37 | HideSolutionNode = FALSE 38 | EndGlobalSection 39 | GlobalSection(ExtensibilityGlobals) = postSolution 40 | SolutionGuid = {869DBDB7-A9D0-40AD-9B1C-F7BA19A2EB63} 41 | EndGlobalSection 42 | EndGlobal 43 | -------------------------------------------------------------------------------- /UltraMapper/AssemblyOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo( "UltraMapper.Tests" )] 4 | [assembly: InternalsVisibleTo( "UltraMapper.CommandLine" )] 5 | [assembly: InternalsVisibleTo( "UltraMapper.Csv" )] 6 | -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/ExpressionLoops.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | 5 | namespace UltraMapper.Internals 6 | { 7 | public static class ExpressionLoops 8 | { 9 | public static Expression ForEach( Expression collection, 10 | ParameterExpression loopVar, Expression loopContent ) 11 | { 12 | var breakLabel = Expression.Label( "LoopBreak" ); 13 | var continueLabel = Expression.Label( "LoopContinue" ); 14 | 15 | return ForEach( collection, loopVar, loopContent, breakLabel, continueLabel ); 16 | } 17 | 18 | public static Expression ForEach( Expression collection, ParameterExpression loopVar, 19 | Expression loopContent, LabelTarget @break, LabelTarget @continue ) 20 | { 21 | var elementType = loopVar.Type; 22 | var enumerableType = typeof( IEnumerable<> ).MakeGenericType( elementType ); 23 | var enumeratorType = typeof( IEnumerator<> ).MakeGenericType( elementType ); 24 | 25 | var enumeratorVar = Expression.Variable( enumeratorType, "enumerator" ); 26 | var getEnumeratorCall = Expression.Call( collection, enumerableType.GetMethod( nameof( IEnumerable.GetEnumerator ) ) ); 27 | var enumeratorAssign = Expression.Assign( enumeratorVar, getEnumeratorCall ); 28 | 29 | //The MoveNext method's actually on IEnumerator, not IEnumerator 30 | var moveNextCall = Expression.Call( enumeratorVar, typeof( IEnumerator ) 31 | .GetMethod( nameof( IEnumerator.MoveNext ) ) ); 32 | 33 | return Expression.Block 34 | ( 35 | new[] { enumeratorVar }, 36 | 37 | enumeratorAssign, 38 | 39 | Expression.Loop 40 | ( 41 | Expression.IfThenElse 42 | ( 43 | Expression.Equal( moveNextCall, Expression.Constant( true ) ), 44 | Expression.Block 45 | ( 46 | new[] { loopVar }, 47 | 48 | Expression.Assign( loopVar, Expression.Property( enumeratorVar, nameof( IEnumerator.Current ) ) ), 49 | loopContent 50 | ), 51 | 52 | Expression.Break( @break ) 53 | ), 54 | 55 | @break, 56 | @continue 57 | ) ); 58 | } 59 | 60 | public static Expression For( ParameterExpression loopVar, Expression initValue, 61 | Expression condition, Expression increment, Expression loopContent ) 62 | { 63 | var initAssign = Expression.Assign( loopVar, initValue ); 64 | var breakLabel = Expression.Label( "LoopBreak" ); 65 | 66 | var loop = Expression.Block 67 | ( 68 | new[] { loopVar }, 69 | 70 | initAssign, 71 | 72 | Expression.Loop 73 | ( 74 | Expression.IfThenElse 75 | ( 76 | condition, 77 | Expression.Block 78 | ( 79 | loopContent, 80 | increment 81 | ), 82 | 83 | Expression.Break( breakLabel ) 84 | ), 85 | 86 | breakLabel 87 | ) 88 | ); 89 | 90 | return loop; 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/ExpressionParameterReplacer.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace UltraMapper.Internals 4 | { 5 | internal class ExpressionParameterReplacer : ExpressionVisitor 6 | { 7 | private readonly Expression _expression; 8 | private readonly string _name; 9 | 10 | protected override Expression VisitParameter( ParameterExpression node ) 11 | { 12 | if( node.Name == _name && (node.Type == _expression.Type || 13 | node.Type.IsAssignableFrom( _expression.Type ) || 14 | _expression.Type.IsAssignableFrom( node.Type )) ) 15 | { 16 | return _expression; 17 | } 18 | 19 | return base.VisitParameter( node ); 20 | } 21 | 22 | internal ExpressionParameterReplacer( Expression expression, string name ) 23 | { 24 | _expression = expression; 25 | _name = name; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/MappingExpressionBuilders/AbstractMappingExpressionBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using UltraMapper.Internals; 4 | 5 | namespace UltraMapper.MappingExpressionBuilders 6 | { 7 | public class AbstractMappingExpressionBuilder : ReferenceMapper 8 | { 9 | public override bool CanHandle( Mapping mapping ) 10 | { 11 | var source = mapping.Source; 12 | var target = mapping.Target; 13 | 14 | return source.EntryType.IsAbstract || source.EntryType.IsInterface || source.EntryType == typeof( object ) || 15 | target.EntryType.IsAbstract || target.EntryType.IsInterface || target.EntryType == typeof( object ); 16 | } 17 | 18 | public override LambdaExpression GetMappingExpression( Mapping mapping ) 19 | { 20 | var source = mapping.Source; 21 | var target = mapping.Target; 22 | 23 | var context = GetMapperContext( mapping ); 24 | 25 | var typeMapping = context.MapperConfiguration[ source.EntryType, target.EntryType ]; 26 | var mapMethod = ReferenceMapperContext.RecursiveMapMethodInfo 27 | .MakeGenericMethod( source.EntryType, target.EntryType ); 28 | 29 | var expression = Expression.Block 30 | ( 31 | new[] { context.Mapper }, 32 | 33 | Expression.Assign( context.Mapper, Expression.Constant( context.MapperInstance ) ), 34 | 35 | Expression.Call( context.Mapper, mapMethod, 36 | context.SourceInstance, context.TargetInstance, 37 | context.ReferenceTracker, Expression.Constant( typeMapping ) ) 38 | ); 39 | 40 | var delegateType = typeof( Action<,,> ).MakeGenericType( 41 | context.ReferenceTracker.Type, context.SourceInstance.Type, 42 | context.TargetInstance.Type ); 43 | 44 | return Expression.Lambda( delegateType, expression, 45 | context.ReferenceTracker, context.SourceInstance, context.TargetInstance ); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/MappingExpressionBuilders/BaseMappingExpressionBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using UltraMapper.Internals; 4 | 5 | namespace UltraMapper.MappingExpressionBuilders 6 | { 7 | public class MappingExpressionBuilder 8 | { 9 | public static UltraMapperDelegate GetMappingEntryPoint( 10 | Type source, Type target, LambdaExpression mappingExpression ) 11 | { 12 | var referenceTrackerParam = Expression.Parameter( typeof( ReferenceTracker ), "referenceTracker" ); 13 | var sourceParam = Expression.Parameter( typeof( object ), "sourceInstance" ); 14 | var targetParam = Expression.Parameter( typeof( object ), "targetInstance" ); 15 | 16 | var sourceInstance = Expression.Convert( sourceParam, source ); 17 | var targetInstance = Expression.Convert( targetParam, target ); 18 | 19 | if( mappingExpression.Parameters.Count == 2 && 20 | mappingExpression.Parameters[ 0 ].Type == typeof( ReferenceTracker ) ) 21 | { 22 | var bodyExp = Expression.Block 23 | ( 24 | Expression.Invoke( mappingExpression, referenceTrackerParam, sourceInstance ) 25 | ); 26 | 27 | return Expression.Lambda( 28 | bodyExp, referenceTrackerParam, sourceParam, targetParam ).Compile(); 29 | } 30 | else if( mappingExpression.Parameters.Count == 1 ) 31 | { 32 | var bodyExp = Expression.Convert( Expression.Invoke( mappingExpression, sourceInstance ), typeof( object ) ); 33 | 34 | return Expression.Lambda( 35 | bodyExp, referenceTrackerParam, sourceParam, targetParam ).Compile(); 36 | } 37 | else 38 | { 39 | var bodyExp = (Expression)Expression.Invoke( mappingExpression, 40 | referenceTrackerParam, sourceInstance, targetInstance ); 41 | 42 | bodyExp = Expression.Convert( bodyExp, typeof( object ) ); 43 | 44 | return Expression.Lambda( 45 | bodyExp, referenceTrackerParam, sourceParam, targetParam ).Compile(); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/MappingExpressionBuilders/Contexts/CollectionMapperContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using UltraMapper.Internals; 4 | 5 | namespace UltraMapper.MappingExpressionBuilders 6 | { 7 | public class CollectionMapperContext : ReferenceMapperContext 8 | { 9 | public Type SourceCollectionElementType { get; protected set; } 10 | public Type TargetCollectionElementType { get; protected set; } 11 | 12 | public bool IsSourceElementTypeBuiltIn { get; protected set; } 13 | public bool IsTargetElementTypeBuiltIn { get; protected set; } 14 | 15 | public ParameterExpression SourceCollectionLoopingVar { get; set; } 16 | 17 | public LabelTarget Continue { get; } 18 | public LabelTarget Break { get; } 19 | 20 | public CollectionMapperContext( Mapping mapping ) : base( mapping ) 21 | { 22 | SourceCollectionElementType = SourceInstance.Type.GetCollectionGenericType(); 23 | TargetCollectionElementType = TargetInstance.Type.GetCollectionGenericType(); 24 | 25 | if( SourceCollectionElementType != null ) 26 | { 27 | IsSourceElementTypeBuiltIn = SourceCollectionElementType.IsBuiltIn( true ); 28 | SourceCollectionLoopingVar = Expression.Parameter( SourceCollectionElementType, "loopVar" ); 29 | } 30 | 31 | if( TargetCollectionElementType != null ) 32 | IsTargetElementTypeBuiltIn = TargetCollectionElementType.IsBuiltIn( true ); 33 | 34 | Continue = Expression.Label( "LoopContinue" ); 35 | Break = Expression.Label( "LoopBreak" ); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/MappingExpressionBuilders/Contexts/CollectionMapperViaTempCollectionContext.cs: -------------------------------------------------------------------------------- 1 | //using System; 2 | //using System.Linq.Expressions; 3 | 4 | //namespace UltraMapper.MappingExpressionBuilders 5 | //{ 6 | // public class CollectionMapperViaTempCollectionContext : CollectionMapperContext 7 | // { 8 | // public ParameterExpression TempCollection { get; set; } 9 | // public ParameterExpression TempTargetCollectionLoopingVar { get; } 10 | 11 | // public CollectionMapperViaTempCollectionContext( Type source, Type target, IMappingOptions options ) 12 | // : base( source, target, options ) 13 | // { 14 | // TempTargetCollectionLoopingVar = Expression.Parameter( TargetCollectionElementType, "loopVar" ); 15 | // } 16 | // } 17 | //} 18 | -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/MappingExpressionBuilders/Contexts/CustomConverterContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace UltraMapper.MappingExpressionBuilders 5 | { 6 | public class CustomConverterContext 7 | { 8 | public ParameterExpression SourceInstance { get; protected set; } 9 | public ParameterExpression TargetInstance { get; protected set; } 10 | 11 | public ConstantExpression SourceNullValue { get; protected set; } 12 | public ConstantExpression TargetNullValue { get; protected set; } 13 | 14 | public ParameterExpression ReferenceTracker { get; protected set; } 15 | public ParameterExpression TrackedReference { get; protected set; } 16 | 17 | public CustomConverterContext( Type source, Type target ) 18 | { 19 | SourceInstance = Expression.Parameter( source, "sourceInstance" ); 20 | TargetInstance = Expression.Parameter( target, "targetInstance" ); 21 | ReferenceTracker = Expression.Parameter( typeof( ReferenceTracker ), "referenceTracker" ); 22 | TrackedReference = Expression.Parameter( target, "trackedReference" ); 23 | 24 | if( !SourceInstance.Type.IsValueType ) 25 | SourceNullValue = Expression.Constant( null, SourceInstance.Type ); 26 | 27 | if( !TargetInstance.Type.IsValueType ) 28 | TargetNullValue = Expression.Constant( null, TargetInstance.Type ); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/MappingExpressionBuilders/Contexts/DictionaryMapperContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using UltraMapper.Internals; 4 | 5 | namespace UltraMapper.MappingExpressionBuilders 6 | { 7 | public class DictionaryMapperContext : CollectionMapperContext 8 | { 9 | public ParameterExpression SourceCollectionElementKey { get; private set; } 10 | public ParameterExpression SourceCollectionElementValue { get; private set; } 11 | 12 | public ParameterExpression TargetCollectionElementKey { get; private set; } 13 | public ParameterExpression TargetCollectionElementValue { get; private set; } 14 | 15 | public DictionaryMapperContext( Mapping mapping ) : base( mapping ) 16 | { 17 | var sourceCollectionElementKeyType = SourceCollectionElementType.GetGenericArguments()[ 0 ]; 18 | var sourceCollectionElementValueType = SourceCollectionElementType.GetGenericArguments()[ 1 ]; 19 | 20 | var targetCollectionElementKeyType = TargetCollectionElementType.GetGenericArguments()[ 0 ]; 21 | var targetCollectionElementValueType = TargetCollectionElementType.GetGenericArguments()[ 1 ]; 22 | 23 | SourceCollectionElementKey = Expression.Variable( sourceCollectionElementKeyType, "sourceKey" ); 24 | SourceCollectionElementValue = Expression.Variable( sourceCollectionElementValueType, "sourceValue" ); 25 | 26 | TargetCollectionElementKey = Expression.Variable( targetCollectionElementKeyType, "targetKey" ); 27 | TargetCollectionElementValue = Expression.Variable( targetCollectionElementValueType, "targetValue" ); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/MappingExpressionBuilders/Contexts/MapperContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using UltraMapper.Internals; 4 | 5 | namespace UltraMapper.MappingExpressionBuilders 6 | { 7 | public class MapperContext 8 | { 9 | public ParameterExpression SourceInstance { get; protected set; } 10 | public ParameterExpression TargetInstance { get; protected set; } 11 | public IMappingOptions Options { get; protected set; } 12 | public Mapping Mapping { get; } 13 | public Configuration MapperConfiguration { get; } 14 | 15 | public MapperContext( Type source, Type target, IMappingOptions options ) 16 | { 17 | this.SourceInstance = Expression.Parameter( source, "sourceInstance" ); 18 | this.TargetInstance = Expression.Parameter( target, "targetInstance" ); 19 | this.Options = options; 20 | this.Mapping = (Mapping)options; 21 | this.MapperConfiguration = this.Mapping.GlobalConfig; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/MappingExpressionBuilders/Contexts/MemberMappingContext.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using UltraMapper.Internals; 3 | 4 | namespace UltraMapper.MappingExpressionBuilders 5 | { 6 | public class MemberMappingContext : ReferenceMapperContext 7 | { 8 | public ParameterExpression TrackedReference { get; protected set; } 9 | 10 | public ParameterExpression TargetMember { get; protected set; } 11 | public ParameterExpression SourceMember { get; protected set; } 12 | 13 | public Expression SourceMemberValueGetter { get; protected set; } 14 | public Expression TargetMemberValueGetter { get; protected set; } 15 | 16 | public Expression TargetMemberValueSetter { get; protected set; } 17 | 18 | public Expression TargetMemberNullValue { get; internal set; } 19 | public Expression SourceMemberNullValue { get; internal set; } 20 | 21 | public readonly MemberMapping MemberMapping; 22 | 23 | public MemberMappingContext( MemberMapping mapping ) 24 | : base( mapping.InstanceTypeMapping.Source.EntryType, 25 | mapping.InstanceTypeMapping.Target.EntryType, mapping ) 26 | { 27 | this.MemberMapping = mapping; 28 | 29 | var sourceMemberType = mapping.SourceMember.MemberType; 30 | var targetMemberType = mapping.TargetMember.MemberType; 31 | 32 | TrackedReference = Expression.Parameter( targetMemberType, "trackedReference" ); 33 | 34 | SourceMember = Expression.Variable( sourceMemberType, "sourceValue" ); 35 | TargetMember = Expression.Variable( targetMemberType, "targetValue" ); 36 | 37 | if( !sourceMemberType.IsValueType ) 38 | SourceMemberNullValue = Expression.Constant( null, sourceMemberType ); 39 | 40 | if( !targetMemberType.IsValueType ) 41 | TargetMemberNullValue = Expression.Constant( null, targetMemberType ); 42 | 43 | var sourceGetterInstanceParamName = mapping.SourceMember 44 | .ValueGetter.Parameters[ 0 ].Name; 45 | 46 | SourceMemberValueGetter = mapping.SourceMember.ValueGetter.Body 47 | .ReplaceParameter( SourceInstance, sourceGetterInstanceParamName ); 48 | 49 | if( mapping.TargetMember.ValueGetter != null ) 50 | { 51 | var targetGetterInstanceParamName = mapping.TargetMember 52 | .ValueGetter.Parameters[ 0 ].Name; 53 | 54 | TargetMemberValueGetter = mapping.TargetMember.ValueGetter.Body 55 | .ReplaceParameter( TargetInstance, targetGetterInstanceParamName ); 56 | } 57 | 58 | var targetSetterInstanceParamName = mapping.TargetMember.ValueSetter.Parameters[ 0 ].Name; 59 | var targetSetterMemberParamName = mapping.TargetMember.ValueSetter.Parameters[ 1 ].Name; 60 | 61 | TargetMemberValueSetter = mapping.TargetMember.ValueSetter.Body 62 | .ReplaceParameter( TargetInstance, targetSetterInstanceParamName ) 63 | .ReplaceParameter( TargetMember, targetSetterMemberParamName ); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/MappingExpressionBuilders/Contexts/ReferenceMapperContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using UltraMapper.Internals; 6 | 7 | namespace UltraMapper.MappingExpressionBuilders 8 | { 9 | public class ReferenceMapperContext : MapperContext 10 | { 11 | public ConstantExpression SourceInstanceNullValue { get; protected set; } 12 | public ConstantExpression TargetInstanceNullValue { get; protected set; } 13 | 14 | public ParameterExpression ReferenceTracker { get; protected set; } 15 | public ParameterExpression Mapper { get; private set; } 16 | 17 | public static MethodInfo RecursiveMapMethodInfo { get; protected set; } 18 | public Mapper MapperInstance { get; private set; } 19 | 20 | static ReferenceMapperContext() 21 | { 22 | RecursiveMapMethodInfo = GetUltraMapperMapGenericMethodMemberMapping(); 23 | } 24 | 25 | public ReferenceMapperContext( Mapping mapping ) 26 | : this( mapping.Source.EntryType, mapping.Target.EntryType, (IMappingOptions)mapping ) { } 27 | 28 | public ReferenceMapperContext( Type source, Type target, IMappingOptions options ) 29 | : base( source, target, options ) 30 | { 31 | ReferenceTracker = Expression.Parameter( typeof( ReferenceTracker ), "referenceTracker" ); 32 | 33 | if( !SourceInstance.Type.IsValueType ) 34 | SourceInstanceNullValue = Expression.Constant( null, SourceInstance.Type ); 35 | 36 | if( !TargetInstance.Type.IsValueType ) 37 | TargetInstanceNullValue = Expression.Constant( null, TargetInstance.Type ); 38 | 39 | Mapper = Expression.Variable( typeof( Mapper ), "mapper" ); 40 | MapperInstance = new Mapper( MapperConfiguration ); 41 | } 42 | 43 | private static MethodInfo GetUltraMapperMapGenericMethodMemberMapping() 44 | { 45 | return typeof( Mapper ) 46 | .GetMethods( BindingFlags.Instance | BindingFlags.NonPublic ) 47 | .Where( m => m.Name == nameof( UltraMapper.Mapper.Map ) ) 48 | .Select( m => new 49 | { 50 | Method = m, 51 | Params = m.GetParameters(), 52 | GenericArgs = m.GetGenericArguments() 53 | } ) 54 | .Where 55 | ( 56 | x => 57 | x.Params.Length == 4 && x.GenericArgs.Length == 2 && 58 | x.Params[ 0 ].ParameterType == x.GenericArgs[ 0 ] && 59 | x.Params[ 1 ].ParameterType == x.GenericArgs[ 1 ] && 60 | x.Params[ 2 ].ParameterType == typeof( ReferenceTracker ) && 61 | x.Params[ 3 ].ParameterType == typeof( IMapping ) 62 | ) 63 | .Select( x => x.Method ) 64 | .First(); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/MappingExpressionBuilders/CustomConverterExpressionBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using UltraMapper.Internals; 4 | 5 | namespace UltraMapper.MappingExpressionBuilders 6 | { 7 | public class CustomConverterExpressionBuilder 8 | { 9 | public static LambdaExpression ReplaceParams( LambdaExpression customConverter ) 10 | { 11 | if( customConverter.Parameters.Count == 1 ) 12 | { 13 | //First param: SourceInstance 14 | var sourceType = customConverter.Parameters[ 0 ].Type; 15 | var targetType = customConverter.ReturnType; 16 | var context = new CustomConverterContext( sourceType, targetType ); 17 | 18 | var expression = customConverter.Body.ReplaceParameter( context.SourceInstance, 19 | customConverter.Parameters[ 0 ].Name ); 20 | 21 | var delegateType = typeof( Func<,> ).MakeGenericType( 22 | context.SourceInstance.Type, context.TargetInstance.Type ); 23 | 24 | return Expression.Lambda( delegateType, expression, context.SourceInstance ); 25 | } 26 | else if( customConverter.Parameters.Count == 2 ) 27 | { 28 | //First param: ReferenceTracker 29 | //Second param: SourceInstance 30 | 31 | var sourceType = customConverter.Parameters[ 1 ].Type; 32 | var targetType = customConverter.ReturnType; 33 | var context = new CustomConverterContext( sourceType, targetType ); 34 | 35 | var expression = customConverter.Body 36 | .ReplaceParameter( context.ReferenceTracker, customConverter.Parameters[ 0 ].Name ) 37 | .ReplaceParameter( context.SourceInstance, customConverter.Parameters[ 1 ].Name ); 38 | 39 | var delegateType = typeof( Func<,,> ).MakeGenericType( 40 | context.ReferenceTracker.Type, context.SourceInstance.Type, 41 | context.TargetInstance.Type ); 42 | 43 | return Expression.Lambda( delegateType, expression, 44 | context.ReferenceTracker, context.SourceInstance ); 45 | } 46 | 47 | throw new NotSupportedException( "The type of custom converter you supplied is not supported" ); 48 | } 49 | 50 | ///// 51 | ///// Adds reference tracking 52 | ///// 53 | ///// 54 | //public static LambdaExpression Encapsule( LambdaExpression customConverter ) 55 | //{ 56 | // var sourceType = customConverter.Parameters[ 0 ].Type; 57 | // var targetType = customConverter.ReturnType; 58 | // var context = new CustomConverterContext( sourceType, targetType ); 59 | 60 | // var expression = GetExpression( customConverter, context ); 61 | 62 | // var delegateType = typeof( Func<,,> ).MakeGenericType( 63 | // context.ReferenceTracker.Type, context.SourceInstance.Type, 64 | // context.TargetInstance.Type ); 65 | 66 | // return Expression.Lambda( delegateType, expression, 67 | // context.ReferenceTracker, context.SourceInstance ); 68 | //} 69 | 70 | //private static Expression GetExpression( LambdaExpression customConverter, CustomConverterContext context ) 71 | //{ 72 | // var sourceType = customConverter.Parameters[ 0 ].Type; 73 | // var targetType = customConverter.ReturnType; 74 | 75 | // if( sourceType.IsBuiltIn( true ) || targetType.IsBuiltIn( true ) ) 76 | // { 77 | // //If either the source type or the target type is a primitive type then 78 | // //there's nothing to track but the lambda will end up being encapsulated anyway 79 | // return Expression.Invoke( customConverter, context.SourceInstance ); 80 | // } 81 | 82 | // var memberAssignment = Expression.Assign( context.TargetInstance, customConverter.Body 83 | // .ReplaceParameter( context.SourceInstance, customConverter.Parameters[ 0 ].Name ) ); 84 | 85 | // var lookUpBlock = ReferenceTracking.ReferenceTrackingExpression.GetMappingExpression( 86 | // context.ReferenceTracker, context.SourceInstance, context.TargetInstance, 87 | // memberAssignment, null, null, null, false ); 88 | 89 | // return Expression.Block 90 | // ( 91 | // new[] { context.TargetInstance }, 92 | // lookUpBlock, 93 | // context.TargetInstance 94 | // ); 95 | //} 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/MappingExpressionBuilders/IMappingExpressionBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | using UltraMapper.Internals; 3 | 4 | namespace UltraMapper.MappingExpressionBuilders 5 | { 6 | public interface IMappingExpressionBuilder 7 | { 8 | /// 9 | /// Gets a value indicating whether the mapper can handle the 10 | /// mapping between and 11 | /// 12 | /// Source type 13 | /// Target type 14 | /// True if the mapping can be handled by the mapper, False otherwise. 15 | bool CanHandle( Mapping mapping ); 16 | 17 | /// 18 | /// Gets an expression capable of mapping between 19 | /// and 20 | /// 21 | /// Source type 22 | /// Target type 23 | /// Mapping options 24 | /// The mapping expression 25 | LambdaExpression GetMappingExpression( Mapping mapping ); 26 | } 27 | 28 | public interface IMemberMappingExpression 29 | { 30 | Expression GetMemberAssignment( MemberMappingContext memberContext, out bool needsTrackingOrRecursion ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/MappingExpressionBuilders/PrimitiveTypes/BuiltInTypesMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using UltraMapper.Internals; 4 | 5 | namespace UltraMapper.MappingExpressionBuilders 6 | { 7 | public sealed class BuiltInTypeMapper : PrimitiveMapperBase 8 | { 9 | public override bool CanHandle( Mapping mapping ) 10 | { 11 | var source = mapping.Source; 12 | var target = mapping.Target; 13 | 14 | bool areTypesBuiltIn = source.EntryType.IsBuiltIn( false ) 15 | && target.EntryType.IsBuiltIn( false ); 16 | 17 | return areTypesBuiltIn && (source == target || 18 | source.EntryType.IsImplicitlyConvertibleTo( target.EntryType ) || 19 | source.EntryType.IsExplicitlyConvertibleTo( target.EntryType )); 20 | } 21 | 22 | protected override Expression GetValueExpression( MapperContext context ) 23 | { 24 | if( context.SourceInstance.Type == context.TargetInstance.Type ) 25 | return context.SourceInstance; 26 | 27 | return Expression.Convert( context.SourceInstance, 28 | context.TargetInstance.Type ); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/MappingExpressionBuilders/PrimitiveTypes/ConvertMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using UltraMapper.Internals; 5 | 6 | namespace UltraMapper.MappingExpressionBuilders 7 | { 8 | public sealed class ConvertMapper : PrimitiveMapperBase 9 | { 10 | private static readonly Type _convertType = typeof( Convert ); 11 | private static readonly HashSet _supportedConversions = new HashSet(); 12 | 13 | public override bool CanHandle( Mapping mapping ) 14 | { 15 | var source = mapping.Source; 16 | var target = mapping.Target; 17 | bool areTypesBuiltIn = source.EntryType.IsBuiltIn( false ) && target.EntryType.IsBuiltIn( false ); 18 | return areTypesBuiltIn || SourceIsConvertibleToTarget( source.EntryType, target.EntryType ); 19 | } 20 | 21 | private bool SourceIsConvertibleToTarget( Type source, Type target ) 22 | { 23 | try 24 | { 25 | var typePair = new TypePair( source, target ); 26 | if( _supportedConversions.Contains( typePair ) ) 27 | return true; 28 | 29 | if( !source.ImplementsInterface( typeof( IConvertible ) ) ) 30 | return false; 31 | 32 | var testValue = Activator.CreateInstance( source ); 33 | Convert.ChangeType( testValue, target ); 34 | 35 | _supportedConversions.Add( typePair ); 36 | return true; 37 | } 38 | catch( InvalidCastException ) 39 | { 40 | return false; 41 | } 42 | catch( Exception ) 43 | { 44 | return false; 45 | } 46 | } 47 | 48 | protected override Expression GetValueExpression( MapperContext context ) 49 | { 50 | var methodName = $"To{context.TargetInstance.Type.Name}"; 51 | var methodParams = new[] { context.SourceInstance.Type }; 52 | 53 | var convertMethod = _convertType.GetMethod( methodName, methodParams ); 54 | return Expression.Call( convertMethod, context.SourceInstance ); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/MappingExpressionBuilders/PrimitiveTypes/EnumMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using UltraMapper.Internals; 4 | 5 | namespace UltraMapper.MappingExpressionBuilders 6 | { 7 | public class EnumMapper : PrimitiveMapperBase 8 | { 9 | public override bool CanHandle( Mapping mapping ) 10 | { 11 | var source = mapping.Source; 12 | var target = mapping.Target; 13 | 14 | return target.EntryType.IsEnum; 15 | } 16 | 17 | protected override Expression GetValueExpression( MapperContext context ) 18 | { 19 | return Expression.Convert( Expression.Convert( 20 | context.SourceInstance, typeof( int ) ), context.TargetInstance.Type ); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/MappingExpressionBuilders/PrimitiveTypes/NullableMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using UltraMapper.Internals; 4 | 5 | namespace UltraMapper.MappingExpressionBuilders 6 | { 7 | public class NullableMapper : PrimitiveMapperBase 8 | { 9 | public override bool CanHandle( Mapping mapping ) 10 | { 11 | var source = mapping.Source; 12 | var target = mapping.Target; 13 | return source.EntryType.IsNullable() || target.EntryType.IsNullable(); 14 | } 15 | 16 | protected override Expression GetValueExpression( MapperContext context ) 17 | { 18 | if( context.SourceInstance.Type == context.TargetInstance.Type ) 19 | return context.SourceInstance; 20 | 21 | var sourceNullUnwrappedType = context.SourceInstance.Type.GetUnderlyingTypeIfNullable(); 22 | var targetNullUnwrappedType = context.TargetInstance.Type.GetUnderlyingTypeIfNullable(); 23 | 24 | var labelTarget = Expression.Label( context.TargetInstance.Type, "returnTarget" ); 25 | 26 | Expression returnValue = Expression.Convert( context.SourceInstance, sourceNullUnwrappedType ); 27 | 28 | if( sourceNullUnwrappedType != targetNullUnwrappedType ) 29 | { 30 | var conversionlambda = context.MapperConfiguration[ sourceNullUnwrappedType, 31 | targetNullUnwrappedType ].MappingExpression; 32 | 33 | returnValue = conversionlambda.Body 34 | .ReplaceParameter( returnValue, conversionlambda.Parameters[ 0 ].Name ); 35 | } 36 | 37 | if( context.TargetInstance.Type.IsNullable() ) 38 | { 39 | var constructor = context.TargetInstance.Type 40 | .GetConstructor( new Type[] { targetNullUnwrappedType } ); 41 | 42 | returnValue = Expression.New( constructor, returnValue ); 43 | } 44 | else 45 | { 46 | returnValue = Expression.Convert( returnValue, targetNullUnwrappedType ); 47 | } 48 | 49 | if( context.SourceInstance.Type.CanBeSetNull() ) 50 | { 51 | return Expression.Block 52 | ( 53 | Expression.IfThen 54 | ( 55 | Expression.Equal( context.SourceInstance, Expression.Constant( null, context.SourceInstance.Type ) ), 56 | Expression.Return( labelTarget, Expression.Default( context.TargetInstance.Type ) ) 57 | ), 58 | 59 | Expression.Label( labelTarget, returnValue ) 60 | ); 61 | } 62 | 63 | return returnValue; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/MappingExpressionBuilders/PrimitiveTypes/PrimitiveMapperBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using UltraMapper.Internals; 4 | 5 | namespace UltraMapper.MappingExpressionBuilders 6 | { 7 | public abstract class PrimitiveMapperBase : IMappingExpressionBuilder 8 | { 9 | public LambdaExpression GetMappingExpression( Mapping mapping ) 10 | { 11 | var source = mapping.Source; 12 | var target = mapping.Target; 13 | 14 | var context = this.GetContext( source.EntryType, target.EntryType, mapping ); 15 | var getValueExpression = this.GetValueExpression( context ); 16 | 17 | var delegateType = typeof( Func<,> ) 18 | .MakeGenericType( source.EntryType, target.EntryType ); 19 | 20 | return Expression.Lambda( delegateType, 21 | getValueExpression, context.SourceInstance ); 22 | } 23 | 24 | protected virtual MapperContext GetContext( Type sourceType, Type targetType, Mapping mapping ) 25 | { 26 | return new MapperContext( sourceType, targetType, (IMappingOptions)mapping ); 27 | } 28 | 29 | public abstract bool CanHandle( Mapping mapping ); 30 | 31 | protected abstract Expression GetValueExpression( MapperContext context ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/MappingExpressionBuilders/PrimitiveTypes/StringToEnumMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using UltraMapper.Internals; 4 | 5 | namespace UltraMapper.MappingExpressionBuilders 6 | { 7 | public class StringToEnumMapper : PrimitiveMapperBase 8 | { 9 | public override bool CanHandle( Mapping mapping ) 10 | { 11 | var source = mapping.Source; 12 | var target = mapping.Target; 13 | 14 | return source.EntryType == typeof( string ) && target.EntryType.IsEnum; 15 | } 16 | 17 | protected override Expression GetValueExpression( MapperContext context ) 18 | { 19 | var enumParseCall = Expression.Call( typeof( Enum ), nameof( Enum.Parse ), null, 20 | Expression.Constant( context.TargetInstance.Type ), context.SourceInstance, Expression.Constant( true ) ); 21 | 22 | return Expression.Convert( enumParseCall, context.TargetInstance.Type ); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/MappingExpressionBuilders/PrimitiveTypes/StructMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using UltraMapper.Internals; 4 | 5 | namespace UltraMapper.MappingExpressionBuilders 6 | { 7 | public class StructMapper : PrimitiveMapperBase 8 | { 9 | public override bool CanHandle( Mapping mapping ) 10 | { 11 | var source = mapping.Source; 12 | var target = mapping.Target; 13 | 14 | return source.EntryType.IsValueType && target.EntryType.IsValueType; 15 | } 16 | 17 | protected override Expression GetValueExpression( MapperContext context ) 18 | { 19 | return context.SourceInstance; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/MappingExpressionBuilders/ReferenceTypes/CollectionTypes/DictionaryMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Linq.Expressions; 4 | using UltraMapper.Internals; 5 | 6 | namespace UltraMapper.MappingExpressionBuilders 7 | { 8 | public class DictionaryMapper : CollectionMapper 9 | { 10 | public override bool CanHandle( Mapping mapping ) 11 | { 12 | var source = mapping.Source; 13 | var target = mapping.Target; 14 | 15 | bool sourceIsDictionary = typeof( IDictionary ).IsAssignableFrom( source.EntryType ); 16 | bool targetIsDictionary = typeof( IDictionary ).IsAssignableFrom( target.EntryType ); 17 | 18 | return sourceIsDictionary || targetIsDictionary; 19 | } 20 | 21 | protected override ReferenceMapperContext GetMapperContext( Mapping mapping ) 22 | { 23 | var source = mapping.Source.EntryType; 24 | var target = mapping.Target.EntryType; 25 | 26 | return new DictionaryMapperContext( mapping ); 27 | } 28 | 29 | protected override Expression GetExpressionBody( ReferenceMapperContext contextObj ) 30 | { 31 | var context = contextObj as DictionaryMapperContext; 32 | 33 | var addMethod = base.GetTargetCollectionInsertionMethod( context ); 34 | 35 | var keyExpression = this.GetKeyOrValueExpression( context, 36 | context.SourceCollectionElementKey, context.TargetCollectionElementKey ); 37 | 38 | var valueExpression = this.GetKeyOrValueExpression( context, 39 | context.SourceCollectionElementValue, context.TargetCollectionElementValue ); 40 | 41 | return Expression.Block 42 | ( 43 | new[] { context.Mapper, context.SourceCollectionElementKey, context.SourceCollectionElementValue, 44 | context.TargetCollectionElementKey, context.TargetCollectionElementValue }, 45 | 46 | Expression.Assign( context.Mapper, Expression.Constant( context.MapperInstance ) ), 47 | 48 | ExpressionLoops.ForEach( context.SourceInstance, 49 | context.SourceCollectionLoopingVar, Expression.Block 50 | ( 51 | Expression.Assign( context.SourceCollectionElementKey, 52 | Expression.Property( context.SourceCollectionLoopingVar, nameof( DictionaryEntry.Key ) ) ), 53 | 54 | Expression.Assign( context.SourceCollectionElementValue, 55 | Expression.Property( context.SourceCollectionLoopingVar, nameof( DictionaryEntry.Value ) ) ), 56 | 57 | keyExpression, 58 | valueExpression, 59 | 60 | Expression.Call( context.TargetInstance, addMethod, 61 | context.TargetCollectionElementKey, context.TargetCollectionElementValue ) 62 | ) ) 63 | ); 64 | } 65 | 66 | protected virtual Expression GetKeyOrValueExpression( DictionaryMapperContext context, 67 | ParameterExpression sourceParam, ParameterExpression targetParam ) 68 | { 69 | if( (sourceParam.Type.IsBuiltIn( false ) && targetParam.Type.IsBuiltIn( false )) || 70 | (!sourceParam.Type.IsClass || !targetParam.Type.IsClass) ) 71 | { 72 | var itemMapping = context.MapperConfiguration[ sourceParam.Type, 73 | targetParam.Type ].MappingExpression; 74 | 75 | var itemMappingExp = itemMapping.Body.ReplaceParameter( 76 | sourceParam, "sourceInstance" ); 77 | 78 | return Expression.Assign( targetParam, itemMappingExp ); 79 | } 80 | 81 | return base.LookUpBlock( sourceParam, targetParam, 82 | context.ReferenceTracker, context.Mapper, context ); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/MappingExpressionBuilders/ReferenceTypes/CollectionTypes/EnumerableIteratorToArrayMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using UltraMapper.Internals; 7 | using UltraMapper.Internals.ExtensionMethods; 8 | 9 | namespace UltraMapper.MappingExpressionBuilders 10 | { 11 | /// 12 | /// Unmaterialized enumerables resolve internally to the Iterator class and its subclasses. 13 | /// Unmaterialized enumerables must not read Count property to map on array. 14 | /// This mapper uses a temp List and then performs a .ToArray() 15 | /// 16 | public class EnumerableIteratorToArrayMapper : CollectionMapper 17 | { 18 | public override bool CanHandle( Mapping mapping ) 19 | { 20 | var source = mapping.Source; 21 | var target = mapping.Target; 22 | 23 | return base.CanHandle( mapping ) 24 | //&& source.EntryType?.BaseType?.Name?.StartsWith( "Iterator" ) == true 25 | && target.EntryType.IsArray; 26 | } 27 | 28 | protected override Expression GetExpressionBody( ReferenceMapperContext contextObj ) 29 | { 30 | var context = contextObj as CollectionMapperContext; 31 | 32 | var targetTempType = typeof( List<> ).MakeGenericType( context.TargetCollectionElementType ); 33 | var tempColl = Expression.Parameter( targetTempType, "tempColl" ); 34 | 35 | Type iteratorElementType = null; 36 | 37 | var sourceInstance = context.SourceInstance.Type; 38 | if( context.SourceInstance.Type.Name == "RangeIterator" ) 39 | { 40 | iteratorElementType = context.SourceInstance.Type 41 | .BaseType.GetGenericArguments().FirstOrDefault(); 42 | } 43 | else 44 | { 45 | iteratorElementType = context.SourceInstance 46 | .Type.GenericTypeArguments.LastOrDefault(); 47 | } 48 | 49 | if( iteratorElementType != null ) 50 | sourceInstance = typeof( IEnumerable<> ).MakeGenericType( iteratorElementType ); 51 | 52 | var tempMapping = context.MapperConfiguration[ sourceInstance, targetTempType ] 53 | .MappingExpression; 54 | 55 | var ctorParam = new Type[] { typeof( IEnumerable<> ) 56 | .MakeGenericType( context.TargetCollectionElementType ) }; 57 | 58 | var ctor = typeof( List<> ) 59 | .MakeGenericType( context.TargetCollectionElementType ) 60 | .GetConstructor( ctorParam ); 61 | 62 | var toArrayMethod = typeof( List<> ) 63 | .MakeGenericType( context.TargetCollectionElementType ) 64 | .GetMethod( nameof( List.ToArray ) ); 65 | 66 | return Expression.Block 67 | ( 68 | new[] { tempColl }, 69 | 70 | Expression.Assign( tempColl, Expression.New( tempColl.Type ) ), 71 | Expression.Invoke( tempMapping, context.ReferenceTracker, context.SourceInstance, tempColl ), 72 | Expression.Invoke( _debugExp, tempColl ), 73 | Expression.Assign( contextObj.TargetInstance, Expression.Call( tempColl, toArrayMethod ) ) 74 | ); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/MappingExpressionBuilders/ReferenceTypes/CollectionTypes/LinkedListMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using UltraMapper.Internals; 5 | 6 | namespace UltraMapper.MappingExpressionBuilders 7 | { 8 | public class LinkedListMapper : CollectionMapper 9 | { 10 | public override bool CanHandle( Mapping mapping ) 11 | { 12 | var source = mapping.Source; 13 | var target = mapping.Target; 14 | 15 | return base.CanHandle( mapping ) && 16 | target.EntryType.IsCollectionOfType( typeof( LinkedList<> ) ); 17 | } 18 | 19 | protected override MethodInfo GetTargetCollectionInsertionMethod( CollectionMapperContext context ) 20 | { 21 | //It is forbidden to use nameof with unbound generic types. We use 'int' just to get around that. 22 | var methodName = nameof( LinkedList.AddLast ); 23 | var methodParams = new[] { context.TargetCollectionElementType }; 24 | 25 | return context.TargetInstance.Type.GetMethod( methodName, methodParams ); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/MappingExpressionBuilders/ReferenceTypes/CollectionTypes/QueueMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using UltraMapper.Internals; 5 | using UltraMapper.Internals.ExtensionMethods; 6 | 7 | namespace UltraMapper.MappingExpressionBuilders 8 | { 9 | public class QueueMapper : CollectionMapper 10 | { 11 | public override bool CanHandle( Mapping mapping ) 12 | { 13 | var source = mapping.Source; 14 | var target = mapping.Target; 15 | 16 | return base.CanHandle( mapping ) && 17 | target.EntryType.IsCollectionOfType( typeof( Queue<> ) ); 18 | } 19 | 20 | protected override MethodInfo GetTargetCollectionInsertionMethod( CollectionMapperContext context ) 21 | { 22 | //It is forbidden to use nameof with unbound generic types. We use 'int' just to get around that. 23 | var methodName = nameof( Queue.Enqueue ); 24 | var methodParams = new[] { context.TargetCollectionElementType }; 25 | 26 | return context.TargetInstance.Type.GetMethod( methodName, methodParams ); 27 | } 28 | 29 | protected override MethodInfo GetUpdateCollectionMethod( CollectionMapperContext context ) 30 | { 31 | return typeof( LinqExtensions ).GetMethod 32 | ( 33 | nameof( LinqExtensions.UpdateQueue ), 34 | BindingFlags.Static | BindingFlags.Public 35 | ) 36 | .MakeGenericMethod 37 | ( 38 | context.SourceCollectionElementType, 39 | context.TargetCollectionElementType 40 | ); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/MappingExpressionBuilders/ReferenceTypes/CollectionTypes/ReadOnlyCollectionMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using UltraMapper.Internals; 7 | 8 | namespace UltraMapper.MappingExpressionBuilders 9 | { 10 | public class ReadOnlyCollectionMapper : CollectionMapper 11 | { 12 | public override bool CanHandle( Mapping mapping ) 13 | { 14 | var source = mapping.Source; 15 | var target = mapping.Target; 16 | 17 | return base.CanHandle( mapping ) && new Lazy( () => 18 | { 19 | bool hasTargetDefaultParameterlessCtor = target.EntryType.GetConstructor( 20 | BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null ) != null; 21 | 22 | if( hasTargetDefaultParameterlessCtor ) return false; 23 | 24 | bool hasInputCollectionCtor = target.EntryType 25 | .GetConstructors().FirstOrDefault( ctor => 26 | { 27 | var parameters = ctor.GetParameters(); 28 | if( parameters.Length != 1 ) return false; 29 | 30 | return parameters[ 0 ].ParameterType.IsEnumerable(); 31 | } ) != null; 32 | 33 | return hasInputCollectionCtor; 34 | } ).Value; 35 | } 36 | 37 | protected override Expression GetExpressionBody( ReferenceMapperContext contextObj ) 38 | { 39 | return Expression.Empty(); 40 | } 41 | 42 | protected virtual MethodInfo GetTemporaryCollectionInsertionMethod( CollectionMapperContext context ) 43 | { 44 | return this.GetTemporaryCollectionType( context ).GetMethod( nameof( List.Add ) ); 45 | } 46 | 47 | protected virtual Type GetTemporaryCollectionType( CollectionMapperContext context ) 48 | { 49 | return typeof( List<> ).MakeGenericType( context.SourceCollectionElementType ); 50 | } 51 | 52 | public override Expression GetMemberNewInstance( MemberMappingContext context, out bool isMapComplete ) 53 | { 54 | isMapComplete = false; 55 | //1. Create a new temporary collection passing source as input 56 | //2. Read items from the newly created temporary collection and add items to the target 57 | 58 | var collectionContext = (CollectionMapperContext)GetMapperContext( (Mapping)context.Options ); 59 | 60 | var tempCollectionType = this.GetTemporaryCollectionType( collectionContext ); 61 | var tempCollectionConstructorInfo = tempCollectionType.GetConstructor( Type.EmptyTypes ); 62 | var tempCollection = Expression.Parameter( tempCollectionType, "tempCollection" ); 63 | 64 | var newTempCollectionExp = Expression.New( tempCollectionConstructorInfo ); 65 | 66 | var newTargetCtor = context.TargetMember.Type.GetConstructors().First( ctor => 67 | { 68 | var parameters = ctor.GetParameters(); 69 | if( parameters.Length != 1 ) return false; 70 | 71 | return parameters[ 0 ].ParameterType.IsEnumerable(); 72 | } ); 73 | 74 | var temporaryCollectionInsertionMethod = this.GetTemporaryCollectionInsertionMethod( collectionContext ); 75 | if( collectionContext.IsTargetElementTypeBuiltIn ) 76 | { 77 | return Expression.Block 78 | ( 79 | new[] { tempCollection }, 80 | Expression.Assign( tempCollection, newTempCollectionExp ), 81 | 82 | SimpleCollectionLoop 83 | ( 84 | context.SourceMember, 85 | collectionContext.SourceCollectionElementType, 86 | tempCollection, 87 | collectionContext.TargetCollectionElementType, 88 | temporaryCollectionInsertionMethod, 89 | collectionContext.SourceCollectionLoopingVar, 90 | collectionContext.Mapper, 91 | collectionContext.ReferenceTracker, 92 | collectionContext 93 | ), 94 | 95 | Expression.New( newTargetCtor, tempCollection ) 96 | ); 97 | } 98 | 99 | return Expression.Block 100 | ( 101 | new[] { tempCollection }, 102 | 103 | Expression.Assign( tempCollection, newTempCollectionExp ), 104 | 105 | ComplexCollectionLoop 106 | ( 107 | context.SourceMember, 108 | collectionContext.SourceCollectionElementType, 109 | tempCollection, 110 | collectionContext.TargetCollectionElementType, 111 | temporaryCollectionInsertionMethod, 112 | collectionContext.SourceCollectionLoopingVar, 113 | context.ReferenceTracker, 114 | context.Mapper, 115 | collectionContext 116 | ), 117 | 118 | Expression.New( newTargetCtor, tempCollection ) 119 | ); 120 | } 121 | } 122 | } -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/MappingExpressionBuilders/ReferenceTypes/CollectionTypes/SortedSetMapper.cs: -------------------------------------------------------------------------------- 1 | //using System; 2 | //using System.Collections.Generic; 3 | //using System.Linq.Expressions; 4 | //using UltraMapper.Internals; 5 | 6 | //namespace UltraMapper.MappingExpressionBuilders 7 | //{ 8 | // //We only solve a problem starting from .NET5 9 | // //Solving reported issue: https://github.com/dotnet/runtime/issues/71643 10 | // public class SortedSetMapper : CollectionMapper 11 | // { 12 | // public override bool CanHandle( Mapping mapping ) 13 | // { 14 | // return base.CanHandle( mapping ) && 15 | // //mapping.Source.EntryType.IsCollectionOfType( typeof( SortedSet<> ) ) && 16 | // mapping.Target.EntryType == typeof( SortedSet ); 17 | // } 18 | 19 | // protected override Expression GetNewInstanceFromSourceCollection( MemberMappingContext context, CollectionMapperContext collectionContext ) 20 | // { 21 | // var targetConstructor = context.TargetMember.Type.GetConstructor( 22 | // new[] { typeof( IComparer<> ).MakeGenericType( collectionContext.TargetCollectionElementType ) } ); 23 | 24 | // if( targetConstructor == null ) return null; 25 | // return Expression.New( targetConstructor, Expression.Property( context.SourceMember, nameof( SortedSet.Comparer ) ) ); 26 | 27 | 28 | // // Expression.Constant( StringComparer.OrdinalIgnoreCase ); 29 | 30 | 31 | // } 32 | // } 33 | //} 34 | -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/MappingExpressionBuilders/ReferenceTypes/CollectionTypes/StackMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using UltraMapper.Internals; 6 | using UltraMapper.Internals.ExtensionMethods; 7 | 8 | namespace UltraMapper.MappingExpressionBuilders 9 | { 10 | /*Stack and other LIFO collections require the list to be read in reverse 11 | * to preserve order and have a specular clone */ 12 | public class StackMapper : CollectionMappingViaTempCollection 13 | { 14 | //protected override bool IsCopySourceToTempCollection => true; 15 | //protected override bool IsCopyTargetToTempCollection => true; 16 | 17 | public override bool CanHandle( Mapping mapping ) 18 | { 19 | var source = mapping.Source; 20 | var target = mapping.Target; 21 | 22 | return base.CanHandle( mapping ) && 23 | target.EntryType.IsCollectionOfType( typeof( Stack<> ) ); 24 | } 25 | 26 | protected override MethodInfo GetTempCollectionInsertionMethod( CollectionMapperContext context ) 27 | { 28 | return this.GetTargetCollectionInsertionMethod( context ); 29 | } 30 | 31 | protected override MethodInfo GetTargetCollectionInsertionMethod( CollectionMapperContext context ) 32 | { 33 | //It is forbidden to use nameof with unbound generic types. We use 'int' just to get around that. 34 | var methodName = nameof( Stack.Push ); 35 | var methodParams = new[] { context.TargetCollectionElementType }; 36 | 37 | return context.TargetInstance.Type.GetMethod( methodName, methodParams ); 38 | } 39 | 40 | protected override Type GetTemporaryCollectionType( CollectionMapperContext context ) 41 | { 42 | //by copying data in a temp stack and then in the target collection 43 | //the correct order of the items is preserved 44 | return typeof( Stack<> ).MakeGenericType( context.SourceCollectionElementType ); 45 | } 46 | 47 | protected override Expression GetNewInstanceFromSourceCollection( MemberMappingContext context, CollectionMapperContext collectionContext ) 48 | { 49 | var targetConstructor = context.TargetMember.Type.GetConstructor( 50 | new[] { typeof( IEnumerable<> ).MakeGenericType( collectionContext.TargetCollectionElementType ) } ); 51 | 52 | return Expression.New( targetConstructor, 53 | Expression.New( targetConstructor, context.SourceMember ) ); 54 | } 55 | 56 | protected override MethodInfo GetUpdateCollectionMethod( CollectionMapperContext context ) 57 | { 58 | return typeof( LinqExtensions ).GetMethod 59 | ( 60 | nameof( LinqExtensions.UpdateStack ), 61 | BindingFlags.Static | BindingFlags.Public 62 | ) 63 | .MakeGenericMethod 64 | ( 65 | context.SourceCollectionElementType, 66 | context.TargetCollectionElementType 67 | ); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/MappingExpressionBuilders/ReferenceTypes/ReferenceToStructMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using UltraMapper.Internals; 4 | 5 | namespace UltraMapper.MappingExpressionBuilders 6 | { 7 | public class ReferenceToStructMapper : ReferenceMapper 8 | { 9 | public override bool CanHandle( Mapping mapping ) 10 | { 11 | var source = mapping.Source; 12 | var target = mapping.Target; 13 | 14 | return !source.EntryType.IsBuiltIn( false ) && !target.EntryType.IsBuiltIn( false ); 15 | } 16 | 17 | public override LambdaExpression GetMappingExpression( Mapping mapping ) 18 | { 19 | var context = this.GetMapperContext( mapping ); 20 | 21 | var expression = Expression.Block 22 | ( 23 | base.GetMappingExpression( mapping ).Body 24 | .ReplaceParameter( context.Mapper, context.Mapper.Name ) 25 | .ReplaceParameter( context.ReferenceTracker, context.ReferenceTracker.Name ) 26 | .ReplaceParameter( context.SourceInstance, context.SourceInstance.Name ) 27 | .ReplaceParameter( context.TargetInstance, context.TargetInstance.Name ), 28 | 29 | context.TargetInstance 30 | ); 31 | 32 | var delegateType = typeof( UltraMapperDelegate<,> ) 33 | .MakeGenericType( context.SourceInstance.Type, context.TargetInstance.Type ); 34 | 35 | return Expression.Lambda( delegateType, expression, 36 | context.ReferenceTracker, context.SourceInstance, context.TargetInstance ); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /UltraMapper/CodeGeneration/ReferenceTrackingExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using UltraMapper.MappingExpressionBuilders; 4 | 5 | namespace UltraMapper.ReferenceTracking 6 | { 7 | public static class ReferenceTrackingExpression 8 | { 9 | private static readonly Func _referenceLookup = 10 | ( referenceTracker, sourceInstance, targetType ) => 11 | { 12 | referenceTracker.TryGetValue( sourceInstance, 13 | targetType, out object targetInstance ); 14 | 15 | return targetInstance; 16 | }; 17 | 18 | private static readonly Action _addReferenceToTracker = 19 | ( referenceTracker, sourceInstance, targetType, targetInstance ) => 20 | { 21 | referenceTracker.Add( sourceInstance, 22 | targetType, targetInstance ); 23 | }; 24 | 25 | public static Expression GetMappingExpression( 26 | ParameterExpression referenceTracker, 27 | ParameterExpression sourceMember, 28 | Expression targetMember, 29 | Expression memberAssignment, 30 | ParameterExpression mapperParam, 31 | Mapper mapper, 32 | Expression mapping, 33 | bool redirectMappingToRuntime = true ) 34 | { 35 | var refLookupExp = Expression.Call 36 | ( 37 | Expression.Constant( _referenceLookup.Target ), 38 | _referenceLookup.Method, 39 | referenceTracker, 40 | sourceMember, 41 | Expression.Constant( targetMember.Type ) 42 | ); 43 | 44 | var addRefToTrackerExp = Expression.Call 45 | ( 46 | Expression.Constant( _addReferenceToTracker.Target ), 47 | _addReferenceToTracker.Method, 48 | referenceTracker, 49 | sourceMember, 50 | Expression.Constant( targetMember.Type ), 51 | targetMember 52 | ); 53 | 54 | var mapMethod = ReferenceMapperContext.RecursiveMapMethodInfo 55 | .MakeGenericMethod( sourceMember.Type, targetMember.Type ); 56 | 57 | var trackedReference = Expression.Parameter( targetMember.Type, "trackedReference" ); 58 | 59 | var sourceNullConstant = Expression.Constant( null, sourceMember.Type ); 60 | var targetNullConstant = Expression.Constant( null, targetMember.Type ); 61 | 62 | /* SOURCE (NULL) -> TARGET = NULL 63 | * 64 | * SOURCE (NOT NULL / VALUE ALREADY TRACKED) -> TARGET (NULL) = ASSIGN TRACKED OBJECT 65 | * SOURCE (NOT NULL / VALUE ALREADY TRACKED) -> TARGET (NOT NULL) = ASSIGN TRACKED OBJECT (the priority is to map identically the source to the target) 66 | * 67 | * SOURCE (NOT NULL / VALUE UNTRACKED) -> TARGET (NULL) = ASSIGN NEW OBJECT 68 | * SOURCE (NOT NULL / VALUE UNTRACKED) -> TARGET (NOT NULL) = KEEP USING INSTANCE OR CREATE NEW INSTANCE 69 | */ 70 | 71 | ParameterExpression[] blockParameters = new ParameterExpression[ 0 ]; 72 | 73 | if( mapper != null && mapper.Config.IsReferenceTrackingEnabled ) 74 | blockParameters = new ParameterExpression[] { mapperParam, trackedReference }; 75 | 76 | else if( mapper == null ) //mapper param could be defined at higher level 77 | blockParameters = new ParameterExpression[] { trackedReference }; 78 | 79 | return Expression.Block 80 | ( 81 | blockParameters, 82 | 83 | mapper == null ? (Expression)Expression.Empty() : 84 | Expression.Assign( mapperParam, Expression.Constant( mapper ) ), 85 | 86 | Expression.IfThenElse 87 | ( 88 | Expression.Equal( sourceMember, sourceNullConstant ), 89 | Expression.Assign( targetMember, targetNullConstant ), 90 | Expression.Block 91 | ( 92 | Expression.Assign( trackedReference, 93 | Expression.Convert( refLookupExp, targetMember.Type ) ), 94 | 95 | Expression.IfThenElse 96 | ( 97 | Expression.NotEqual( trackedReference, targetNullConstant ), 98 | Expression.Assign( targetMember, trackedReference ), 99 | Expression.Block 100 | ( 101 | memberAssignment, 102 | addRefToTrackerExp, 103 | 104 | redirectMappingToRuntime ? Expression.Call( mapperParam, mapMethod, sourceMember, 105 | targetMember, referenceTracker, mapping ) : (Expression)Expression.Empty() 106 | ) 107 | ) 108 | ) 109 | ) 110 | ); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Conventions/ConventionResolvers/DefaultConventionResolver.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using UltraMapper.Conventions; 4 | using UltraMapper.Conventions.Resolvers; 5 | using UltraMapper.Internals; 6 | 7 | namespace UltraMapper 8 | { 9 | public class DefaultConventionResolver : IConventionResolver 10 | { 11 | public void MapByConvention( TypeMapping typeMapping, IEnumerable conventions ) 12 | { 13 | foreach( var convention in conventions ) 14 | { 15 | var memberPairings = convention.MapByConvention( 16 | typeMapping.Source.EntryType, typeMapping.Target.EntryType ); 17 | 18 | foreach( var memberPair in memberPairings ) 19 | { 20 | var sourceMember = memberPair.SourceMemberPath.Last(); 21 | var mappingSource = typeMapping.GetMappingSource( sourceMember, memberPair.SourceMemberPath ); 22 | 23 | var targetMember = memberPair.TargetMemberPath.Last(); 24 | var mappingTarget = typeMapping.GetMappingTarget( targetMember, memberPair.TargetMemberPath ); 25 | 26 | var mapping = new MemberMapping( typeMapping, mappingSource, mappingTarget ) 27 | { 28 | MappingResolution = MappingResolution.RESOLVED_BY_CONVENTION 29 | }; 30 | 31 | typeMapping.MemberToMemberMappings[ mappingTarget ] = mapping; 32 | } 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Conventions/ConventionResolvers/IConventionResolver.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UltraMapper.Internals; 3 | 4 | namespace UltraMapper.Conventions.Resolvers 5 | { 6 | public interface IConventionResolver 7 | { 8 | void MapByConvention( TypeMapping newTypeMapping, 9 | IEnumerable conventions ); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Conventions/MappingConventions/DefaultConvention.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using UltraMapper.Internals; 5 | 6 | namespace UltraMapper.Conventions 7 | { 8 | public class DefaultConvention : IMappingConvention 9 | { 10 | public ISourceMemberProvider SourceMemberProvider { get; set; } 11 | public ITargetMemberProvider TargetMemberProvider { get; set; } 12 | 13 | public IMatchingRulesEvaluator MatchingRulesEvaluator { get; set; } 14 | public TypeSet MatchingRules { get; set; } 15 | 16 | public DefaultConvention() 17 | { 18 | this.SourceMemberProvider = new SourceMemberProvider(); 19 | this.TargetMemberProvider = new TargetMemberProvider(); 20 | 21 | this.MatchingRules = new TypeSet( cfg => 22 | { 23 | cfg.GetOrAdd( rule => rule.IgnoreCase = true ); 24 | } ); 25 | 26 | this.MatchingRulesEvaluator = new DefaultMatchingRuleEvaluator(); 27 | } 28 | 29 | public IEnumerable MapByConvention( Type source, Type target ) 30 | { 31 | var sourceMembers = this.SourceMemberProvider.GetMembers( source ); 32 | var targetMembers = this.TargetMemberProvider.GetMembers( target ).ToList(); 33 | 34 | foreach( var sourceMember in sourceMembers ) 35 | { 36 | foreach( var targetMember in targetMembers ) 37 | { 38 | if( this.MatchingRulesEvaluator.IsMatch( sourceMember, targetMember, this.MatchingRules ) ) 39 | { 40 | yield return new MemberPair( sourceMember, targetMember ); 41 | break; //sourceMember is now mapped, jump directly to the next sourceMember 42 | } 43 | } 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Conventions/MappingConventions/IMappingConvention.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UltraMapper.Internals; 4 | 5 | namespace UltraMapper.Conventions 6 | { 7 | public interface IMappingConvention 8 | { 9 | TypeSet MatchingRules { get; set; } 10 | IMatchingRulesEvaluator MatchingRulesEvaluator { get; set; } 11 | 12 | ISourceMemberProvider SourceMemberProvider { get; set; } 13 | ITargetMemberProvider TargetMemberProvider { get; set; } 14 | 15 | IEnumerable MapByConvention( Type source, Type target ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Conventions/MappingConventions/MatchingRuleEvaluators/DefaultMatchingRuleEvaluator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | 6 | namespace UltraMapper.Conventions 7 | { 8 | /// 9 | /// Each rule implements one or more rules of type (or a derived interface). 10 | /// Rules are grouped by interface type and evaluated. 11 | /// Each group must have at least one compliant rule to validate a mapping. 12 | /// 13 | public class DefaultMatchingRuleEvaluator : IMatchingRulesEvaluator 14 | { 15 | private IEnumerable _lastMatchingRuleSet; 16 | private Dictionary> _ruleGroups 17 | = new Dictionary>(); 18 | 19 | public bool IsMatch( MemberInfo source, MemberInfo target, IEnumerable matchingRules ) 20 | { 21 | //try not to degrade performance too much by caching the last grouped set of rules 22 | if( _lastMatchingRuleSet != matchingRules ) 23 | { 24 | foreach( var rule in matchingRules ) 25 | { 26 | var ruleTypes = rule.GetType().GetInterfaces() 27 | .Where( @interface => typeof( IMatchingRule ).IsAssignableFrom( @interface ) ); 28 | 29 | foreach( var ruletype in ruleTypes ) 30 | { 31 | if( !_ruleGroups.TryGetValue( ruletype, out var rules ) ) 32 | _ruleGroups.Add( ruletype, rules = new List() ); 33 | 34 | rules.Add( rule ); 35 | } 36 | } 37 | 38 | //To avoid evaluating the same rules twice as an IMatchingRule (the base matching rule interface), 39 | //we remove from the IMatchingRule group the rules already being part of other groups as derived types. 40 | if( _ruleGroups.TryGetValue( typeof( IMatchingRule ), out var baseInterfaceRules ) ) 41 | { 42 | foreach( var group in _ruleGroups ) 43 | { 44 | if( group.Key != typeof( IMatchingRule ) ) 45 | { 46 | foreach( var rule in group.Value ) 47 | baseInterfaceRules.Remove( rule ); 48 | } 49 | } 50 | 51 | if( baseInterfaceRules.Count == 0 ) 52 | _ruleGroups.Remove( typeof( IMatchingRule ) ); 53 | } 54 | 55 | _lastMatchingRuleSet = matchingRules; 56 | } 57 | 58 | return _ruleGroups.All( ruleGroup => 59 | { 60 | var relevantRules = ruleGroup.Value.Where( rule => rule.CanHandle( source, target ) ); 61 | return relevantRules.Any( rule => rule.IsCompliant( source, target ) ); 62 | } ); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Conventions/MappingConventions/MatchingRuleEvaluators/IMatchingRulesEvaluator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Reflection; 3 | 4 | namespace UltraMapper.Conventions 5 | { 6 | public interface IMatchingRulesEvaluator 7 | { 8 | bool IsMatch( MemberInfo source, MemberInfo target, IEnumerable matchingRules ); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Conventions/MappingConventions/MatchingRules/IMatchingRule.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace UltraMapper.Conventions 4 | { 5 | public interface IMatchingRule 6 | { 7 | bool CanHandle( MemberInfo source, MemberInfo target ); 8 | bool IsCompliant( MemberInfo source, MemberInfo target ); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Conventions/MappingConventions/MatchingRules/NameMatching/ExactNameMatching.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace UltraMapper.Conventions 5 | { 6 | /// 7 | /// Two members match if they have the same name. 8 | /// Name case can be optionally ignored. 9 | /// 10 | public class ExactNameMatching : INameMatchingRule 11 | { 12 | public bool IgnoreCase { get; set; } = true; 13 | 14 | public bool CanHandle( MemberInfo source, MemberInfo target ) 15 | { 16 | return !(source is MethodInfo) && !(target is MethodInfo); 17 | } 18 | 19 | public bool IsCompliant( MemberInfo source, MemberInfo target ) 20 | { 21 | var comparisonType = this.IgnoreCase ? 22 | StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; 23 | 24 | return source.Name.Equals( target.Name, comparisonType ); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Conventions/MappingConventions/MatchingRules/NameMatching/INameMatchingRule.cs: -------------------------------------------------------------------------------- 1 | using UltraMapper.Conventions; 2 | 3 | namespace UltraMapper.Conventions 4 | { 5 | public interface INameMatchingRule : IMatchingRule 6 | { 7 | bool IgnoreCase { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Conventions/MappingConventions/MatchingRules/NameMatching/MethodNameMatching.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Reflection; 4 | using UltraMapper.Internals; 5 | 6 | namespace UltraMapper.Conventions 7 | { 8 | public class MethodNameMatching : INameMatchingRule 9 | { 10 | public bool IgnoreCase { get; set; } 11 | 12 | public string[] GetMethodPrefixes { get; set; } 13 | public string[] SetMethodPrefixes { get; set; } 14 | 15 | public MethodNameMatching() 16 | { 17 | this.GetMethodPrefixes = new string[] { "Get_", "Get" }; 18 | this.SetMethodPrefixes = new string[] { "Set_", "Set" }; 19 | } 20 | 21 | public bool CanHandle( MemberInfo source, MemberInfo target ) 22 | { 23 | return source is MethodInfo && target is MethodInfo; 24 | } 25 | 26 | public bool IsCompliant( MemberInfo source, MemberInfo target ) 27 | { 28 | var comparisonType = this.IgnoreCase ? 29 | StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; 30 | 31 | var sourceName = this.CleanAffix( source.Name, this.GetMethodPrefixes ); 32 | var targetName = this.CleanAffix( target.Name, this.SetMethodPrefixes ); 33 | 34 | return sourceName.Equals( targetName, comparisonType ); 35 | } 36 | 37 | private string CleanAffix( string input, string[] affixes ) 38 | { 39 | foreach( var affix in affixes ) 40 | { 41 | if( input.StartsWith( affix, this.IgnoreCase, CultureInfo.InvariantCulture ) ) 42 | return input.Remove( 0, affix.Length ); 43 | } 44 | 45 | return input; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Conventions/MappingConventions/MatchingRules/NameMatching/PrefixMatching.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | 5 | namespace UltraMapper.Conventions 6 | { 7 | /// 8 | /// Two members match if (sourceName == prefix + targetName) or (targetName == prefix + sourceName). 9 | /// 10 | public class PrefixMatching : INameMatchingRule 11 | { 12 | public bool IgnoreCase { get; set; } 13 | public string[] Prefixes { get; set; } 14 | 15 | public PrefixMatching() 16 | : this( new string[] { "Dto", "DataTransferObject" } ) { } 17 | 18 | public PrefixMatching( params string[] prefixes ) 19 | { 20 | this.IgnoreCase = true; 21 | this.Prefixes = prefixes; 22 | } 23 | 24 | public bool CanHandle( MemberInfo source, MemberInfo target ) 25 | { 26 | return !(source is MethodInfo) && !(target is MethodInfo); 27 | } 28 | 29 | public bool IsCompliant( MemberInfo source, MemberInfo target ) 30 | { 31 | var comparisonType = this.IgnoreCase ? 32 | StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; 33 | 34 | return this.Prefixes.Any( prefix => 35 | source.Name.Equals( prefix + target.Name, comparisonType ) || 36 | target.Name.Equals( prefix + source.Name, comparisonType ) ); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Conventions/MappingConventions/MatchingRules/NameMatching/SuffixMatching.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | 5 | namespace UltraMapper.Conventions 6 | { 7 | /// 8 | /// Two members match if (sourceName == targetName + suffix) or (targetName == sourceName + suffix). 9 | /// 10 | public class SuffixMatching : INameMatchingRule 11 | { 12 | public bool IgnoreCase { get; set; } 13 | public string[] Suffixes { get; set; } 14 | 15 | public SuffixMatching() 16 | : this( new string[] { "Dto", "DataTransferObject" } ) { } 17 | 18 | public SuffixMatching( params string[] suffixes ) 19 | { 20 | this.IgnoreCase = true; 21 | this.Suffixes = suffixes; 22 | } 23 | 24 | public bool CanHandle( MemberInfo source, MemberInfo target ) 25 | { 26 | return !(source is MethodInfo) && !(target is MethodInfo); 27 | } 28 | 29 | public bool IsCompliant( MemberInfo source, MemberInfo target ) 30 | { 31 | var comparisonType = this.IgnoreCase ? 32 | StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; 33 | 34 | return this.Suffixes.Any( suffix => 35 | source.Name.Equals( target.Name + suffix, comparisonType ) || 36 | target.Name.Equals( source.Name + suffix, comparisonType ) ); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Conventions/MappingConventions/MatchingRules/TypeMatching/ITypeMatchingRule.cs: -------------------------------------------------------------------------------- 1 | using UltraMapper.Conventions; 2 | 3 | namespace UltraMapper.Conventions 4 | { 5 | public interface ITypeMatchingRule : IMatchingRule 6 | { 7 | 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Conventions/MappingConventions/MatchingRules/TypeMatching/MethodTypeMatching.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Reflection; 4 | using UltraMapper.Internals; 5 | 6 | namespace UltraMapper.Conventions 7 | { 8 | public class MethodTypeMatching : ITypeMatchingRule 9 | { 10 | public bool CanHandle( MemberInfo source, MemberInfo target ) 11 | { 12 | return source is MethodInfo && target is MethodInfo; 13 | } 14 | 15 | public bool IsCompliant( MemberInfo source, MemberInfo target ) 16 | { 17 | if( source is MethodInfo smi && !smi.IsGetterMethod() ) 18 | return false; 19 | 20 | if( target is MethodInfo tmi && !tmi.IsSetterMethod() ) 21 | return false; 22 | 23 | return source.GetMemberType() == target.GetMemberType(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Conventions/MappingConventions/MatchingRules/TypeMatching/TypeMatching.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using UltraMapper.Internals; 4 | 5 | namespace UltraMapper.Conventions 6 | { 7 | /// 8 | /// Two properties match if the source type is of the same type 9 | /// or (optionally) implicitly/explicitly convertible to the target type. 10 | /// 11 | public class TypeMatchingRule : ITypeMatchingRule 12 | { 13 | public bool AllowImplicitConversions { get; set; } = true; 14 | public bool AllowExplicitConversions { get; set; } = true; 15 | //public bool AllowNullableUnwrappings { get; set; } = true; 16 | 17 | public bool CanHandle( MemberInfo source, MemberInfo target ) 18 | { 19 | return !(source is MethodInfo) && !(target is MethodInfo); 20 | } 21 | 22 | public bool IsCompliant( MemberInfo source, MemberInfo target ) 23 | { 24 | var sourceType = source.GetMemberType(); 25 | var targetType = target.GetMemberType(); 26 | 27 | var isCompliant = this.CanHandle( sourceType, targetType ); 28 | //if( !isCompliant && this.AllowNullableUnwrappings ) 29 | //{ 30 | // isCompliant = targetType.IsAssignableFrom( sourceType ); 31 | 32 | // if( sourceType.IsNullable() && !targetType.IsNullable() ) 33 | // { 34 | // var underlyingSourceType = Nullable.GetUnderlyingType( sourceType ); 35 | // isCompliant = this.CanHandle( underlyingSourceType, targetType ); 36 | // } 37 | //} 38 | 39 | return isCompliant; 40 | } 41 | 42 | private bool CanHandle( Type source, Type target ) 43 | { 44 | //PrimitiveType -> Nullable always possible. No flag to disable that. 45 | var primitiveToNullablePrimitive = new Lazy( () => 46 | { 47 | return !source.IsNullable() && target.IsNullable() 48 | && target.IsAssignableFrom( source ); 49 | } ); 50 | 51 | return source == target || primitiveToNullablePrimitive.Value || 52 | this.AllowImplicitConversions && source.IsImplicitlyConvertibleTo( target ) || 53 | this.AllowExplicitConversions && source.IsExplicitlyConvertibleTo( target ); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Conventions/MappingConventions/MemberProviders/IMemberProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | 5 | namespace UltraMapper.Conventions 6 | { 7 | public interface IMemberProvider 8 | { 9 | bool IgnoreFields { get; set; } 10 | bool IgnoreProperties { get; set; } 11 | bool IgnoreMethods { get; set; } 12 | bool IgnoreNonPublicMembers { get; set; } 13 | 14 | IEnumerable GetMembers( Type type ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Conventions/MappingConventions/MemberProviders/SourceMemberProviders/ISourceMemberProvider.cs: -------------------------------------------------------------------------------- 1 | using UltraMapper.Conventions; 2 | 3 | namespace UltraMapper.Conventions 4 | { 5 | public interface ISourceMemberProvider : IMemberProvider 6 | { 7 | bool AllowGetterMethodsOnly { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Conventions/MappingConventions/MemberProviders/SourceMemberProviders/SourceMemberProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using UltraMapper.Internals; 6 | using System.Runtime.CompilerServices; 7 | 8 | namespace UltraMapper.Conventions 9 | { 10 | public class SourceMemberProvider : ISourceMemberProvider 11 | { 12 | public bool IgnoreFields { get; set; } = true; 13 | public bool IgnoreProperties { get; set; } = false; 14 | public bool IgnoreMethods { get; set; } = false; 15 | public bool IgnoreNonPublicMembers { get; set; } = true; 16 | 17 | /// 18 | /// Sets BindingFlags.FlattenHierarchy 19 | /// 20 | public bool FlattenHierarchy { get; set; } = true; 21 | 22 | /// 23 | /// Sets BindingFlags.DeclaredOnly 24 | /// 25 | public bool DeclaredOnly { get; set; } = false; 26 | 27 | /// 28 | /// Consider only methods that are parameterless and return void 29 | /// 30 | public bool AllowGetterMethodsOnly { get; set; } = true; 31 | 32 | public IEnumerable GetMembers( Type type ) 33 | { 34 | var bindingFlags = BindingFlags.Instance | BindingFlags.Public; 35 | 36 | if( this.FlattenHierarchy ) bindingFlags |= BindingFlags.FlattenHierarchy; 37 | if( this.DeclaredOnly ) bindingFlags |= BindingFlags.DeclaredOnly; 38 | 39 | if( !this.IgnoreNonPublicMembers ) bindingFlags |= BindingFlags.NonPublic; 40 | 41 | if( !this.IgnoreFields ) 42 | { 43 | //- In case of an interface type we do nothing special since interfaces do not support fields 44 | //- Notice that we don't check for readonly fields: we only need to read from the source! 45 | 46 | var sourceFields = type.GetFields( bindingFlags ) 47 | .Select( field => field ); 48 | 49 | foreach( var field in sourceFields ) yield return field; 50 | } 51 | 52 | if( !this.IgnoreProperties ) 53 | { 54 | var sourceProperties = type.GetProperties( bindingFlags ) 55 | .Select( property => property ); 56 | 57 | if( type.IsInterface ) 58 | { 59 | sourceProperties = sourceProperties.Concat( type.GetInterfaces() 60 | .SelectMany( i => i.GetProperties( bindingFlags ) ) ); 61 | } 62 | 63 | sourceProperties = sourceProperties.Where( 64 | p => p.CanRead && p.GetIndexParameters().Length == 0 ); //no indexed properties 65 | 66 | foreach( var property in sourceProperties ) yield return property; 67 | } 68 | 69 | if( !this.IgnoreMethods ) 70 | { 71 | var sourceMethods = type.GetMethods( bindingFlags ) 72 | .Where( method => method.GetCustomAttribute() == null ); 73 | 74 | if( type.IsInterface ) 75 | { 76 | sourceMethods = sourceMethods.Concat( type.GetInterfaces() 77 | .SelectMany( i => i.GetMethods( bindingFlags ) ) ); 78 | } 79 | 80 | if( this.AllowGetterMethodsOnly ) 81 | sourceMethods = sourceMethods.Where( method => method.IsGetterMethod() ); 82 | 83 | foreach( var method in sourceMethods ) yield return method; 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Conventions/MappingConventions/MemberProviders/TargetMemberProviders/ITargetMemberProvider.cs: -------------------------------------------------------------------------------- 1 | using UltraMapper.Conventions; 2 | 3 | namespace UltraMapper.Conventions 4 | { 5 | public interface ITargetMemberProvider : IMemberProvider 6 | { 7 | bool AllowGetterOrSetterMethodsOnly { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Conventions/MappingConventions/MemberProviders/TargetMemberProviders/TargetMemberProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Runtime.CompilerServices; 6 | using UltraMapper.Internals; 7 | 8 | namespace UltraMapper.Conventions 9 | { 10 | public class TargetMemberProvider : ITargetMemberProvider 11 | { 12 | public bool IgnoreFields { get; set; } = true; 13 | public bool IgnoreProperties { get; set; } = false; 14 | public bool IgnoreMethods { get; set; } = false; 15 | public bool IgnoreNonPublicMembers { get; set; } = true; 16 | 17 | /// 18 | /// Sets BindingFlags.FlattenHierarchy 19 | /// 20 | public bool FlattenHierarchy { get; set; } = true; 21 | 22 | /// 23 | /// Sets BindingFlags.DeclaredOnly 24 | /// 25 | public bool DeclaredOnly { get; set; } = false; 26 | 27 | /// 28 | /// Consider only methods that return void and take as input exactly one parameter (getters or getter-like methods); 29 | /// Or methods that return anything but void and take exactly one parameter as input (setters or setters-like methods). 30 | /// 31 | public bool AllowGetterOrSetterMethodsOnly { get; set; } = true; 32 | 33 | /// 34 | /// Allows to write on readonly fields and to set getter only properties 35 | /// (since the compiler generates readonly backingfields for getter only properties). 36 | /// 37 | /// ExpressionTrees, unlike reflection, enforce C# language and thus does not allow to write on readonly fields. 38 | /// https://visualstudio.uservoice.com/forums/121579-visual-studio-ide/suggestions/2727812-allow-expression-assign-to-set-readonly-struct-f 39 | /// 40 | /// This option is ready in case this is fixed by Microsoft. 41 | /// 42 | public readonly bool IgnoreReadonlyFields = true; 43 | 44 | public IEnumerable GetMembers( Type type ) 45 | { 46 | var bindingFlags = BindingFlags.Instance | BindingFlags.Public; 47 | 48 | if( this.FlattenHierarchy ) bindingFlags |= BindingFlags.FlattenHierarchy; 49 | if( this.DeclaredOnly ) bindingFlags |= BindingFlags.DeclaredOnly; 50 | 51 | if( !this.IgnoreNonPublicMembers ) bindingFlags |= BindingFlags.NonPublic; 52 | 53 | if( !this.IgnoreFields ) 54 | { 55 | //- In case of an interface type we do nothing special since interfaces do not support fields 56 | 57 | var targetFields = type.GetFields( bindingFlags ) 58 | .Select( field => field ); 59 | 60 | if( IgnoreReadonlyFields ) 61 | targetFields = targetFields.Where( field => !field.IsInitOnly ); 62 | 63 | foreach( var field in targetFields ) 64 | yield return field; 65 | } 66 | 67 | if( !this.IgnoreProperties ) 68 | { 69 | var targetProperties = type.GetProperties( bindingFlags ) 70 | .Select( property => property ); 71 | 72 | if( type.IsInterface ) 73 | { 74 | targetProperties = targetProperties.Concat( type.GetInterfaces() 75 | .SelectMany( i => i.GetProperties( bindingFlags ) ) ); 76 | } 77 | 78 | targetProperties = targetProperties.Where( property => 79 | property.CanWrite && property.GetSetMethod() != null 80 | && property.GetIndexParameters().Length == 0 ); //no indexed properties 81 | 82 | foreach( var property in targetProperties ) 83 | yield return property; 84 | } 85 | 86 | if( !this.IgnoreMethods ) 87 | { 88 | var targetMethods = type.GetMethods( bindingFlags ) 89 | .Where( method => method.GetCustomAttribute() == null ); 90 | 91 | if( type.IsInterface ) 92 | { 93 | targetMethods = targetMethods.Concat( type.GetInterfaces() 94 | .SelectMany( i => i.GetMethods( bindingFlags ) ) ); 95 | } 96 | 97 | if( this.AllowGetterOrSetterMethodsOnly ) 98 | { 99 | targetMethods = targetMethods.Where( method => 100 | method.IsGetterMethod() || method.IsSetterMethod() ); 101 | } 102 | 103 | foreach( var method in targetMethods ) 104 | yield return method; 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Conventions/MappingConventions/StringSplitting/IStringSplitter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace UltraMapper.Conventions 4 | { 5 | public interface IStringSplitter 6 | { 7 | IEnumerable Split( string str ); 8 | } 9 | } -------------------------------------------------------------------------------- /UltraMapper/Configuration/Conventions/MappingConventions/StringSplitting/SplittingRules/IStringSplittingRule.cs: -------------------------------------------------------------------------------- 1 | namespace UltraMapper.Conventions 2 | { 3 | public interface IStringSplittingRule 4 | { 5 | /// 6 | /// Set whether remove or keep the splitting char when it is found in the string. 7 | /// 8 | bool RemoveSplitChar { get; } 9 | 10 | /// 11 | /// Identifies the splitting char 12 | /// 13 | /// The char to check 14 | /// True if the char is a splitting char. False otherwise. 15 | bool IsSplitChar( char c ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Conventions/MappingConventions/StringSplitting/SplittingRules/RelayStringSplittingRule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace UltraMapper.Conventions 4 | { 5 | public class RelayStringSplittingRule : IStringSplittingRule 6 | { 7 | private readonly Func _splittingRule; 8 | 9 | public bool RemoveSplitChar { get; private set; } 10 | 11 | public bool IsSplitChar( char ch ) => _splittingRule( ch ); 12 | 13 | public RelayStringSplittingRule( Func splittingRule, bool removeSplitChar ) 14 | { 15 | _splittingRule = splittingRule; 16 | this.RemoveSplitChar = removeSplitChar; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Conventions/MappingConventions/StringSplitting/SplittingRules/StringSplittingRules.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace UltraMapper.Conventions 4 | { 5 | public static class StringSplittingRules 6 | { 7 | /// 8 | /// Informs to split if an upper case character is encountered 9 | /// 10 | public static IStringSplittingRule PascalCase = 11 | new RelayStringSplittingRule( c => Char.IsUpper( c ), removeSplitChar: false ); 12 | 13 | /// 14 | /// Informs the caller to split if an underscore character is encountered. 15 | /// The underscore itself is not considered part of the split. 16 | /// 17 | public static IStringSplittingRule SnakeCase = 18 | new RelayStringSplittingRule( c => c == '_', removeSplitChar: true ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Conventions/MappingConventions/StringSplitting/StringSplitter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace UltraMapper.Conventions 5 | { 6 | public sealed class StringSplitter : IStringSplitter 7 | { 8 | public readonly IStringSplittingRule SplittingRule; 9 | 10 | public StringSplitter( IStringSplittingRule splittingRule ) 11 | { 12 | this.SplittingRule = splittingRule; 13 | } 14 | 15 | public IEnumerable Split( string str ) 16 | { 17 | if( String.IsNullOrEmpty( str ) ) yield break; 18 | 19 | int removeCharOffset = this.SplittingRule.RemoveSplitChar ? 1 : 0; 20 | 21 | int lastSplit = 0; 22 | for( int i = 1; i < str.Length; i++ ) 23 | { 24 | if( this.SplittingRule.IsSplitChar( str[ i ] ) ) 25 | { 26 | yield return str.Substring( lastSplit, i - lastSplit ); 27 | lastSplit = i + removeCharOffset; 28 | } 29 | } 30 | 31 | if( lastSplit <= str.Length - 1 ) 32 | yield return str.Substring( lastSplit, str.Length - lastSplit ); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/GeneratedExpressionCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Text; 5 | 6 | namespace UltraMapper.Internals 7 | { 8 | public class GeneratedExpressionCache 9 | { 10 | private struct TypePairWithOptions 11 | { 12 | private static readonly MappingOptionsComparer _mappingOptionsComparer 13 | = new MappingOptionsComparer(); 14 | 15 | public readonly Type SourceType; 16 | public readonly Type TargetType; 17 | public readonly IMappingOptions MappingOptions; 18 | 19 | private string _toString; 20 | private int? _hashcode; 21 | 22 | public TypePairWithOptions( Type source, Type target, IMappingOptions mappingOptions = null ) 23 | { 24 | this.SourceType = source; 25 | this.TargetType = target; 26 | this.MappingOptions = mappingOptions; 27 | 28 | _toString = null; 29 | _hashcode = null; 30 | } 31 | 32 | public override bool Equals( object obj ) 33 | { 34 | if( obj is TypePairWithOptions typePair ) 35 | { 36 | return this.SourceType.Equals( typePair.SourceType ) && 37 | this.TargetType.Equals( typePair.TargetType ) 38 | && _mappingOptionsComparer.Equals( this.MappingOptions, typePair.MappingOptions ); 39 | } 40 | 41 | return false; 42 | } 43 | 44 | public override int GetHashCode() 45 | { 46 | if( _hashcode == null ) 47 | { 48 | _hashcode = this.SourceType.GetHashCode() 49 | ^ this.TargetType.GetHashCode() 50 | ^ _mappingOptionsComparer.GetHashCode( this.MappingOptions ); 51 | } 52 | 53 | return _hashcode.Value; 54 | } 55 | 56 | public static bool operator !=( TypePairWithOptions obj1, TypePairWithOptions obj2 ) 57 | { 58 | return !(obj1 == obj2); 59 | } 60 | 61 | public static bool operator ==( TypePairWithOptions obj1, TypePairWithOptions obj2 ) 62 | { 63 | return obj1.Equals( obj2 ); 64 | } 65 | 66 | public override string ToString() 67 | { 68 | if( _toString == null ) 69 | { 70 | StringBuilder sb = new StringBuilder(); 71 | 72 | string sourceTypeName = this.SourceType.GetPrettifiedName(); 73 | string targetTypeName = this.TargetType.GetPrettifiedName(); 74 | 75 | sb.Append( $"[{sourceTypeName}, {targetTypeName}]" ); 76 | sb.AppendFormat( " With Options: " ); 77 | sb.AppendFormat( " {0} = '{1}' ", nameof( IMappingOptions.ReferenceBehavior ), this.MappingOptions?.ReferenceBehavior ); 78 | sb.AppendFormat( " {0} = '{1}' ", nameof( IMappingOptions.CollectionBehavior ), this.MappingOptions?.CollectionBehavior ); 79 | 80 | _toString = sb.ToString(); 81 | } 82 | 83 | return _toString; 84 | } 85 | } 86 | 87 | private readonly Dictionary _cache = 88 | new Dictionary(); 89 | 90 | public LambdaExpression Get( Type source, Type target, IMappingOptions options ) 91 | { 92 | var key = new TypePairWithOptions( source, target, options ); 93 | _cache.TryGetValue( key, out var value ); 94 | return value; 95 | } 96 | 97 | public void Add( Type source, Type target, IMappingOptions options, LambdaExpression mappingExpression ) 98 | { 99 | var key = new TypePairWithOptions( source, target, options ); 100 | _cache.Add( key, mappingExpression ); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Inheritance/ConfigInheritanceNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UltraMapper.Internals; 3 | 4 | namespace UltraMapper.Config 5 | { 6 | internal sealed class ConfigInheritanceNode 7 | { 8 | public List Children { get; set; } 9 | public ConfigInheritanceNode Parent { get; set; } 10 | public TypeMapping Item { get; private set; } 11 | 12 | public ConfigInheritanceNode( TypeMapping item ) 13 | { 14 | this.Children = new List(); 15 | this.Item = item; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Mapping/IMapping.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using UltraMapper.MappingExpressionBuilders; 4 | 5 | namespace UltraMapper.Internals 6 | { 7 | public interface IMapping 8 | { 9 | IMappingSource Source { get; } 10 | IMappingTarget Target { get; } 11 | IMappingExpressionBuilder Mapper { get; } 12 | UltraMapperDelegate MappingFunc { get; } 13 | LambdaExpression MappingExpression { get; } 14 | } 15 | } -------------------------------------------------------------------------------- /UltraMapper/Configuration/Mapping/Mapping.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using UltraMapper.MappingExpressionBuilders; 5 | 6 | namespace UltraMapper.Internals 7 | { 8 | public abstract class Mapping : IMapping 9 | { 10 | public readonly Configuration GlobalConfig; 11 | 12 | public IMappingSource Source { get; } 13 | public IMappingTarget Target { get; } 14 | 15 | public MappingResolution MappingResolution { get; internal set; } 16 | public abstract LambdaExpression CustomConverter { get; set; } 17 | 18 | private IMappingExpressionBuilder _mapper; 19 | public IMappingExpressionBuilder Mapper 20 | { 21 | get 22 | { 23 | if( _mapper == null ) 24 | { 25 | _mapper = GlobalConfig.Mappers.FirstOrDefault( 26 | mapper => mapper.CanHandle( this ) ); 27 | 28 | if( _mapper == null && this.CustomConverter == null ) 29 | { 30 | string sourceTypeName = this.Source.EntryType.GetPrettifiedName(); 31 | string targetTypeName = this.Target.EntryType.GetPrettifiedName(); 32 | 33 | throw new Exception( $"No object mapper can handle [{sourceTypeName} -> {targetTypeName}]" ); 34 | } 35 | } 36 | 37 | return _mapper; 38 | } 39 | } 40 | 41 | private LambdaExpression _mappingExpression; 42 | public virtual LambdaExpression MappingExpression 43 | { 44 | get 45 | { 46 | if( this.CustomConverter != null ) 47 | return this.CustomConverter; 48 | 49 | if( _mappingExpression != null ) 50 | return _mappingExpression; 51 | 52 | _mappingExpression = GlobalConfig.ExpCache.Get( this.Source.EntryType, 53 | this.Target.EntryType, (IMappingOptions)this ); 54 | 55 | if( _mappingExpression == null ) 56 | { 57 | _mappingExpression = this.Mapper.GetMappingExpression( this ); 58 | 59 | GlobalConfig.ExpCache.Add( this.Source.EntryType, 60 | this.Target.EntryType, (IMappingOptions)this, _mappingExpression ); 61 | } 62 | 63 | return _mappingExpression; 64 | } 65 | } 66 | 67 | private UltraMapperDelegate _mappingFunc; 68 | public UltraMapperDelegate MappingFunc 69 | { 70 | get 71 | { 72 | if( _mappingFunc != null ) return _mappingFunc; 73 | 74 | return _mappingFunc = MappingExpressionBuilder.GetMappingEntryPoint( 75 | this.Source.EntryType, this.Target.EntryType, this.MappingExpression ); 76 | } 77 | } 78 | 79 | //protected Mapping( Configuration config, Type sourceType, Type targetType ) 80 | //{ 81 | // this.GlobalConfig = config; 82 | 83 | // this.Source = new MappingSource( sourceType ); 84 | // this.Target = new MappingTarget( targetType ); 85 | //} 86 | 87 | protected Mapping( Configuration config, IMappingSource source, IMappingTarget target ) 88 | { 89 | GlobalConfig = config; 90 | 91 | this.Source = source; 92 | this.Target = target; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Mapping/MappingPoints/IMappingPoint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace UltraMapper.Internals 4 | { 5 | public interface IMappingPoint 6 | { 7 | Type EntryType { get; } 8 | Type ReturnType { get; } 9 | 10 | Type MemberType { get; } 11 | MemberAccessPath MemberAccessPath { get; } 12 | bool Ignore { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Mapping/MappingPoints/MappingPoint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | 5 | namespace UltraMapper.Internals 6 | { 7 | public abstract class MappingPoint : IMappingPoint 8 | { 9 | private string _toString; 10 | 11 | public bool Ignore { get; set; } 12 | public MemberAccessPath MemberAccessPath { get; } 13 | public readonly MemberInfo MemberInfo; 14 | public Type MemberType { get; } 15 | 16 | public Type EntryType => this.MemberAccessPath.EntryInstance; 17 | public Type ReturnType => this.MemberAccessPath.ReturnType; 18 | 19 | public MappingPoint( MemberAccessPath memberAccessPath ) 20 | { 21 | this.MemberAccessPath = memberAccessPath; 22 | this.MemberInfo = memberAccessPath.LastOrDefault() ?? memberAccessPath.EntryInstance; 23 | this.MemberType = this.MemberInfo.GetMemberType(); 24 | } 25 | 26 | public override bool Equals( object obj ) 27 | { 28 | if( obj is MappingPoint mp ) 29 | { 30 | return this.MemberInfo.Equals( mp.MemberInfo ) && 31 | this.MemberAccessPath.EntryInstance.Equals( mp.MemberAccessPath.EntryInstance ) && 32 | this.MemberAccessPath.ReturnType.Equals( mp.MemberAccessPath.ReturnType ); 33 | } 34 | 35 | return false; 36 | } 37 | 38 | public override int GetHashCode() 39 | { 40 | return this.MemberInfo.GetHashCode() ^ 41 | this.MemberAccessPath.EntryInstance?.GetHashCode() ?? 0 ^ 42 | this.MemberAccessPath.ReturnType?.GetHashCode() ?? 0; 43 | } 44 | 45 | public override string ToString() 46 | { 47 | if( _toString == null ) 48 | { 49 | string typeName = this.MemberType.GetPrettifiedName(); 50 | _toString = this.MemberInfo == this.MemberType ? typeName 51 | : $"{typeName} {this.MemberInfo.Name}"; 52 | } 53 | 54 | return _toString; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Mapping/MappingPoints/MappingSource/IMappingSource.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace UltraMapper.Internals 4 | { 5 | public interface IMappingSource : IMappingPoint 6 | { 7 | LambdaExpression ValueGetter { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Mapping/MappingPoints/MappingSource/MappingSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | 5 | namespace UltraMapper.Internals 6 | { 7 | public sealed class MappingSource : MappingPoint, IMappingSource 8 | { 9 | public LambdaExpression ValueGetter { get; } 10 | 11 | public MappingSource( Type type ) 12 | : this( new MemberAccessPath( type ) ) { } 13 | 14 | public MappingSource( MemberInfo memberInfo ) 15 | : this( new MemberAccessPath( memberInfo.DeclaringType ?? (Type)memberInfo, memberInfo ) ) { } 16 | 17 | public MappingSource( MemberAccessPath memberGetter ) 18 | : base( memberGetter ) 19 | { 20 | this.ValueGetter = this.MemberAccessPath.Count == 1 ? 21 | this.MemberAccessPath.GetGetterExp() : 22 | this.MemberAccessPath.GetGetterExpWithNullChecks(); 23 | } 24 | 25 | public MappingSource( LambdaExpression memberGetter ) 26 | : base( memberGetter.GetMemberAccessPath() ) 27 | { 28 | this.ValueGetter = this.MemberAccessPath.Count == 1 ? memberGetter : 29 | this.MemberAccessPath.GetGetterExpWithNullChecks(); 30 | } 31 | 32 | public MappingSource( Expression memberGetter ) 33 | : base( memberGetter.GetMemberAccessPath() ) 34 | { 35 | this.ValueGetter = this.MemberAccessPath.Count == 1 ? 36 | this.MemberAccessPath.GetGetterExp() : 37 | this.MemberAccessPath.GetGetterExpWithNullChecks(); 38 | } 39 | } 40 | 41 | public sealed class MappingSource : MappingPoint, IMappingSource 42 | { 43 | public LambdaExpression ValueGetter { get; } 44 | 45 | public MappingSource( Expression> memberGetter ) 46 | : base( memberGetter.GetMemberAccessPath() ) 47 | { 48 | this.ValueGetter = this.MemberAccessPath.Count == 1 ? memberGetter : 49 | this.MemberAccessPath.GetGetterExpWithNullChecks(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Mapping/MappingPoints/MappingTarget/IMappingTarget.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace UltraMapper.Internals 4 | { 5 | public interface IMappingTarget : IMappingPoint 6 | { 7 | LambdaExpression ValueGetter { get; } 8 | LambdaExpression ValueSetter { get; } 9 | LambdaExpression CustomConstructor { get; } 10 | } 11 | } -------------------------------------------------------------------------------- /UltraMapper/Configuration/Mapping/MappingPoints/MappingTarget/MappingTarget.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | 5 | namespace UltraMapper.Internals 6 | { 7 | public sealed class MappingTarget : MappingPoint, IMappingTarget 8 | { 9 | public LambdaExpression ValueGetter { get; } 10 | public LambdaExpression ValueSetter { get; } 11 | 12 | public LambdaExpression CustomConstructor { get; set; } 13 | 14 | public MappingTarget( Type type ) 15 | : this( new MemberAccessPath( type ) ) { } 16 | 17 | public MappingTarget( MemberInfo memberInfo ) 18 | : this( new MemberAccessPath( memberInfo.DeclaringType, memberInfo ), null ) { } 19 | 20 | public MappingTarget( MemberAccessPath memberSetter, MemberAccessPath memberGetter = null ) 21 | : base( memberSetter ) 22 | { 23 | this.ValueSetter = memberSetter.Count > 1 ? 24 | memberSetter.GetSetterExpWithNullInstancesInstantiation() : 25 | memberSetter.GetSetterExp(); 26 | 27 | try 28 | { 29 | //build the getter from the getter member path if provided; 30 | //try to figure out the getter from the setter member path otherwise 31 | //(this will work if the member being accessed is a field or property 32 | //but won't necessarily work for methods) 33 | this.ValueGetter = memberGetter == null 34 | ? memberSetter.GetGetterExp() 35 | : memberGetter.GetGetterExp(); 36 | } 37 | catch( Exception ) 38 | { 39 | //Must be provided from where to read the member. 40 | //We don't always have the real need to 'read' the member being set (we need to write it). 41 | //This could still be not a problem. 42 | } 43 | } 44 | 45 | public MappingTarget( LambdaExpression memberSetter, LambdaExpression memberGetter = null ) 46 | : base( memberSetter.GetMemberAccessPath() ) 47 | { 48 | this.ValueGetter = memberGetter?.GetMemberAccessPath() 49 | .GetGetterExpWithNullChecks(); 50 | 51 | this.ValueSetter = this.MemberAccessPath.Count == 1 ? memberSetter : 52 | this.MemberAccessPath.GetSetterExpWithNullChecks(); 53 | } 54 | } 55 | 56 | public sealed class MappingTarget : MappingPoint, IMappingTarget 57 | { 58 | public LambdaExpression ValueGetter { get; } 59 | public LambdaExpression ValueSetter { get; } 60 | 61 | public LambdaExpression CustomConstructor { get; set; } 62 | 63 | public MappingTarget( Expression> memberSetter, 64 | Expression> memberGetter = null ) 65 | : base( memberSetter.GetMemberAccessPath() ) 66 | { 67 | this.ValueGetter = memberGetter?.GetMemberAccessPath() 68 | .GetGetterExpWithNullChecks(); 69 | 70 | this.ValueSetter = this.MemberAccessPath.Count == 1 ? memberSetter : 71 | this.MemberAccessPath.GetSetterExpWithNullChecks(); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/MappingResolution.cs: -------------------------------------------------------------------------------- 1 | namespace UltraMapper.Internals 2 | { 3 | public enum MappingResolution 4 | { 5 | /// 6 | /// Indicates that a mapping has been resolved 7 | /// automatically via conventions. 8 | /// 9 | RESOLVED_BY_CONVENTION, 10 | 11 | /// 12 | /// Indicates that a mapping has been 13 | /// configured manually by the user 14 | /// 15 | USER_DEFINED 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Options/CollectionBehaviors.cs: -------------------------------------------------------------------------------- 1 | namespace UltraMapper 2 | { 3 | public enum CollectionBehaviors 4 | { 5 | /// 6 | /// Inherits this option 7 | /// 8 | INHERIT, 9 | 10 | /// 11 | /// If is set to 12 | /// keeps using the target collection (same reference) and then the target collection is cleared and then elements are added. 13 | /// 14 | /// If is set to 15 | /// a new instance is created for the target collection. 16 | /// 17 | RESET, 18 | 19 | /// 20 | /// If is set to 21 | /// keeps using the target collection (same reference) and elements are added. 22 | /// 23 | /// If is set to 24 | /// the target collection's items are added to the new instance and then source collection's items are added 25 | /// 26 | MERGE, 27 | 28 | /// 29 | /// Keeps using the target collection (same reference). 30 | /// If is set to it is ignored. 31 | /// Each target item matching a source item is updated. 32 | /// Each source item non existing in the target collection is added. 33 | /// Each target item non existing in the source collection is removed. 34 | /// A way to compare two items must be provided. 35 | /// 36 | UPDATE 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Options/IMappingOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace UltraMapper 5 | { 6 | public interface IMappingOptions 7 | { 8 | bool IsReferenceTrackingEnabled { get; set; } 9 | 10 | CollectionBehaviors CollectionBehavior { get; set; } 11 | ReferenceBehaviors ReferenceBehavior { get; set; } 12 | 13 | LambdaExpression CollectionItemEqualityComparer { get; set; } 14 | LambdaExpression CustomTargetConstructor { get; set; } 15 | LambdaExpression CustomConverter { get; set; } 16 | 17 | void SetCustomTargetConstructor( Expression> ctor ); 18 | void SetCollectionItemEqualityComparer( Expression> converter ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Options/IMemberMappingOptions.cs: -------------------------------------------------------------------------------- 1 | namespace UltraMapper 2 | { 3 | public interface IMemberMappingOptions : IMappingOptions 4 | { 5 | bool Ignore { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Options/ITypeMappingOptions.cs: -------------------------------------------------------------------------------- 1 | namespace UltraMapper 2 | { 3 | public interface ITypeMappingOptions : IMappingOptions 4 | { 5 | bool? IgnoreMemberMappingResolvedByConvention { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Options/MappingOptionsComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace UltraMapper.Internals 4 | { 5 | internal class MappingOptionsComparer : IEqualityComparer 6 | { 7 | public bool Equals( IMappingOptions x, IMappingOptions y ) 8 | { 9 | if( x == null && y == null ) return true; 10 | if( x == null || y == null ) return false; 11 | 12 | return x.CollectionBehavior == y.CollectionBehavior && 13 | x.ReferenceBehavior == y.ReferenceBehavior && 14 | x.IsReferenceTrackingEnabled == y.IsReferenceTrackingEnabled; 15 | } 16 | 17 | public int GetHashCode( IMappingOptions obj ) 18 | { 19 | return (obj?.CollectionBehavior.GetHashCode() ?? 0) ^ 20 | (obj?.ReferenceBehavior.GetHashCode() ?? 0) ^ 21 | (obj?.IsReferenceTrackingEnabled.GetHashCode() ?? 0); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /UltraMapper/Configuration/Options/ReferenceBehaviors.cs: -------------------------------------------------------------------------------- 1 | namespace UltraMapper 2 | { 3 | public enum ReferenceBehaviors 4 | { 5 | /// 6 | /// Inherits this option 7 | /// 8 | INHERIT, 9 | 10 | /// 11 | /// Creates a new instance, but only if the reference has not been mapped and tracked yet. 12 | /// If the reference has been mapped and tracked, the tracked object is assigned. 13 | /// This is the default. 14 | /// 15 | CREATE_NEW_INSTANCE, 16 | 17 | /// 18 | /// The instance of the target is used in one particular case, following this table: 19 | /// SOURCE (NULL) -> TARGET = NULL 20 | /// 21 | /// SOURCE (NOT NULL / VALUE UNTRACKED) -> TARGET (NULL) = ASSIGN NEW OBJECT 22 | /// SOURCE (NOT NULL / VALUE UNTRACKED) -> TARGET (NOT NULL) = KEEP USING INSTANCE OR CREATE NEW INSTANCE 23 | /// 24 | /// SOURCE (NOT NULL / VALUE ALREADY TRACKED) -> TARGET (NULL) = ASSIGN TRACKED OBJECT 25 | /// SOURCE (NOT NULL / VALUE ALREADY TRACKED) -> TARGET (NOT NULL) = ASSIGN TRACKED OBJECT (the priority is to map identically the source to the target) 26 | /// 27 | USE_TARGET_INSTANCE_IF_NOT_NULL 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /UltraMapper/Internals/CompilerServices.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Enables C#9 record and init property with target framework < NET50 3 | */ 4 | 5 | #if !NET5_0_OR_GREATER 6 | namespace System.Runtime.CompilerServices 7 | { 8 | using System.ComponentModel; 9 | /// 10 | /// Reserved to be used by the compiler for tracking metadata. 11 | /// This class should not be used by developers in source code. 12 | /// 13 | [EditorBrowsable( EditorBrowsableState.Never )] 14 | internal static class IsExternalInit 15 | { 16 | } 17 | } 18 | #endif -------------------------------------------------------------------------------- /UltraMapper/Internals/CustomDelegates.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace UltraMapper.Internals 6 | { 7 | public delegate TTarget UltraMapperDelegate( ReferenceTracker reference, TSource source, TTarget target ); 8 | public delegate object UltraMapperDelegate( ReferenceTracker reference, object source, object target ); 9 | } 10 | -------------------------------------------------------------------------------- /UltraMapper/Internals/ExtensionMethods/DictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace UltraMapper.Internals 5 | { 6 | internal static class DictionaryExtensions 7 | { 8 | public static TValue GetOrAdd( 9 | this Dictionary dictionary, TKey key, Func valueFactory ) 10 | { 11 | if( !dictionary.TryGetValue( key, out TValue value ) ) 12 | { 13 | value = valueFactory(); 14 | dictionary.Add( key, value ); 15 | } 16 | 17 | return value; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /UltraMapper/Internals/ExtensionMethods/ExpressionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | 7 | namespace UltraMapper.Internals 8 | { 9 | public static class ExpressionExtensions 10 | { 11 | public static MemberAccessPath GetMemberAccessPath( this Expression lambdaExpression ) 12 | { 13 | if( !(lambdaExpression is LambdaExpression lambda) ) 14 | throw new InvalidCastException( "Invalid lambda expression" ); 15 | 16 | var stack = new Stack(); 17 | Expression exp = lambda.Body; 18 | 19 | //we are always only interested in the left part of an assignment expression. 20 | if( exp.NodeType == ExpressionType.Assign ) 21 | exp = ((BinaryExpression)exp).Left; 22 | 23 | //if the expression is a constant, we just return the type of the constant 24 | else if( exp.NodeType == ExpressionType.Constant ) 25 | { 26 | var type = ((ConstantExpression)exp).Type; 27 | return new MemberAccessPath( lambda.Type, type ); 28 | } 29 | 30 | if( exp is GotoExpression expression ) 31 | { 32 | stack.Push( expression.Value ); 33 | } 34 | else 35 | { 36 | //break the expression down member by member 37 | while( !(exp is ParameterExpression) ) 38 | { 39 | stack.Push( exp ); 40 | 41 | if( exp.NodeType == ExpressionType.Convert ) 42 | exp = ((UnaryExpression)exp).Operand as Expression; 43 | 44 | else if( exp.NodeType == ExpressionType.MemberAccess ) 45 | exp = ((MemberExpression)exp).Expression; 46 | 47 | else if( exp.NodeType == ExpressionType.Call ) 48 | exp = ((MethodCallExpression)exp).Object; 49 | } 50 | 51 | //instance parameter 52 | stack.Push( exp ); 53 | } 54 | 55 | //If the instance on which we call is a derived class and the property 56 | //we select is defined in the base class, we will notice that 57 | //the PropertyInfo is retrieved through the base class; hence 58 | //DeclaredType and ReflectedType are equal and we basically 59 | //lose information about the ReflectedType (which should be the derived class)... 60 | //..to fix that we do another search. 61 | //We search the member we are accessing by name in the actual type we meant to use for the invocation 62 | //Since we support deep member accessing, things get a little more complex here 63 | //but we basically just follow each member access starting from the passed lambda parameter. 64 | 65 | //Follow member accesses starting from the lambda input parameter. 66 | var lambdaMember = (stack.Pop() as ParameterExpression); 67 | var member = lambda.Parameters.First( 68 | p => p.Name == lambdaMember.Name ).Type as MemberInfo; 69 | 70 | var memberAcessPath = new MemberAccessPath( lambdaMember.Type ); 71 | 72 | if( stack.Count == 0 ) //noticed we already popped once! 73 | { 74 | //return entry instance 75 | memberAcessPath.Add( member ); 76 | memberAcessPath.ReturnType = member.GetMemberType(); 77 | } 78 | else 79 | { 80 | var bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; 81 | 82 | Type type = member.GetMemberType(); 83 | foreach( var item in stack ) 84 | { 85 | var expItem = item; 86 | string memberName = null; 87 | 88 | if( expItem is MemberExpression memberExp ) 89 | { 90 | memberName = memberExp.Member.Name; 91 | 92 | member = type.GetMember( memberName, bindingFlags )[ 0 ]; 93 | type = member.GetMemberType(); 94 | memberAcessPath.Add( member ); 95 | } 96 | else if( expItem is MethodCallExpression methodCallExp ) 97 | { 98 | memberName = methodCallExp.Method.Name; 99 | member = type.GetMember( memberName, bindingFlags )[ 0 ]; 100 | type = member.GetMemberType(); 101 | memberAcessPath.Add( member ); 102 | } 103 | else if( expItem is UnaryExpression unaryExp ) 104 | { 105 | expItem = unaryExp.Operand; 106 | type = unaryExp.Type; 107 | } 108 | } 109 | 110 | memberAcessPath.ReturnType = type; 111 | } 112 | 113 | return memberAcessPath; 114 | } 115 | 116 | public static Expression ReplaceParameter( this Expression expression, Expression newExpression, string name ) 117 | { 118 | return new ExpressionParameterReplacer( newExpression, name ).Visit( expression ); 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /UltraMapper/Internals/ExtensionMethods/MemberInfoExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace UltraMapper.Internals 5 | { 6 | public static class MemberInfoExtensions 7 | { 8 | /// 9 | /// Gets the type of the accessed member (last member) of the expression. 10 | /// 11 | /// 12 | /// 13 | public static Type GetMemberType( this MemberInfo memberInfo ) 14 | { 15 | switch( memberInfo ) 16 | { 17 | case Type type: return type; 18 | case FieldInfo field: return field.FieldType; 19 | case PropertyInfo property: return property.PropertyType; 20 | 21 | case MethodInfo method: 22 | { 23 | if( method.IsGetterMethod() ) 24 | return method.ReturnType; 25 | 26 | if( method.IsSetterMethod() ) 27 | return method.GetParameters()[ 0 ].ParameterType; 28 | 29 | throw new ArgumentException( "Only methods in the form of (T)Get_Value() " + 30 | "or (void)Set_Value(T value) are supported." ); 31 | } 32 | } 33 | 34 | throw new ArgumentException( $"'{memberInfo}' is not supported." ); 35 | } 36 | 37 | /// 38 | /// Attemps to get the type of the accessed member (last member) of the expression. 39 | /// 40 | /// 41 | /// When this method returns, contains the accessed member if found; 42 | /// otherwise, the default value for the type of the value parameter 43 | /// 44 | public static bool TryGetMemberType( this MemberInfo memberInfo, out Type memberType ) 45 | { 46 | memberType = default; 47 | 48 | switch( memberInfo ) 49 | { 50 | case Type type: 51 | { 52 | memberType = type; 53 | return true; 54 | } 55 | 56 | case FieldInfo field: 57 | { 58 | memberType = field.FieldType; 59 | return true; 60 | } 61 | 62 | case PropertyInfo property: 63 | { 64 | memberType = property.PropertyType; 65 | return true; 66 | } 67 | 68 | case MethodInfo methodInfo: 69 | { 70 | if( methodInfo.IsGetterMethod() ) 71 | memberType = methodInfo.ReturnType; 72 | 73 | if( methodInfo.IsSetterMethod() ) 74 | memberType = methodInfo.GetParameters()[ 0 ].ParameterType; 75 | 76 | return false; 77 | } 78 | } 79 | 80 | return false; 81 | } 82 | 83 | /// 84 | /// Checks if a member it's part of a specific type. 85 | /// 86 | /// 87 | /// 88 | /// 89 | public static bool BelongsTo( this MemberInfo member ) 90 | => BelongsTo( member, typeof( TTarget ) ); 91 | 92 | /// 93 | /// Checks if a member it's part of a specific type. 94 | /// 95 | /// 96 | /// 97 | /// 98 | public static bool BelongsTo( this MemberInfo member, Type type ) 99 | => type == member.ReflectedType; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /UltraMapper/Internals/ExtensionMethods/MethodInfoExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace UltraMapper.Internals 5 | { 6 | internal static class MethodInfoExtensions 7 | { 8 | /// 9 | /// Checks if a method is a getter. 10 | /// 11 | /// The method to be inspected 12 | /// True if a method is parameterless and its return type is not void; False otherwise 13 | public static bool IsGetterMethod( this MethodInfo methodInfo ) 14 | { 15 | return methodInfo.ReturnType != typeof( void ) && 16 | methodInfo.GetParameters().Length == 0; 17 | } 18 | 19 | /// 20 | /// Checks if a method is a getter of a given return type. 21 | /// 22 | /// Expected return type of the method 23 | /// The method to be inspected 24 | /// True if a method is parameterless and its return type is of type ; False otherwise 25 | public static bool IsGetterMethod( this MethodInfo methodInfo ) 26 | { 27 | return IsGetterMethod( methodInfo, typeof( T ) ); 28 | } 29 | 30 | /// 31 | /// Checks if a method is a getter of a given return type. 32 | /// 33 | /// The method to be inspected 34 | /// Expected return type of the method 35 | /// True if a method is parameterless and its return type is of type ; False otherwise 36 | public static bool IsGetterMethod( this MethodInfo methodInfo, Type returnType ) 37 | { 38 | return methodInfo.ReturnType == returnType && 39 | methodInfo.GetParameters().Length == 0; 40 | } 41 | 42 | /// 43 | /// Checks if a method is a setter. 44 | /// 45 | /// The method to be inspected 46 | /// True if a method returns void and takes as input exactly one parameter; False otherwise 47 | public static bool IsSetterMethod( this MethodInfo methodInfo ) 48 | { 49 | return methodInfo.ReturnType == typeof( void ) && 50 | methodInfo.GetParameters().Length == 1; 51 | } 52 | 53 | /// 54 | /// Checks if a method is a setter taking as input exactly one parameter of a given type. 55 | /// 56 | /// Expected type of the input parameter 57 | /// The method to be inspected 58 | /// True if a method returns void and takes as input exactly one parameter of type ; False otherwise 59 | public static bool IsSetterMethod( this MethodInfo methodInfo ) 60 | { 61 | return IsSetterMethod( methodInfo, typeof( T ) ); 62 | } 63 | 64 | /// 65 | /// Checks if a method is a setter taking as input exactly one parameter of a given type. 66 | /// 67 | /// The method to be inspected 68 | /// Expected type of the input parameter 69 | /// True if a method returns void and takes as input exactly one parameter of type ; False otherwise 70 | public static bool IsSetterMethod( this MethodInfo methodInfo, Type paramType ) 71 | { 72 | var parameters = methodInfo.GetParameters(); 73 | 74 | return methodInfo.ReturnType == typeof( void ) && parameters.Length == 1 75 | && parameters[ 0 ].ParameterType == paramType; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /UltraMapper/Internals/MemberAccessPath.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | 8 | namespace UltraMapper.Internals 9 | { 10 | public class MemberAccessPath : IEnumerable 11 | { 12 | public Type EntryInstance { get; } 13 | public Type ReturnType { get; set; } 14 | 15 | private readonly List _memberAccess = new(); 16 | 17 | public int Count => _memberAccess.Count; 18 | 19 | public MemberAccessPath( Type entryInstance ) 20 | { 21 | this.EntryInstance = entryInstance; 22 | this.ReturnType = entryInstance; 23 | } 24 | 25 | public MemberAccessPath( Type entryInstance, MemberInfo memberInfo ) 26 | : this( entryInstance, new[] { memberInfo } ) { } 27 | 28 | public MemberAccessPath( Type entryInstance, IEnumerable members ) 29 | { 30 | this.EntryInstance = entryInstance; 31 | 32 | foreach( var member in members ) 33 | _memberAccess.Add( member ); 34 | } 35 | 36 | public void Add( MemberInfo memberInfo ) 37 | => _memberAccess.Add( memberInfo ); 38 | 39 | public MemberInfo this[ int index ] => _memberAccess[ index ]; 40 | 41 | public MemberAccessPath Reverse() 42 | { 43 | _memberAccess.Reverse(); 44 | return this; 45 | } 46 | 47 | public IEnumerator GetEnumerator() 48 | => _memberAccess.GetEnumerator(); 49 | 50 | IEnumerator IEnumerable.GetEnumerator() 51 | => this.GetEnumerator(); 52 | 53 | public override bool Equals( object obj ) 54 | { 55 | if( obj is MemberAccessPath accessPath ) 56 | return this.GetHashCode() == accessPath.GetHashCode(); 57 | 58 | return false; 59 | } 60 | 61 | public override int GetHashCode() 62 | { 63 | return this.Select( i => i.GetHashCode() ) 64 | .Aggregate( 0, ( aggregate, next ) => aggregate ^ next ); 65 | } 66 | 67 | public override string ToString() 68 | { 69 | var sb = new StringBuilder(); 70 | 71 | for( int i = 0; i < this.Count - 1; i++ ) 72 | sb.Append( $"{this[ i ].Name} -> " ); 73 | 74 | sb.Append( $"{this[ this.Count - 1 ].Name}" ); 75 | return sb.ToString(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /UltraMapper/Internals/MemberPair.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace UltraMapper.Internals 4 | { 5 | public struct MemberPair 6 | { 7 | public readonly MemberAccessPath SourceMemberPath; 8 | public readonly MemberAccessPath TargetMemberPath; 9 | 10 | public MemberPair( MemberAccessPath source, MemberAccessPath target ) 11 | { 12 | this.SourceMemberPath = source; 13 | this.TargetMemberPath = target; 14 | } 15 | 16 | public MemberPair( MemberInfo source, MemberInfo target ) 17 | { 18 | this.SourceMemberPath = new MemberAccessPath( source.DeclaringType ) { source }; 19 | this.TargetMemberPath = new MemberAccessPath( target.DeclaringType ) { target }; 20 | } 21 | 22 | public MemberPair( MemberAccessPath source, MemberInfo target ) 23 | { 24 | this.SourceMemberPath = source; 25 | this.TargetMemberPath = new MemberAccessPath( target.DeclaringType ) { target }; 26 | } 27 | 28 | public MemberPair( MemberInfo source, MemberAccessPath target ) 29 | { 30 | this.SourceMemberPath = new MemberAccessPath( source.DeclaringType ) { source }; 31 | this.TargetMemberPath = target; 32 | } 33 | 34 | public override bool Equals( object obj ) 35 | { 36 | if( obj is MemberPair memberPair ) 37 | { 38 | return this.SourceMemberPath.Equals( memberPair.SourceMemberPath ) && 39 | this.TargetMemberPath.Equals( memberPair.TargetMemberPath ); 40 | } 41 | 42 | return false; 43 | } 44 | 45 | public override int GetHashCode() 46 | { 47 | return this.SourceMemberPath.GetHashCode() ^ 48 | this.TargetMemberPath.GetHashCode(); 49 | } 50 | 51 | public static bool operator ==( MemberPair left, MemberPair right ) 52 | { 53 | return left.Equals( right ); 54 | } 55 | 56 | public static bool operator !=( MemberPair left, MemberPair right ) 57 | { 58 | return !(left == right); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /UltraMapper/Internals/SpecializedCollections/OrderedTypeSet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace UltraMapper.Internals 6 | { 7 | /// 8 | /// Represents a collection where each element is unique by type 9 | /// and insertion order is preserved. 10 | /// 11 | public sealed class OrderedTypeSet : ICollection 12 | { 13 | private readonly Dictionary> _dictionary; 14 | private readonly LinkedList _linkedList; 15 | 16 | public int Count => _dictionary.Count; 17 | public bool IsReadOnly => false; 18 | 19 | public OrderedTypeSet() 20 | { 21 | _dictionary = new Dictionary>(); 22 | _linkedList = new LinkedList(); 23 | } 24 | 25 | public IEnumerator GetEnumerator() 26 | { 27 | return _linkedList.GetEnumerator(); 28 | } 29 | 30 | IEnumerator IEnumerable.GetEnumerator() 31 | { 32 | return GetEnumerator(); 33 | } 34 | 35 | public bool Contains( T item ) 36 | { 37 | return _dictionary.ContainsKey( item.GetType() ); 38 | } 39 | 40 | public void CopyTo( T[] array, int arrayIndex ) 41 | { 42 | _linkedList.CopyTo( array, arrayIndex ); 43 | } 44 | 45 | public bool Add( T item ) 46 | { 47 | if( _dictionary.ContainsKey( item.GetType() ) ) 48 | return false; 49 | 50 | var node = _linkedList.AddLast( item ); 51 | _dictionary.Add( item.GetType(), node ); 52 | 53 | return true; 54 | } 55 | 56 | public void AddBefore( 57 | params T[] mebs ) 58 | { 59 | var node = _dictionary[ typeof( TMappingExpressionBuilder ) ]; 60 | 61 | for( int i = 0; i < mebs.Length; i++ ) 62 | { 63 | var newNode = _linkedList.AddBefore( node, mebs[ i ] ); 64 | _dictionary.Add( mebs[ i ].GetType(), newNode ); 65 | } 66 | } 67 | 68 | public void AddAfter( 69 | params T[] mebs ) 70 | { 71 | var node = _dictionary[ typeof( TMappingExpressionBuilder ) ]; 72 | 73 | for( int i = 0; i < mebs.Length; i++ ) 74 | { 75 | var nextNode = new LinkedListNode( mebs[ i ] ); 76 | _linkedList.AddBefore( node, mebs[i]); 77 | _dictionary.Add( mebs[ i ].GetType(), nextNode ); 78 | node = nextNode; 79 | } 80 | } 81 | 82 | public bool Remove( T item ) 83 | { 84 | bool found = _dictionary.TryGetValue( item.GetType(), 85 | out LinkedListNode node ); 86 | 87 | if( !found ) return false; 88 | 89 | _dictionary.Remove( item.GetType() ); 90 | _linkedList.Remove( node ); 91 | 92 | return true; 93 | } 94 | 95 | public void Clear() 96 | { 97 | _dictionary.Clear(); 98 | _linkedList.Clear(); 99 | } 100 | 101 | void ICollection.Add( T item ) 102 | { 103 | Add( item ); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /UltraMapper/Internals/SpecializedCollections/TypeSet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace UltraMapper.Internals 6 | { 7 | /// 8 | /// Represents a collection where each element is unique by type. 9 | /// 10 | public class TypeSet : IEnumerable 11 | { 12 | protected Dictionary _instances; 13 | 14 | public TypeSet( Action> config = null ) 15 | { 16 | _instances = new Dictionary(); 17 | config?.Invoke( this ); 18 | } 19 | 20 | public TypeSet GetOrAdd( Action config = null ) 21 | where T : TInterface, new() 22 | { 23 | var instance = _instances.GetOrAdd( typeof( T ), () => new T() ); 24 | config?.Invoke( (T)instance ); 25 | 26 | return this; 27 | } 28 | 29 | public TypeSet Remove() where T : TInterface 30 | { 31 | _instances.Remove( typeof( T ) ); 32 | return this; 33 | } 34 | 35 | public void Clear() 36 | { 37 | _instances.Clear(); 38 | } 39 | 40 | public IEnumerator GetEnumerator() 41 | { 42 | return _instances.Values.GetEnumerator(); 43 | } 44 | 45 | IEnumerator IEnumerable.GetEnumerator() 46 | { 47 | return this.GetEnumerator(); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /UltraMapper/Internals/TypePair.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace UltraMapper.Internals 4 | { 5 | internal struct TypePair 6 | { 7 | public readonly Type SourceType; 8 | public readonly Type TargetType; 9 | 10 | private string _toString; 11 | private int? _hashcode; 12 | 13 | public TypePair( Type source, Type target ) 14 | { 15 | this.SourceType = source; 16 | this.TargetType = target; 17 | 18 | _toString = null; 19 | _hashcode = null; 20 | } 21 | 22 | public override bool Equals( object obj ) 23 | { 24 | if( obj is TypePair typePair ) 25 | { 26 | return this.SourceType.Equals( typePair.SourceType ) && 27 | this.TargetType.Equals( typePair.TargetType ); 28 | } 29 | 30 | return false; 31 | } 32 | 33 | public override int GetHashCode() 34 | { 35 | if( _hashcode == null ) 36 | { 37 | _hashcode = this.SourceType.GetHashCode() 38 | ^ this.TargetType.GetHashCode(); 39 | } 40 | 41 | return _hashcode.Value; 42 | } 43 | 44 | public static bool operator !=( TypePair obj1, TypePair obj2 ) 45 | { 46 | return !(obj1 == obj2); 47 | } 48 | 49 | public static bool operator ==( TypePair obj1, TypePair obj2 ) 50 | { 51 | return obj1.Equals( obj2 ); 52 | } 53 | 54 | public override string ToString() 55 | { 56 | if( _toString == null ) 57 | { 58 | string sourceTypeName = this.SourceType.GetPrettifiedName(); 59 | string targetTypeName = this.TargetType.GetPrettifiedName(); 60 | 61 | _toString = $"[{sourceTypeName}, {targetTypeName}]"; 62 | } 63 | 64 | return _toString; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /UltraMapper/Note.txt: -------------------------------------------------------------------------------- 1 | PROBLEMS: 2 | 3 | - CustomConverters do not add references to the ReferenceTracker. 4 | 5 | When invoking the recursion map (especially with non concrete types) 6 | a reference to the referencetracker is expected as first argument. 7 | 8 | Solution: 9 | - Wrap the call to the converter inside a block calling the referencetracker 10 | (we can therefore take care of the source and target instances) 11 | 12 | - Map overload for converter that takes as first argument the reference tracker 13 | so that inner references can be managed (added to the referencetracker) by the user 14 | 15 | KNOWN (POTENTIAL) PROBLEMS: 16 | 17 | - Configuration can be done against interfaces and base classes but at 18 | runtime we should always work with the actual concrete type. 19 | While performing the mapping step we should always get the configuration 20 | of the concrete type via 'GetType()' (avoid typeof(T)) 21 | 22 | - When unflattening by convention OR manually mapping using deep nested member selectors (ie: projections, ie: x => x.PropertyA.GetMethodB().GetFieldC() ), 23 | all instances referred by the selector (both on source and target) must be instantiated or you can incur in NullReferenceException. 24 | 25 | ON SOURCE SIDE: The problem has been solved by checking for null references before accessing members. OK! 26 | 27 | ON TARGET SIDE: The problem has been solved by instantiating a new instance of every null object in the expression. OK! 28 | MINOR PROBLEM: Custom constructors are not taken into account and the type must have a parameterless public constructor. 29 | 30 | - Collection mapping strategies: when updating order is not preserved 31 | (this could be right since it is implied that we keep using the existing target collection). 32 | 33 | - Reference type's CustomConverters do not recurse on inner references. 34 | (this might be correct since the entire mapping is delegated to the user). 35 | 36 | - Configurations can be shared, but not subsequently edited because conventions and expression builders are not run again. 37 | Introduce a seal mechanism to inform the user about this; or a configuration refresh mechanism. 38 | 39 | - Mapping types not providing a parameterless constructor is probably feasible but would need a big refactor. 40 | One example is ReadOnlyCollection support: it is supported only if we find it as a target's member. 41 | 42 | - When mapping to an array: if the capacity of the target array is more than enough to hold all of the items of the source collection 43 | and ReferenceBehavior = USE_TARGET_INSTANCE_IF_NOT_NULL, then target array will contain null elements. 44 | It can be ok. Force to ReferenceBehavior = CREATE_NEW_INSTANCE to have a same-sized array. 45 | 46 | TODO: 47 | 48 | - Tuple support 49 | 50 | - Automatic reverse mapping (at the time a reverse mapping is a normal mapping 51 | encountered at execution time and resolved by convention and thus does not take into account manual overrides). 52 | 53 | - Dynamic mapping 54 | 55 | - Member configuration should work regardless the type of the members involved. 56 | This would solve EntityFramework problems with DynamicProxies not reading the 57 | configuration (stored by type and not taken into account because concrete object type != dynamic proxied type). 58 | Type configuration, instead, obviously should work only exactly for the specified types 59 | 60 | - Configurators need extra overloads to cover target member mapping via methods Get/Set 61 | (both untyped (MemberInfo), and strongly types (Lambdas) helpers are missing). 62 | 63 | - Multidimensional/jagged arrays; 64 | 65 | - UNIT TEST: VERIFY MAPPING method should search for objects sharing the same reference in the source and check 66 | that on the target those objects are mapped to a single object sharing the same reference 67 | 68 | NOTES: 69 | 70 | - Interface-to-interface mapping is not possible (eg List -> List) 71 | because i dont't know what concrete target type the user wants. 72 | The user can specify a custom converter or custom constructor can be provided to solve the problem 73 | or map directly to the concrete type they want (eg List -> List) -------------------------------------------------------------------------------- /UltraMapper/ReferenceTracking/ReferenceTracker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace UltraMapper 5 | { 6 | /// 7 | /// This class helps tracking and retrieving each source reference to its mapped target instance. 8 | /// A reference type can be mapped to many different types (one instance for each target type). 9 | /// 10 | public class ReferenceTracker 11 | { 12 | //> 13 | private readonly Dictionary> _mappings 14 | = new Dictionary>( 64 ); 15 | 16 | public void Add( object sourceInstance, Type targetType, object targetInstance ) 17 | { 18 | if( !_mappings.TryGetValue( sourceInstance, out var dict ) ) 19 | { 20 | dict = new Dictionary() { { targetType, targetInstance } }; 21 | _mappings.Add( sourceInstance, dict ); 22 | } 23 | else 24 | { 25 | #if NET5_0_OR_GREATER 26 | dict.TryAdd( targetType, targetInstance ); 27 | #else 28 | if( !dict.ContainsKey( targetType ) ) 29 | dict.Add( targetType, targetInstance ); 30 | #endif 31 | } 32 | } 33 | 34 | public bool TryGetValue( object sourceInstance, Type targetType, out object targetInstance ) 35 | { 36 | if( _mappings.TryGetValue( sourceInstance, out Dictionary dict ) ) 37 | return dict.TryGetValue( targetType, out targetInstance ); 38 | 39 | targetInstance = null; 40 | return false; 41 | } 42 | 43 | public bool Contains( object sourceInstance, Type targetType ) 44 | { 45 | if( _mappings.ContainsKey( sourceInstance ) ) 46 | return _mappings[ sourceInstance ].ContainsKey( targetType ); 47 | 48 | return false; 49 | } 50 | 51 | public object this[ object sourceInstance, Type targetType ] 52 | => _mappings[ sourceInstance ][ targetType ]; 53 | 54 | public void Clear() => _mappings.Clear(); 55 | } 56 | } -------------------------------------------------------------------------------- /UltraMapper/UltraMapper.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard20;net452;net46;net461;net462;net47;net471;net472;net48;net50;net60 5 | true 6 | Mauro Sampietro 7 | 2021 8 | false 9 | 1.0.0.0 10 | https://maurosampietro.github.io/UltraMapper/ 11 | https://github.com/maurosampietro/UltraMapper 12 | A .NET object-mapper useful to deep-copy an object, or transform (map) an object of a certain type to another type. 13 | 14 | map mapper object deep copy 15 | 1.0.0.0 16 | 1.0.0.0 17 | AnyCPU 18 | latest 19 | 20 | 21 | 22 | 23 | <_Parameter1>UltraMapper.Tests 24 | 25 | 26 | <_Parameter1>UltraMapper.CommandLine 27 | 28 | 29 | <_Parameter1>UltraMapper.Csv 30 | 31 | 32 | <_Parameter1>UltraMapper.Parsing 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /UltraMapper/UltraMapper.nuspec: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | UltraMapper 5 | $version$ 6 | Mauro Sampietro 7 | Mauro Sampietro 8 | https://maurosampietro.github.io/UltraMapper/ 9 | false 10 | UltraMapper is a .NET object-mapper. It can be useful to deep-copy an object, or transform (map) an object of a certain type to another. 11 | 12 | Copyright 2017 UltraMapper 13 | Mapper object mapping deep copy clone cloner .net 14 | 15 | 16 | -------------------------------------------------------------------------------- /UltraMapper/UltraMapperExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace UltraMapper 4 | { 5 | public static class UltraMapperExtensionMethods 6 | { 7 | private static readonly Configuration _configuration = new Configuration(); 8 | private static Mapper _mapper = new Mapper( _configuration ); 9 | 10 | public static void MapTo( this TSource source, 11 | TTarget target, Action config = null ) where TTarget : class 12 | { 13 | config?.Invoke( _configuration ); 14 | _mapper.Map( source, target ); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman 2 | title: "UltraMapper" 3 | description: "UltraMapper is a .NET object-mapper. It can be useful to deep-copy an object, or transform (map) an object of a certain type to another." 4 | show_downloads: "true" 5 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 0.2.{build} 2 | pull_requests: 3 | do_not_increment_build_number: true 4 | image: Visual Studio 2022 5 | assembly_info: 6 | patch: true 7 | file: '**\AssemblyInfo.*' 8 | assembly_version: '{version}' 9 | assembly_file_version: '{version}' 10 | assembly_informational_version: '{version}' 11 | dotnet_csproj: 12 | patch: true 13 | file: '**\*.csproj' 14 | version: '{version}' 15 | version_prefix: '{version}' 16 | package_version: '{version}' 17 | assembly_version: '{version}' 18 | file_version: '{version}' 19 | informational_version: '{version}' 20 | before_build: 21 | - cmd: nuget restore 22 | build: 23 | project: UltraMapper\UltraMapper.csproj 24 | publish_nuget: true 25 | project: UltraMapper\UltraMapper.csproj 26 | verbosity: detailed 27 | deploy: 28 | - provider: NuGet 29 | api_key: 30 | secure: V9IBfEGtS3LppLMRphtIACQYBJDGNevXDKbw2OSGYbTO5eYEOHuCT/vea9obbsgV 31 | skip_symbols: false 32 | - provider: GitHub 33 | tag: UltraMapper v$(appveyor_build_version) 34 | release: UltraMapper v$(appveyor_build_version) 35 | auth_token: 36 | secure: QeUb3wjlvP0bht90xWcoRLcGA5GI6YXvv4CfXUgV/Oh3OJuLQHZ8VtD9c9FEwZqB 37 | artifact: /.*\.nupkg/ 38 | -------------------------------------------------------------------------------- /graph0.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maurosampietro/UltraMapper/e0814f84874fe4b07dd8be7138a1819e117c2ed7/graph0.PNG -------------------------------------------------------------------------------- /graph1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maurosampietro/UltraMapper/e0814f84874fe4b07dd8be7138a1819e117c2ed7/graph1.PNG --------------------------------------------------------------------------------