├── .vscode ├── settings.json └── tasks.json ├── .gitignore ├── .github └── workflows │ ├── build-test.yml │ └── publish-nuget.yml ├── src ├── .editorconfig ├── TypeMerger.Tests │ ├── TypeMerger.Tests.csproj │ └── TypeMergerTests.cs └── TypeMerger │ ├── TypeMerger.csproj │ ├── TypeMergerPolicy.cs │ └── TypeMerger.cs ├── LICENSE.txt ├── README.md └── TypeMerger.sln /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "dotnet-test-explorer.testProjectPath": "**/*.Tests" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | *.userprefs 3 | /build/ 4 | *.suo 5 | *.user 6 | _ReSharper.*/ 7 | *.sdf 8 | bin/ 9 | obj/ 10 | Debug/ 11 | Release/ 12 | *.opensdf 13 | *.tlog 14 | *.log 15 | TestResult.xml 16 | *.VisualState.xml 17 | Version.cs 18 | Version.h 19 | Version.cpp 20 | .DS_Store 21 | Icon 22 | Icon 23 | -------------------------------------------------------------------------------- /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | name: Build & Test 2 | 3 | on: 4 | pull_request: 5 | branches: [ develop, main ] 6 | 7 | jobs: 8 | build: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Setup .NET 15 | uses: actions/setup-dotnet@v1 16 | with: 17 | dotnet-version: 6.0.x 18 | - name: Restore dependencies 19 | run: dotnet restore 20 | - name: Build 21 | run: dotnet build --no-restore 22 | - name: Test 23 | run: dotnet test --no-build --verbosity normal 24 | -------------------------------------------------------------------------------- /src/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.cs] 4 | # New line preferences 5 | csharp_new_line_before_catch = false 6 | csharp_new_line_before_else = false 7 | csharp_new_line_before_finally = false 8 | csharp_new_line_before_members_in_anonymous_types = false 9 | csharp_new_line_before_members_in_object_initializers = false 10 | csharp_new_line_before_open_brace = false 11 | csharp_new_line_between_query_expression_clauses = false 12 | 13 | [*] 14 | charset = utf-8 15 | indent_style = space 16 | indent_size = 4 17 | end_of_line = lf 18 | insert_final_newline = true 19 | trim_trailing_whitespace = true 20 | -------------------------------------------------------------------------------- /.github/workflows/publish-nuget.yml: -------------------------------------------------------------------------------- 1 | name: Publish Nuget Package 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | jobs: 8 | build-test-publish: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Setup .NET 15 | uses: actions/setup-dotnet@v1 16 | with: 17 | dotnet-version: 6.0.x 18 | - name: Restore dependencies 19 | run: dotnet restore 20 | - name: Build 21 | run: dotnet build --no-restore 22 | - name: Test 23 | run: dotnet test --no-build --verbosity normal 24 | - name: Publish nuget on version change 25 | uses: tedd/publish-nuget-neo@v1 26 | with: 27 | PROJECT_FILE_PATH: src/TypeMerger/TypeMerger.csproj 28 | NUGET_KEY: ${{secrets.NUGET_API_KEY}} 29 | -------------------------------------------------------------------------------- /src/TypeMerger.Tests/TypeMerger.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp6.0 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | all 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2008 Mark J. Miller, Kyle Finley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/TypeMerger/TypeMerger.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | A simple convention-based object merger for .NET Core. TypeMerger allows two objects to be merged into a new type using Reflection.Emit to generate a new type. Properties can be ignored and collisions can be resolved using a fluent api. 5 | netstandard2.1;net472;net48 6 | TypeMerger 7 | TypeMerger 8 | © 2023 9 | Object Merger 10 | 2.1.4 11 | 12 | Mark Miller, github.com/developmentalmadness 13 | Kyle Finley, github.com/kfinley 14 | github.com/jpmorel 15 | github.com/jctheod 16 | github.com/lmkz 17 | 18 | https://github.com/kfinley/TypeMerger 19 | LICENSE.txt 20 | README.md 21 | git 22 | https://github.com/kfinley/TypeMerger 23 | true 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TypeMerger - Merge two objects into one 2 | 3 | TypeMerger is a simple convention-based object merger for .NET Core. It allows two objects of any type to be merged into a new ``Type``. Object properties can be ignored and any collisions can be resolved using a fluent api. The object returned is a new ``Type`` that is dynamically generated and loaded using ``System.Reflection.Emit``. 4 | 5 | ## Dependencies 6 | None 7 | ## How is it used? 8 | 9 | ### Simple usage 10 | This will result in a new object that has All the properties from obj1 and obj2. 11 | ``` 12 | var result = TypeMerger.Merge(obj1, obj2); 13 | ``` 14 | 15 | ### Ignore Certain Properties 16 | This will result in a new object that has all of the properties from Obj1 and Obj2 Except for ``SomeProperty`` from obj1 and ``AnotherProperty`` from obj2. 17 | ``` 18 | var result = TypeMerger.Ignore(() => obj1.SomeProperty) 19 | .Ignore(() => obj2.AnotherProperty) 20 | .Merge(obj1, obj2); 21 | 22 | ``` 23 | 24 | ### What About Collisions? 25 | If both objects have the same property there is a fluent method that will tell the ``Merger`` which object to use for that property. You simply tell the ``Merger`` which property to ``Use``. 26 | 27 | In this example given obj1 and obj2 that both have ``SomeProperty``, the value from obj2 will be used. 28 | ``` 29 | var result = TypeMerger.Use(() => obj2.SomeProperty) 30 | .Merge(obj1, obj2); 31 | ``` 32 | 33 | #### *What about collisions which ``Use`` hasn't specified which object's property to use?* 34 | 35 | If both objects have the same property, and you do not specify which one to ``Use``, then the property from the **first** object passed to ``Merge`` will be used. (Look at the ``Merge_Types_with_Name_Collision`` unit test for an example.) 36 | 37 | ### Mix & Match Your Merge 38 | Combining the ``.Ignore`` and ``.Use`` fluent methods allows you to pick and choose what you want from your objects. 39 | 40 | ``` 41 | var obj1 = new { 42 | SomeProperty = "foo", 43 | SomeAmount = 20, 44 | AnotherProperty = "yo" 45 | }; 46 | 47 | var obj2 = new { 48 | SomeProperty = "bar", 49 | SomePrivateStuff = "SECRET!!", 50 | SomeOtherProperty = "more stuff" 51 | }; 52 | 53 | var result = TypeMerger.Ignore(() => obj1.AnotherProperty) 54 | .Use(() => obj2.SomeProperty) 55 | .Ignore(() => obj2.SomePrivateStuff) 56 | .Merge(obj1, obj2); 57 | ``` 58 | 59 | The result object will have the following properties and values: 60 | 61 | SomeProperty: "bar" 62 | SomeAmount: 20 63 | SomeOtherProperty: "more stuff" 64 | 65 | 66 | 67 | 68 | ## History 69 | The code is based on the original TypeMerger class written by [Mark Miller](http://www.developmentalmadness.com/). Updated, enhanced, and now maintained by [Kyle Finley](https://twitter.com/kfinley). 70 | 71 | Original posting: [KyleFinley.net/typemerger](http://goo.gl/qJ9FqN) 72 | 73 | 74 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/src/TypeMerger.Tests/TypeMerger.Tests.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/src/TypeMerger.Tests/TypeMerger.Tests.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "--project", 36 | "${workspaceFolder}/src/TypeMerger.Tests/TypeMerger.Tests.csproj" 37 | ], 38 | "problemMatcher": "$msCompile" 39 | }, 40 | { 41 | "label": "build", 42 | "command": "dotnet", 43 | "type": "process", 44 | "args": [ 45 | "build", 46 | "${workspaceFolder}/src/TypeMerger.Tests/TypeMerger.Tests.csproj", 47 | "/property:GenerateFullPaths=true", 48 | "/consoleloggerparameters:NoSummary" 49 | ], 50 | "problemMatcher": "$msCompile" 51 | }, 52 | { 53 | "label": "publish", 54 | "command": "dotnet", 55 | "type": "process", 56 | "args": [ 57 | "publish", 58 | "${workspaceFolder}/src/TypeMerger.Tests/TypeMerger.Tests.csproj", 59 | "/property:GenerateFullPaths=true", 60 | "/consoleloggerparameters:NoSummary" 61 | ], 62 | "problemMatcher": "$msCompile" 63 | }, 64 | { 65 | "label": "watch", 66 | "command": "dotnet", 67 | "type": "process", 68 | "args": [ 69 | "watch", 70 | "run", 71 | "${workspaceFolder}/src/TypeMerger.Tests/TypeMerger.Tests.csproj", 72 | "/property:GenerateFullPaths=true", 73 | "/consoleloggerparameters:NoSummary" 74 | ], 75 | "problemMatcher": "$msCompile" 76 | }, 77 | { 78 | "label": "Build Solution", 79 | "type": "process", 80 | "command": "dotnet", 81 | "args": [ 82 | "build", 83 | "${workspaceFolder}/src/TypeMerger.sln" 84 | ], 85 | "problemMatcher": "$msCompile", 86 | "group": { 87 | "kind": "build", 88 | "isDefault": true 89 | } 90 | } 91 | ] 92 | } -------------------------------------------------------------------------------- /TypeMerger.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{6B4EC6BD-1717-44FB-9786-F556CF37308A}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TypeMerger", "src\TypeMerger\TypeMerger.csproj", "{11F32BA9-C29F-4842-8CB0-9D5CA7CD2CE4}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TypeMerger.Tests", "src\TypeMerger.Tests\TypeMerger.Tests.csproj", "{46D82B30-EA81-4B17-AADB-64F212355B40}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Debug|x64 = Debug|x64 16 | Debug|x86 = Debug|x86 17 | Release|Any CPU = Release|Any CPU 18 | Release|x64 = Release|x64 19 | Release|x86 = Release|x86 20 | EndGlobalSection 21 | GlobalSection(SolutionProperties) = preSolution 22 | HideSolutionNode = FALSE 23 | EndGlobalSection 24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 25 | {11F32BA9-C29F-4842-8CB0-9D5CA7CD2CE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {11F32BA9-C29F-4842-8CB0-9D5CA7CD2CE4}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {11F32BA9-C29F-4842-8CB0-9D5CA7CD2CE4}.Debug|x64.ActiveCfg = Debug|Any CPU 28 | {11F32BA9-C29F-4842-8CB0-9D5CA7CD2CE4}.Debug|x64.Build.0 = Debug|Any CPU 29 | {11F32BA9-C29F-4842-8CB0-9D5CA7CD2CE4}.Debug|x86.ActiveCfg = Debug|Any CPU 30 | {11F32BA9-C29F-4842-8CB0-9D5CA7CD2CE4}.Debug|x86.Build.0 = Debug|Any CPU 31 | {11F32BA9-C29F-4842-8CB0-9D5CA7CD2CE4}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {11F32BA9-C29F-4842-8CB0-9D5CA7CD2CE4}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {11F32BA9-C29F-4842-8CB0-9D5CA7CD2CE4}.Release|x64.ActiveCfg = Release|Any CPU 34 | {11F32BA9-C29F-4842-8CB0-9D5CA7CD2CE4}.Release|x64.Build.0 = Release|Any CPU 35 | {11F32BA9-C29F-4842-8CB0-9D5CA7CD2CE4}.Release|x86.ActiveCfg = Release|Any CPU 36 | {11F32BA9-C29F-4842-8CB0-9D5CA7CD2CE4}.Release|x86.Build.0 = Release|Any CPU 37 | {46D82B30-EA81-4B17-AADB-64F212355B40}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {46D82B30-EA81-4B17-AADB-64F212355B40}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {46D82B30-EA81-4B17-AADB-64F212355B40}.Debug|x64.ActiveCfg = Debug|Any CPU 40 | {46D82B30-EA81-4B17-AADB-64F212355B40}.Debug|x64.Build.0 = Debug|Any CPU 41 | {46D82B30-EA81-4B17-AADB-64F212355B40}.Debug|x86.ActiveCfg = Debug|Any CPU 42 | {46D82B30-EA81-4B17-AADB-64F212355B40}.Debug|x86.Build.0 = Debug|Any CPU 43 | {46D82B30-EA81-4B17-AADB-64F212355B40}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {46D82B30-EA81-4B17-AADB-64F212355B40}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {46D82B30-EA81-4B17-AADB-64F212355B40}.Release|x64.ActiveCfg = Release|Any CPU 46 | {46D82B30-EA81-4B17-AADB-64F212355B40}.Release|x64.Build.0 = Release|Any CPU 47 | {46D82B30-EA81-4B17-AADB-64F212355B40}.Release|x86.ActiveCfg = Release|Any CPU 48 | {46D82B30-EA81-4B17-AADB-64F212355B40}.Release|x86.Build.0 = Release|Any CPU 49 | EndGlobalSection 50 | GlobalSection(NestedProjects) = preSolution 51 | {11F32BA9-C29F-4842-8CB0-9D5CA7CD2CE4} = {6B4EC6BD-1717-44FB-9786-F556CF37308A} 52 | {46D82B30-EA81-4B17-AADB-64F212355B40} = {6B4EC6BD-1717-44FB-9786-F556CF37308A} 53 | EndGlobalSection 54 | EndGlobal 55 | -------------------------------------------------------------------------------- /src/TypeMerger/TypeMergerPolicy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | 5 | namespace TypeMerger { 6 | 7 | /// 8 | /// Policy class used with the TypeMerger class to define properties that are ignored or priority when there objects contain the same properties and there is a collision. 9 | /// 10 | public class TypeMergerPolicy { 11 | 12 | 13 | readonly private IList> ignoredProperties; 14 | readonly private IList> useProperties; 15 | 16 | public TypeMergerPolicy() { 17 | 18 | ignoredProperties = new List>(); 19 | useProperties = new List>(); 20 | } 21 | 22 | internal IList> IgnoredProperties { 23 | 24 | get { return this.ignoredProperties; } 25 | } 26 | 27 | internal IList> UseProperties { 28 | 29 | get { return this.useProperties; } 30 | } 31 | 32 | /// 33 | /// Specify a property to be ignored from a object being merged. 34 | /// 35 | /// The property of the object to be ignored as a Func. 36 | /// TypeMerger policy used in method chaining. 37 | public TypeMergerPolicy Ignore(Expression> ignoreProperty) { 38 | 39 | ignoredProperties.Add(GetObjectTypeAndProperty(ignoreProperty)); 40 | return this; 41 | } 42 | 43 | /// 44 | /// Specify a property to be ignored from a object being merged. 45 | /// 46 | /// The object instance 47 | /// The property of the object to be ignored as a Func. 48 | /// TypeMerger policy used in method chaining. 49 | public TypeMergerPolicy Ignore(T instance, string ignoreProperty) { 50 | ignoredProperties.Add(new Tuple(instance.GetType().Name, ignoreProperty)); 51 | return this; 52 | } 53 | 54 | /// 55 | /// Specify a property to use when there is a property name collision between objects being merged. 56 | /// 57 | /// 58 | /// TypeMerger policy used in method chaining. 59 | public TypeMergerPolicy Use(Expression> useProperty) { 60 | 61 | useProperties.Add(GetObjectTypeAndProperty(useProperty)); 62 | return this; 63 | } 64 | 65 | /// 66 | /// /// Merge two different object instances into a single object which is a super-set of the properties of both objects. 67 | /// If property name collision occurs and no policy has been created to specify which to use using the .Use() method the property value from 'values1' will be used. 68 | /// 69 | /// An object to be merged. 70 | /// An object to be merged. 71 | /// New object containing properties from both objects 72 | public Object Merge(object values1, object values2) { 73 | 74 | return TypeMerger.Merge(values1, values2, this); 75 | } 76 | 77 | /// 78 | /// Inspects the property specified to get the underlying Type and property name to be used during merging. 79 | /// 80 | /// The property to inspect as a Func Expression. 81 | /// 82 | private Tuple GetObjectTypeAndProperty(Expression> property) { 83 | 84 | var objType = string.Empty; 85 | var propName = string.Empty; 86 | 87 | try { 88 | if (property.Body is MemberExpression) { 89 | objType = ((MemberExpression)property.Body).Expression.Type.Name; 90 | propName = ((MemberExpression)property.Body).Member.Name; 91 | } else if (property.Body is UnaryExpression) { 92 | objType = ((MemberExpression)((UnaryExpression)property.Body).Operand).Expression.Type.Name; 93 | propName = ((MemberExpression)((UnaryExpression)property.Body).Operand).Member.Name; 94 | } else { 95 | throw new Exception("Expression type unknown."); 96 | } 97 | } catch (Exception ex) { 98 | throw new Exception("Error in TypeMergePolicy.GetObjectTypeAndProperty.", ex); 99 | } 100 | 101 | return new Tuple(objType, propName); 102 | } 103 | 104 | 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/TypeMerger/TypeMerger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using System.Reflection.Emit; 5 | using System.ComponentModel; 6 | using System.Linq; 7 | using System.Linq.Expressions; 8 | using System.Text; 9 | using System.Security.Cryptography; 10 | 11 | namespace TypeMerger { 12 | /// 13 | /// A Utility class used to merge the properties of heterogeneous objects 14 | /// 15 | public static class TypeMerger { 16 | 17 | //assembly/module builders 18 | private static AssemblyBuilder asmBuilder = null; 19 | private static ModuleBuilder modBuilder = null; 20 | private static TypeMergerPolicy typeMergerPolicy = null; 21 | 22 | //object type cache 23 | private static IDictionary anonymousTypes = new Dictionary(); 24 | 25 | //used for thread-safe access to Type Dictionary 26 | private static Object _syncLock = new Object(); 27 | 28 | /// 29 | /// Merge two different object instances into a single object which is a super-set of the properties of both objects. 30 | /// If property name collision occurs and no policy has been created to specify which to use using the .Use() method the property value from 'values1' will be used. 31 | /// 32 | /// An object to be merged. 33 | /// An object to be merged. 34 | /// New object containing properties from both objects 35 | public static Object Merge(Object values1, Object values2) { 36 | return Merge(values1, values2, null); 37 | } 38 | 39 | /// 40 | /// Used internally by the TypeMergerPolicy class method chaining for specifying a policy to use during merging. 41 | /// 42 | internal static Object Merge(object values1, object values2, TypeMergerPolicy policy) { 43 | //lock for thread safe writing 44 | lock (_syncLock) { 45 | typeMergerPolicy = policy; 46 | 47 | //create a name from the names of both Types 48 | var name = String.Format("{0}_{1}", values1.GetType(), 49 | values2.GetType()); 50 | 51 | if (typeMergerPolicy != null) { 52 | name += "_" + string.Join(",", typeMergerPolicy.IgnoredProperties.Select(x => String.Format("{0}_{1}", x.Item1, x.Item2))); 53 | } 54 | 55 | //now that we're inside the lock - check one more time 56 | var result = CreateInstance(name, values1, values2, out name); 57 | if (result != null) { 58 | typeMergerPolicy = null; 59 | return result; 60 | } 61 | 62 | //merge list of PropertyDescriptors for both objects 63 | var pdc = GetProperties(values1, values2); 64 | 65 | //make sure static properties are properly initialized 66 | InitializeAssembly(); 67 | 68 | //create the type definition 69 | var newType = CreateType(name, pdc); 70 | 71 | //add it to the cache 72 | anonymousTypes.Add(name, newType); 73 | 74 | //return an instance of the new Type 75 | result = CreateInstance(name, values1, values2, out name); 76 | typeMergerPolicy = null; 77 | return result; 78 | } 79 | } 80 | 81 | /// 82 | /// Specify a property to be ignored from a object being merged. 83 | /// 84 | /// The property of the object to be ignored as a Func. 85 | /// TypeMerger policy used in method chaining. 86 | public static TypeMergerPolicy Ignore(Expression> ignoreProperty) { 87 | return new TypeMergerPolicy().Ignore(ignoreProperty); 88 | } 89 | 90 | /// 91 | /// Specify a property to be ignored from a object being merged. 92 | /// 93 | /// The property of the object to be ignored as a String. 94 | /// TypeMerger policy used in method chaining. 95 | public static TypeMergerPolicy Ignore(T instance, string ignoreProperty) { 96 | return new TypeMergerPolicy().Ignore(instance, ignoreProperty); 97 | } 98 | 99 | /// 100 | /// Specify a property to use when there is a property name collision between objects being merged. 101 | /// 102 | /// 103 | /// TypeMerger policy used in method chaining. 104 | public static TypeMergerPolicy Use(Expression> useProperty) { 105 | return new TypeMergerPolicy().Use(useProperty); 106 | } 107 | 108 | /// 109 | /// Instantiates an instance of an existing Type from cache 110 | /// 111 | private static Object CreateInstance(String name, object values1, object values2, out string keyName) { 112 | 113 | Object newValues = null; 114 | // keyName is the name used to cache the type in the anonymousTypes dictionary. 115 | keyName = name; 116 | 117 | //check to see if type exists 118 | if (anonymousTypes.ContainsKey(name)) { 119 | 120 | //merge all values together into an array 121 | var allValues = MergeValues(values1, values2); 122 | 123 | //get type 124 | var type = anonymousTypes[name]; 125 | 126 | //make sure it isn't null for some reason 127 | if (type != null) { 128 | //create a new instance 129 | newValues = Activator.CreateInstance(type, allValues); 130 | } else { 131 | //remove null type entry 132 | lock (_syncLock) 133 | anonymousTypes.Remove(name); 134 | } 135 | } else if (name.Length > 1024) { 136 | // create a hash for the type name since it's too large 137 | keyName = CreateHash(name); 138 | return CreateInstance(keyName, values1, values2, out keyName); 139 | } 140 | 141 | //return values (if any) 142 | return newValues; 143 | } 144 | 145 | /// 146 | /// Merge PropertyDescriptors for both objects 147 | /// 148 | private static PropertyDescriptor[] GetProperties(object values1, object values2) { 149 | 150 | //dynamic list to hold merged list of properties 151 | var properties = new List(); 152 | 153 | //get the properties from both objects 154 | var pdc1 = TypeDescriptor.GetProperties(values1); 155 | var pdc2 = TypeDescriptor.GetProperties(values2); 156 | 157 | //add properties from values1 158 | for (int i = 0; i < pdc1.Count; i++) { 159 | if (typeMergerPolicy == null 160 | || (!typeMergerPolicy.IgnoredProperties.Contains(new Tuple(values1.GetType().Name, pdc1[i].Name)) 161 | && !typeMergerPolicy.UseProperties.Contains(new Tuple(values2.GetType().Name, pdc1[i].Name)))) 162 | properties.Add(pdc1[i]); 163 | } 164 | //add properties from values2 165 | for (int i = 0; i < pdc2.Count; i++) { 166 | if (typeMergerPolicy == null 167 | || (!typeMergerPolicy.IgnoredProperties.Contains(new Tuple(values2.GetType().Name, pdc2[i].Name)) 168 | && !typeMergerPolicy.UseProperties.Contains(new Tuple(values1.GetType().Name, pdc2[i].Name)))) 169 | properties.Add(pdc2[i]); 170 | } 171 | //return array 172 | return properties.ToArray(); 173 | } 174 | 175 | /// 176 | /// Get the type of each property 177 | /// 178 | private static Type[] GetTypes(PropertyDescriptor[] pdc) { 179 | 180 | var types = new List(); 181 | 182 | for (int i = 0; i < pdc.Length; i++) 183 | types.Add(pdc[i].PropertyType); 184 | 185 | return types.ToArray(); 186 | } 187 | 188 | /// 189 | /// Merge the values of the two types into an object array 190 | /// 191 | private static Object[] MergeValues(object values1, object values2) { 192 | 193 | var pdc1 = TypeDescriptor.GetProperties(values1); 194 | var pdc2 = TypeDescriptor.GetProperties(values2); 195 | 196 | var values = new List(); 197 | for (int i = 0; i < pdc1.Count; i++) { 198 | if (typeMergerPolicy == null 199 | || (!typeMergerPolicy.IgnoredProperties.Contains(new Tuple(values1.GetType().Name, pdc1[i].Name)) 200 | && !typeMergerPolicy.UseProperties.Contains(new Tuple(values2.GetType().Name, pdc1[i].Name)))) 201 | values.Add(pdc1[i].GetValue(values1)); 202 | } 203 | 204 | for (int i = 0; i < pdc2.Count; i++) { 205 | if (typeMergerPolicy == null 206 | || (!typeMergerPolicy.IgnoredProperties.Contains(new Tuple(values2.GetType().Name, pdc2[i].Name)) 207 | && !typeMergerPolicy.UseProperties.Contains(new Tuple(values1.GetType().Name, pdc2[i].Name)))) 208 | values.Add(pdc2[i].GetValue(values2)); 209 | } 210 | return values.ToArray(); 211 | } 212 | 213 | /// 214 | /// Initialize static objects 215 | /// 216 | private static void InitializeAssembly() { 217 | 218 | //check to see if we've already instantiated 219 | //the static objects 220 | if (asmBuilder == null) { 221 | //create a new dynamic assembly 222 | var assembly = new AssemblyName(); 223 | assembly.Name = "AnonymousTypeExtensions"; 224 | 225 | //get a module builder object 226 | asmBuilder = AssemblyBuilder.DefineDynamicAssembly(assembly, AssemblyBuilderAccess.Run); 227 | modBuilder = asmBuilder.DefineDynamicModule(asmBuilder.GetName().Name); 228 | } 229 | } 230 | 231 | /// 232 | /// Create a new Type definition from the list 233 | /// of PropertyDescriptors 234 | /// 235 | private static Type CreateType(String name, PropertyDescriptor[] pdc) { 236 | 237 | //create TypeBuilder 238 | var typeBuilder = CreateTypeBuilder(name); 239 | 240 | //get list of types for ctor definition 241 | var types = GetTypes(pdc); 242 | 243 | //create private fields for use w/in the ctor body and properties 244 | var fields = BuildFields(typeBuilder, pdc); 245 | 246 | //define/emit the Ctor 247 | BuildCtor(typeBuilder, fields, types); 248 | 249 | //define/emit the properties 250 | BuildProperties(typeBuilder, fields); 251 | 252 | //return Type definition 253 | return typeBuilder.CreateType(); 254 | } 255 | 256 | /// 257 | /// Create a type builder with the specified name 258 | /// 259 | private static TypeBuilder CreateTypeBuilder(string typeName) { 260 | 261 | var typeBuilder = modBuilder.DefineType(typeName, 262 | TypeAttributes.Public, 263 | typeof(object)); 264 | //return new type builder 265 | return typeBuilder; 266 | } 267 | 268 | /// 269 | /// Create a base64 string of a sha256 computed hash of a value 270 | /// 271 | private static string CreateHash(string value) { 272 | lock (_syncLock) { 273 | using (var sha256 = SHA256.Create()) { 274 | return Convert.ToBase64String(sha256.ComputeHash(Encoding.UTF8.GetBytes(value))); 275 | } 276 | } 277 | } 278 | 279 | /// 280 | /// Define/emit the ctor and ctor body 281 | /// 282 | private static void BuildCtor(TypeBuilder typeBuilder, FieldBuilder[] fields, Type[] types) { 283 | 284 | //define ctor() 285 | var ctor = typeBuilder.DefineConstructor( 286 | MethodAttributes.Public, 287 | CallingConventions.Standard, 288 | types 289 | ); 290 | 291 | //build ctor() 292 | var ctorGen = ctor.GetILGenerator(); 293 | 294 | //create ctor that will assign to private fields 295 | for (int i = 0; i < fields.Length; i++) { 296 | //load argument (parameter) 297 | ctorGen.Emit(OpCodes.Ldarg_0); 298 | ctorGen.Emit(OpCodes.Ldarg, (i + 1)); 299 | 300 | //store argument in field 301 | ctorGen.Emit(OpCodes.Stfld, fields[i]); 302 | } 303 | 304 | //return from ctor() 305 | ctorGen.Emit(OpCodes.Ret); 306 | } 307 | 308 | /// 309 | /// Define fields based on the list of PropertyDescriptors 310 | /// 311 | private static FieldBuilder[] BuildFields(TypeBuilder typeBuilder, PropertyDescriptor[] pdc) { 312 | 313 | var fields = new List(); 314 | 315 | //build/define fields 316 | for (int i = 0; i < pdc.Length; i++) { 317 | var pd = pdc[i]; 318 | 319 | //define field as '_[Name]' with the object's Type 320 | var field = typeBuilder.DefineField( 321 | String.Format("_{0}", pd.Name), 322 | pd.PropertyType, 323 | FieldAttributes.Private 324 | ); 325 | 326 | //add to list of FieldBuilder objects 327 | if (!fields.Contains(field)) 328 | fields.Add(field); 329 | } 330 | 331 | return fields.ToArray(); 332 | } 333 | 334 | /// 335 | /// Build a list of Properties to match the list of private fields 336 | /// 337 | private static void BuildProperties(TypeBuilder typeBuilder, FieldBuilder[] fields) { 338 | 339 | //build properties 340 | for (int i = 0; i < fields.Length; i++) { 341 | //remove '_' from name for public property name 342 | var propertyName = fields[i].Name.Substring(1); 343 | 344 | //define the property 345 | var property = typeBuilder.DefineProperty(propertyName, 346 | PropertyAttributes.None, fields[i].FieldType, null); 347 | 348 | //define 'Get' method only (anonymous types are read-only) 349 | var getMethod = typeBuilder.DefineMethod( 350 | String.Format("Get_{0}", propertyName), 351 | MethodAttributes.Public, 352 | fields[i].FieldType, 353 | Type.EmptyTypes 354 | ); 355 | 356 | //build 'Get' method 357 | var methGen = getMethod.GetILGenerator(); 358 | 359 | //method body 360 | methGen.Emit(OpCodes.Ldarg_0); 361 | //load value of corresponding field 362 | methGen.Emit(OpCodes.Ldfld, fields[i]); 363 | //return from 'Get' method 364 | methGen.Emit(OpCodes.Ret); 365 | 366 | //assign method to property 'Get' 367 | property.SetGetMethod(getMethod); 368 | 369 | //TODO: look into this.... 370 | ////define 'Set' method 371 | //MethodBuilder setMethod = typeBuilder.DefineMethod( 372 | // String.Format("Set_{0}", propertyName), 373 | // MethodAttributes.Public | MethodAttributes.SpecialName, 374 | // null, 375 | // new Type[] { fields[i].FieldType } 376 | //); 377 | 378 | //ILGenerator setMethodGenerator = setMethod.GetILGenerator(); 379 | //setMethodGenerator.Emit(OpCodes.Ldarg_0); // load this 380 | //setMethodGenerator.Emit(OpCodes.Ldarg_1); // load value 381 | //setMethodGenerator.Emit(OpCodes.Stfld, fields[i]); // store into field 382 | //setMethodGenerator.Emit(OpCodes.Ret); // return 383 | 384 | //property.SetSetMethod(setMethod); 385 | 386 | } 387 | } 388 | } 389 | } 390 | -------------------------------------------------------------------------------- /src/TypeMerger.Tests/TypeMergerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | using FluentAssertions; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace TypeMerger.Tests { 9 | 10 | public class TypeMergerTests { 11 | 12 | [Fact] 13 | public void Test_Basic_Merge_Types() { 14 | 15 | 16 | var obj1 = new { Property1 = "1", Property2 = "2", Property3 = "3", Property4 = "4", Property5 = "5", Property6 = "6", Property7 = "7", Property8 = "8", Property9 = "9", Property10 = "10" }; 17 | var obj2 = new { Property11 = "11", Property12 = "12", Property13 = "13", Property14 = "14", Property15 = "15", Property16 = "16", Property17 = "17", Property18 = "18", Property19 = "19", Property20 = "20" }; 18 | 19 | var result = TypeMerger.Merge(obj1, obj2); 20 | 21 | result.GetType().GetProperties().Length.Should().Be(20); 22 | 23 | result.GetType().GetProperty("Property1").GetValue(result).Should().Be("1"); 24 | result.GetType().GetProperty("Property2").GetValue(result).Should().Be("2"); 25 | result.GetType().GetProperty("Property3").GetValue(result).Should().Be("3"); 26 | result.GetType().GetProperty("Property4").GetValue(result).Should().Be("4"); 27 | result.GetType().GetProperty("Property5").GetValue(result).Should().Be("5"); 28 | result.GetType().GetProperty("Property6").GetValue(result).Should().Be("6"); 29 | result.GetType().GetProperty("Property7").GetValue(result).Should().Be("7"); 30 | result.GetType().GetProperty("Property8").GetValue(result).Should().Be("8"); 31 | result.GetType().GetProperty("Property9").GetValue(result).Should().Be("9"); 32 | result.GetType().GetProperty("Property10").GetValue(result).Should().Be("10"); 33 | result.GetType().GetProperty("Property11").GetValue(result).Should().Be("11"); 34 | result.GetType().GetProperty("Property12").GetValue(result).Should().Be("12"); 35 | result.GetType().GetProperty("Property13").GetValue(result).Should().Be("13"); 36 | result.GetType().GetProperty("Property14").GetValue(result).Should().Be("14"); 37 | result.GetType().GetProperty("Property15").GetValue(result).Should().Be("15"); 38 | result.GetType().GetProperty("Property16").GetValue(result).Should().Be("16"); 39 | result.GetType().GetProperty("Property17").GetValue(result).Should().Be("17"); 40 | result.GetType().GetProperty("Property18").GetValue(result).Should().Be("18"); 41 | result.GetType().GetProperty("Property19").GetValue(result).Should().Be("19"); 42 | result.GetType().GetProperty("Property20").GetValue(result).Should().Be("20"); 43 | } 44 | 45 | [Fact] 46 | public void Merge_Types_with_Ignore_Policy() { 47 | 48 | var obj1 = new { Property1 = "value1", Property2 = "value1" }; 49 | var obj2 = new { Property1 = "value2", Property4 = "value4" }; 50 | 51 | var result = TypeMerger.Ignore(() => obj1.Property1) 52 | .Ignore(() => obj2.Property4) 53 | .Merge(obj1, obj2); 54 | 55 | result.GetType().GetProperties().Length.Should().Be(2); 56 | result.GetType().GetProperty("Property1").GetValue(result).Should().Be("value2"); 57 | result.GetType().GetProperty("Property2").Should().NotBeNull(); 58 | 59 | } 60 | 61 | [Fact] 62 | public void Merge_Types_with_Ignore_Policy_By_Name() { 63 | 64 | var obj1 = new { Property1 = "value1", Property2 = "value1" }; 65 | var obj2 = new { Property1 = "value2", Property4 = "value4" }; 66 | 67 | var result = TypeMerger.Ignore(obj1, nameof(obj1.Property1)) 68 | .Ignore(obj2, nameof(obj2.Property4)) 69 | .Merge(obj1, obj2); 70 | 71 | result.GetType().GetProperties().Length.Should().Be(2); 72 | result.GetType().GetProperty("Property1").GetValue(result).Should().Be("value2"); 73 | result.GetType().GetProperty("Property2").Should().NotBeNull(); 74 | 75 | } 76 | 77 | [Fact] 78 | public void Merge_Types_with_Name_Collision() { 79 | 80 | var obj1 = new { Property1 = "value1", Property2 = "2" }; 81 | var obj2 = new { Property1 = "value2", Property3 = "3" }; 82 | 83 | var result1 = TypeMerger.Merge(obj1, obj2); 84 | 85 | result1.GetType().GetProperties().Length.Should().Be(3); 86 | result1.GetType().GetProperty("Property1").GetValue(result1).Should().Be("value1"); 87 | result1.GetType().GetProperty("Property3").GetValue(result1).Should().Be("3"); 88 | 89 | var result2 = TypeMerger.Merge(obj2, obj1); 90 | 91 | result2.GetType().GetProperties().Length.Should().Be(3); 92 | result2.GetType().GetProperty("Property1").GetValue(result2).Should().Be("value2"); 93 | result2.GetType().GetProperty("Property3").GetValue(result2).Should().Be("3"); 94 | 95 | } 96 | 97 | [Fact] 98 | public void Test_Use_Method_to_Handle_Name_Collision_Priority() { 99 | 100 | var obj1 = new { Property1 = "value1", Property2 = "2" }; 101 | var obj2 = new { Property1 = "value2", Property3 = "3" }; 102 | 103 | var result1 = TypeMerger.Use(() => obj2.Property1) 104 | .Merge(obj1, obj2); 105 | 106 | result1.GetType().GetProperties().Length.Should().Be(3); 107 | result1.GetType().GetProperty("Property1").GetValue(result1).Should().Be("value2"); 108 | 109 | } 110 | 111 | // This test will fail... Should we support this? 112 | // What causes this to fail is that we are using 2 objects that are the same type and TypeMerger.MergeValues can't handle that 113 | // [Fact] 114 | // public void Test_Use_Method_with_same_object_types() { 115 | 116 | // var obj1 = new { Property1 = "value1", Property2 = "2", Property3 = "4" }; 117 | // var obj2 = new { Property1 = "value2", Property2 = "3", Property3 = "5"}; 118 | 119 | // var result1 = TypeMerger.Use(() => obj2.Property2) 120 | // .Merge(obj1, obj2); 121 | 122 | // result1.GetType().GetProperties().Length.Should().Be(3); 123 | // result1.GetType().GetProperty("Property2").GetValue(result1).Should().Be("3"); 124 | 125 | // } 126 | 127 | 128 | [Fact] 129 | public void Test_Ignore_and_Use_Methods_used_in_Single_Merge_Policy() { 130 | 131 | var obj1 = new { Property1 = "value1", Property2 = "2" }; 132 | var obj2 = new { Property1 = "value2", Property3 = "3" }; 133 | 134 | var result = TypeMerger.Use(() => obj2.Property1) 135 | .Ignore(() => obj2.Property3) 136 | .Merge(obj1, obj2); 137 | 138 | result.GetType().GetProperties().Length.Should().Be(2); 139 | result.GetType().GetProperty("Property1").GetValue(result).Should().Be(obj2.Property1); 140 | result.GetType().GetProperty("Property2").GetValue(result).Should().Be(obj1.Property2); 141 | 142 | } 143 | 144 | [Fact] 145 | public void Test_Multiple_Type_Creation_from_Same_Anonymous_Types_Sources() { 146 | 147 | var obj1 = new { Property1 = "value1", Property2 = "2" }; 148 | var obj2 = new { Property1 = "value2", Property3 = "3" }; 149 | 150 | var result1 = TypeMerger.Merge(obj1, obj2); 151 | 152 | result1.GetType().GetProperties().Length.Should().Be(3); 153 | result1.GetType().GetProperty("Property1").GetValue(result1).Should().Be("value1"); 154 | 155 | var result2 = TypeMerger.Ignore(() => obj1.Property2) 156 | .Merge(obj1, obj2); 157 | 158 | result2.GetType().GetProperties().Length.Should().Be(2); 159 | result2.GetType().GetProperty("Property3").GetValue(result2).Should().Be("3"); 160 | } 161 | 162 | [Fact] 163 | public void Test_Type_Creation_from_Concrete_Classes() { 164 | 165 | var class1 = new TestClass1 { 166 | Name = "Foo", 167 | Description = "Test Class Instance", 168 | Number = 10, 169 | SubClass = new TestSubClass1 { 170 | Internal = "Inside" 171 | } 172 | }; 173 | 174 | var class2 = new TestClass2 { 175 | FullName = "Test Class 2", 176 | FullAddress = "123 Main St.", 177 | Total = 28 178 | }; 179 | 180 | var result = TypeMerger.Ignore(() => class1.Name) 181 | .Ignore(() => class2.Total) 182 | .Merge(class1, class2); 183 | 184 | result.GetType().GetProperties().Length.Should().Be(5); 185 | 186 | result.GetType().GetProperty("SubClass").PropertyType.Should().Be(typeof(TestSubClass1)); 187 | result.GetType().GetProperty("SubClass").GetValue(result) 188 | .GetType().GetProperty("Internal") 189 | .GetValue(result.GetType().GetProperty("SubClass") 190 | .GetValue(result)).Should().Be(class1.SubClass.Internal); 191 | 192 | } 193 | 194 | [Fact] 195 | public void Test_Class_with_Built_in_Types() { 196 | 197 | var obj1 = new { Property1 = "value1", Property2 = "2" }; 198 | var obj2 = new AllBuiltInTypes { 199 | ByteType = Byte.MaxValue, 200 | SByteType = SByte.MaxValue, 201 | Int32Type = Int32.MaxValue, 202 | UInt32Type = UInt32.MaxValue, 203 | Int16Type = Int16.MaxValue, 204 | UInt16Type = UInt16.MaxValue, 205 | Int64Type = Int64.MaxValue, 206 | UInt64Type = UInt64.MaxValue, 207 | SingleType = Single.MaxValue, 208 | DoubleType = Double.MaxValue, 209 | DecimalType = 300.5m, 210 | BooleanType = false, 211 | CharType = '\x0058', 212 | ObjectType = new { Test = 1 }, 213 | StringType = "foo", 214 | DateTimeType = DateTime.Now, 215 | EnumType = TestEnum.Val1 216 | }; 217 | 218 | var result1 = TypeMerger.Merge(obj1, obj2); 219 | 220 | result1.GetType().GetProperties().Length.Should().Be(19); 221 | 222 | result1.GetType().GetProperty("Property1").GetValue(result1).Should().Be(obj1.Property1); 223 | result1.GetType().GetProperty("Property2").GetValue(result1).Should().Be(obj1.Property2); 224 | result1.GetType().GetProperty("ByteType").GetValue(result1).Should().Be(obj2.ByteType); 225 | result1.GetType().GetProperty("SByteType").GetValue(result1).Should().Be(obj2.SByteType); 226 | result1.GetType().GetProperty("Int32Type").GetValue(result1).Should().Be(obj2.Int32Type); 227 | result1.GetType().GetProperty("UInt32Type").GetValue(result1).Should().Be(obj2.UInt32Type); 228 | result1.GetType().GetProperty("Int16Type").GetValue(result1).Should().Be(obj2.Int16Type); 229 | result1.GetType().GetProperty("UInt16Type").GetValue(result1).Should().Be(obj2.UInt16Type); 230 | result1.GetType().GetProperty("Int64Type").GetValue(result1).Should().Be(obj2.Int64Type); 231 | result1.GetType().GetProperty("UInt64Type").GetValue(result1).Should().Be(obj2.UInt64Type); 232 | result1.GetType().GetProperty("SingleType").GetValue(result1).Should().Be(obj2.SingleType); 233 | result1.GetType().GetProperty("DoubleType").GetValue(result1).Should().Be(obj2.DoubleType); 234 | result1.GetType().GetProperty("DecimalType").GetValue(result1).Should().Be(obj2.DecimalType); 235 | result1.GetType().GetProperty("BooleanType").GetValue(result1).Should().Be(obj2.BooleanType); 236 | result1.GetType().GetProperty("CharType").GetValue(result1).Should().Be(obj2.CharType); 237 | result1.GetType().GetProperty("ObjectType").GetValue(result1).Should().Be(obj2.ObjectType); 238 | result1.GetType().GetProperty("SingleType").GetValue(result1).Should().Be(obj2.SingleType); 239 | result1.GetType().GetProperty("DateTimeType").GetValue(result1).Should().Be(obj2.DateTimeType); 240 | result1.GetType().GetProperty("EnumType").GetValue(result1).Should().Be(TestEnum.Val1); 241 | 242 | } 243 | 244 | [Fact] 245 | public void Test_Derived_Class_with_Ignored_Base_Class_Property() { 246 | 247 | var obj1 = new DerivedClass { 248 | Name = "foo" 249 | }; 250 | 251 | var obj2 = new { Value = 123 }; 252 | 253 | var result = TypeMerger.Ignore(() => obj1.Name).Merge(obj1, obj2); 254 | 255 | result.GetType().GetProperties().Length.Should().Be(1); 256 | 257 | } 258 | 259 | [Fact] 260 | public void Test_Anonymous_Types_With_Names_Greater_Than_1024_Should_Use_Hash() { 261 | 262 | // Arrange 263 | var count = 30; 264 | object result = null; 265 | 266 | var l = Enumerable.Range(0, count - 1).Select(n => { 267 | var k = n * 3; 268 | 269 | return new { 270 | Prop1 = k + 1, 271 | Prop2 = k + 2, 272 | Prop3 = k + 3, 273 | }; 274 | }).ToList(); 275 | 276 | // Act 277 | l.ForEach(o => result = TypeMerger.Merge(result ?? new { }, o)); 278 | 279 | // Assert 280 | result.Should().NotBeNull(); 281 | 282 | var anonymousTypes = typeof(TypeMerger).GetField("anonymousTypes", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static).GetValue(null) as Dictionary; 283 | 284 | anonymousTypes.Keys.Last().IndexOf('_').Should().BeGreaterThan(5); 285 | } 286 | 287 | [Fact] 288 | public void Test_TypeMergerPolicy_Maintained_On_High_Volume() { 289 | 290 | // Arrange 291 | 292 | // setup some objects 293 | var obj1 = new { Prop1 = 1, Prop2 = 2, Prop3 = 3 }; 294 | var obj2 = new { Prop1 = 4, Prop2 = 5, Prop4 = 6 }; 295 | var obj3 = new { Prop1 = 7, Prop2 = 8, Prop5 = 9 }; 296 | var obj4 = new { Prop1 = 10, Prop2 = 11, Prop6 = 12 }; 297 | var obj5 = new { Prop1 = 13, Prop2 = 14, Prop7 = 15 }; 298 | var obj6 = new { Prop1 = 16, Prop2 = 17, Prop8 = 18 }; 299 | var obj7 = new { Prop1 = 19, Prop2 = 20, Prop9 = 21 }; 300 | var obj8 = new { Prop1 = 22, Prop2 = 23, Prop10 = 24 }; 301 | var obj9 = new { Prop1 = 25, Prop2 = 26, Prop11 = 27 }; 302 | var obj10 = new { Prop1 = 28, Prop2 = 29, Prop12 = 30 }; 303 | 304 | // store them in a list of tuple 305 | var objects = new List> { 306 | new Tuple(obj1, obj2, TypeMerger.Use(() => obj2.Prop2), new { Prop1 = 1, Prop2 = 5 }), 307 | new Tuple(obj2, obj3, TypeMerger.Use(() => obj3.Prop1), new { Prop1 = 7, Prop2 = 5}), 308 | new Tuple(obj3, obj4, TypeMerger.Use(() => obj4.Prop2), new { Prop1 = 7, Prop2 = 11}), 309 | new Tuple(obj4, obj5, TypeMerger.Use(() => obj5.Prop1), new { Prop1 = 13, Prop2 = 11}), 310 | new Tuple(obj5, obj6, TypeMerger.Use(() => obj6.Prop2), new { Prop1 = 13, Prop2 = 17}), 311 | new Tuple(obj6, obj7, TypeMerger.Use(() => obj7.Prop1), new { Prop1 = 19, Prop2 = 17}), 312 | new Tuple(obj7, obj8, TypeMerger.Use(() => obj8.Prop2), new { Prop1 = 19, Prop2 = 23}), 313 | new Tuple(obj8, obj9, TypeMerger.Use(() => obj9.Prop1), new { Prop1 = 25, Prop2 = 23}), 314 | new Tuple(obj9, obj10, TypeMerger.Use(() => obj10.Prop2), new { Prop1 = 25, Prop2 = 29}), 315 | new Tuple(obj10, obj1, TypeMerger.Use(() => obj1.Prop1), new { Prop1 = 1, Prop2 = 29}) 316 | }; 317 | 318 | var results = new Object[10]; 319 | 320 | // Act 321 | Parallel.For(0, 9, (i, loopState) => { 322 | results[i] = objects[(int)i].Item3.Merge(objects[(int)i].Item1, objects[(int)i].Item2); 323 | }); 324 | 325 | // Assert 326 | for (int i = 0; i < 9; i++) { 327 | for (int j = 1; j < 3; j++) { 328 | var val1 = results[i].GetType().GetProperty($"Prop{j}").GetValue(results[i]); 329 | var val2 = objects[i].Item4.GetType().GetProperty($"Prop{j}").GetValue(objects[i].Item4); 330 | val1.Should().Be(val2); 331 | } 332 | } 333 | } 334 | 335 | } 336 | 337 | public class BaseClass { 338 | public string Name { get; set; } 339 | } 340 | 341 | public class DerivedClass : BaseClass { } 342 | 343 | public class TestClass1 { 344 | 345 | public string Name { get; set; } 346 | public string Description { get; set; } 347 | public int Number { get; set; } 348 | public TestSubClass1 SubClass { get; set; } 349 | 350 | } 351 | 352 | public class TestClass2 { 353 | 354 | public string FullName { get; set; } 355 | public string FullAddress { get; set; } 356 | public int Total { get; set; } 357 | } 358 | 359 | public class TestSubClass1 { 360 | public string Internal { get; set; } 361 | } 362 | 363 | public class AllBuiltInTypes { 364 | public Byte ByteType { get; set; } 365 | public SByte SByteType { get; set; } 366 | public Int32 Int32Type { get; set; } 367 | public UInt32 UInt32Type { get; set; } 368 | public Int16 Int16Type { get; set; } 369 | public UInt16 UInt16Type { get; set; } 370 | public Int64 Int64Type { get; set; } 371 | public UInt64 UInt64Type { get; set; } 372 | public Single SingleType { get; set; } 373 | public Double DoubleType { get; set; } 374 | public Char CharType { get; set; } 375 | public Boolean BooleanType { get; set; } 376 | public Object ObjectType { get; set; } 377 | public String StringType { get; set; } 378 | public Decimal DecimalType { get; set; } 379 | public DateTime DateTimeType { get; set; } 380 | public Enum EnumType { get; set; } 381 | } 382 | 383 | public enum TestEnum { Val1 = 1, Val2, Val3, Val4, Val5 }; 384 | } 385 | --------------------------------------------------------------------------------