├── .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