Skip to content

Commit 0e80222

Browse files
committed
added FastDeepCloner to benchmarks
1 parent a93e28e commit 0e80222

File tree

4 files changed

+304
-3
lines changed

4 files changed

+304
-3
lines changed

DeepCloner.Tests/DeepCloner.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
<Compile Include="CloneExtensionsSpec.cs" />
6969
<Compile Include="ConstructorsSpec.cs" />
7070
<Compile Include="GenericsSpec.cs" />
71+
<Compile Include="Imported\FastDeepCloner.cs" />
7172
<Compile Include="InheritanceSpec.cs" />
7273
<Compile Include="LoopCheckSpec.cs" />
7374
<Compile Include="Objects\DoableStruct1.cs" />
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
// copied from https://raw.githubusercontent.com/Alenah091/FastDeepCloner/master/FastDeepCloner.cs because I need .NET 4.0 for tests
2+
using System;
3+
using System.Collections;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Reflection;
7+
using System.Runtime.Serialization;
8+
9+
namespace Force.DeepCloner.Tests.Imported
10+
{
11+
/// <summary>
12+
/// Supports cloning, which creates a new instance of a class with the same value as an existing instance.
13+
/// Used to deep clone objects, whether they are serializable or not.
14+
/// </summary>
15+
public class FastDeepCloner
16+
{
17+
#region Private fields
18+
private const BindingFlags Binding = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy;
19+
private Type _primaryType;
20+
private object _desireObjectToBeCloned;
21+
private int _length;
22+
private bool _isArray;
23+
private bool _isDictionary;
24+
private bool _isList;
25+
private int _rank;
26+
private bool? _initPublicOnly;
27+
private Type _ignorePropertiesWithAttribute;
28+
private static IDictionary<Type, List<FieldInfo>> _cachedFields;
29+
private static IDictionary<Type, List<PropertyInfo>> _cachedPropertyInfo;
30+
private FieldType _fieldType;
31+
private IDictionary<string, bool> _alreadyCloned;
32+
#endregion
33+
34+
#region Constructors
35+
public FastDeepCloner(object desireObjectToBeCloned, FieldType fieldType)
36+
{
37+
if (desireObjectToBeCloned == null)
38+
{
39+
throw new ArgumentNullException("desireObjectToBeCloned");
40+
}
41+
42+
DataBind(desireObjectToBeCloned, fieldType, null, false);
43+
}
44+
45+
public FastDeepCloner(object desireObjectToBeCloned, FieldType fieldType = FieldType.FieldInfo, Type ignorePropertiesWithAttribute = null, bool? initPublicOnly = null)
46+
{
47+
if (desireObjectToBeCloned == null)
48+
{
49+
throw new ArgumentNullException("desireObjectToBeCloned");
50+
}
51+
52+
DataBind(desireObjectToBeCloned, fieldType, ignorePropertiesWithAttribute, initPublicOnly);
53+
}
54+
#endregion
55+
56+
#region Public method clone
57+
/// <summary>
58+
/// Creates a new object that is a copy of the current instance.
59+
/// </summary>
60+
/// <returns>A new object that is a copy of this instance.</returns>
61+
public object Clone()
62+
{
63+
return DeepClone();
64+
}
65+
66+
/// <summary>
67+
/// Creates a new object that is a copy of the current instance.
68+
/// </summary>
69+
/// <returns>A new object that is a copy of this instance.</returns>
70+
public T Clone<T>()
71+
{
72+
return (T)DeepClone();
73+
}
74+
#endregion
75+
76+
#region Private method deep clone
77+
private void DataBind(object desireObjectToBeCloned, FieldType fieldType = FieldType.FieldInfo, Type ignorePropertiesWithAttribute = null, bool? initPublicOnly = null, IDictionary<string, bool> alreadyCloned = null)
78+
{
79+
if (desireObjectToBeCloned == null)
80+
return;
81+
if (_cachedFields == null)
82+
_cachedFields = new Dictionary<Type, List<FieldInfo>>();
83+
if (_cachedPropertyInfo == null)
84+
_cachedPropertyInfo = new Dictionary<Type, List<PropertyInfo>>();
85+
86+
_alreadyCloned = alreadyCloned ?? new Dictionary<string, bool>();
87+
_ignorePropertiesWithAttribute = ignorePropertiesWithAttribute;
88+
_primaryType = desireObjectToBeCloned.GetType();
89+
_desireObjectToBeCloned = desireObjectToBeCloned;
90+
_isArray = _primaryType.IsArray;
91+
_initPublicOnly = initPublicOnly;
92+
_fieldType = fieldType;
93+
94+
if (_isArray)
95+
{
96+
var array = (Array)desireObjectToBeCloned;
97+
_length = array.Length;
98+
_rank = array.Rank;
99+
}
100+
else if ((desireObjectToBeCloned as IList) != null)
101+
_isList = true;
102+
else if (typeof(IDictionary).IsAssignableFrom(_primaryType))
103+
_isDictionary = true;
104+
}
105+
106+
/// <summary>
107+
/// Clone the object properties and its children recursively.
108+
/// </summary>
109+
/// <returns></returns>
110+
private object DeepClone()
111+
{
112+
if (_desireObjectToBeCloned == null)
113+
return null;
114+
// If the item is array of type more than one dimension then use Array.Clone
115+
if (_isArray && _rank > 1)
116+
return ((Array)_desireObjectToBeCloned).Clone();
117+
118+
object tObject;
119+
// Clone IList or Array
120+
if (_isArray || _isList)
121+
{
122+
tObject = _isArray ? Array.CreateInstance(_primaryType.GetElementType(), _length) : Activator.CreateInstance(typeof(List<>).MakeGenericType(_primaryType.GetProperties().Last().PropertyType));
123+
var i = 0;
124+
foreach (var item in (IList)_desireObjectToBeCloned)
125+
{
126+
object clonedIteam = null;
127+
if (item != null)
128+
{
129+
var underlyingSystemType = item.GetType().UnderlyingSystemType;
130+
clonedIteam = (item is string || !underlyingSystemType.IsClass || IsInternalType(underlyingSystemType))
131+
? item
132+
: new FastDeepCloner(item, _fieldType, _ignorePropertiesWithAttribute, _initPublicOnly, _alreadyCloned).DeepClone();
133+
}
134+
if (!_isArray)
135+
((IList)tObject).Add(clonedIteam);
136+
else
137+
((Array)tObject).SetValue(clonedIteam, i);
138+
139+
i++;
140+
}
141+
}
142+
else if (_isDictionary) // Clone IDictionary
143+
{
144+
tObject = Activator.CreateInstance(_primaryType);
145+
var dictionary = (IDictionary)_desireObjectToBeCloned;
146+
foreach (var key in dictionary.Keys)
147+
{
148+
var item = dictionary[key];
149+
object clonedIteam = null;
150+
if (item != null)
151+
{
152+
var underlyingSystemType = item.GetType().UnderlyingSystemType;
153+
clonedIteam = (item is string || !underlyingSystemType.IsClass || IsInternalType(underlyingSystemType))
154+
? item
155+
: new FastDeepCloner(item, _fieldType, _ignorePropertiesWithAttribute, _initPublicOnly, _alreadyCloned).DeepClone();
156+
}
157+
((IDictionary)tObject).Add(key, clonedIteam);
158+
}
159+
}
160+
else
161+
{
162+
// Create an empty object and ignore its constructor.
163+
tObject = FormatterServices.GetUninitializedObject(_primaryType);
164+
var fullPath = _primaryType.Name;
165+
if (_fieldType == FieldType.PropertyInfo)
166+
{
167+
if (!_cachedPropertyInfo.ContainsKey(_primaryType))
168+
{
169+
var properties = new List<PropertyInfo>();
170+
if (_primaryType.BaseType != null && _primaryType.BaseType.Name != "Object")
171+
{
172+
properties.AddRange(_primaryType.BaseType.GetProperties(Binding));
173+
properties.AddRange(_primaryType.GetProperties(Binding | BindingFlags.DeclaredOnly));
174+
}
175+
else properties.AddRange(_primaryType.GetProperties(Binding));
176+
177+
_cachedPropertyInfo.Add(_primaryType, properties);
178+
if (_ignorePropertiesWithAttribute != null)
179+
_cachedPropertyInfo[_primaryType].RemoveAll(
180+
x => x.GetCustomAttributes(_ignorePropertiesWithAttribute, false).FirstOrDefault() != null);
181+
}
182+
}
183+
else if (!_cachedFields.ContainsKey(_primaryType))
184+
{
185+
var properties = new List<FieldInfo>();
186+
if (_primaryType.BaseType != null && _primaryType.BaseType.Name != "Object")
187+
{
188+
properties.AddRange(_primaryType.BaseType.GetFields(Binding));
189+
properties.AddRange(_primaryType.GetFields(Binding | BindingFlags.DeclaredOnly));
190+
}
191+
else properties.AddRange(_primaryType.GetFields(Binding));
192+
193+
_cachedFields.Add(_primaryType, properties);
194+
if (_ignorePropertiesWithAttribute != null)
195+
_cachedFields[_primaryType].RemoveAll(
196+
x => x.GetCustomAttributes(_ignorePropertiesWithAttribute, false).FirstOrDefault() != null);
197+
}
198+
199+
if (_fieldType == FieldType.FieldInfo)
200+
{
201+
foreach (var property in _cachedFields[_primaryType])
202+
{
203+
// Validate if the property is a writable one.
204+
if (property.IsInitOnly || property.FieldType == typeof(System.IntPtr))
205+
continue;
206+
if (_initPublicOnly.HasValue && _initPublicOnly.Value && !property.IsPublic)
207+
continue;
208+
if (_alreadyCloned.ContainsKey(fullPath + property.Name))
209+
continue;
210+
var value = property.GetValue(_desireObjectToBeCloned);
211+
if (value == null)
212+
continue;
213+
214+
if (!property.FieldType.IsClass || value is string)
215+
property.SetValue(tObject, value);
216+
else
217+
{
218+
_alreadyCloned.Add(fullPath + property.Name, true);
219+
property.SetValue(tObject,
220+
new FastDeepCloner(value, _fieldType, _ignorePropertiesWithAttribute, _initPublicOnly,
221+
_alreadyCloned).DeepClone());
222+
}
223+
}
224+
}
225+
else
226+
{
227+
foreach (var property in _cachedPropertyInfo[_primaryType])
228+
{
229+
// Validate if the property is a writable one.
230+
if (!property.CanWrite || !property.CanRead || property.PropertyType == typeof(System.IntPtr))
231+
continue;
232+
if (_alreadyCloned.ContainsKey(fullPath + property.Name))
233+
continue;
234+
var value = property.GetValue(_desireObjectToBeCloned, null);
235+
if (value == null)
236+
continue;
237+
238+
if (!property.PropertyType.IsClass || value is string)
239+
property.SetValue(tObject, value, null);
240+
else
241+
{
242+
_alreadyCloned.Add(fullPath + property.Name, true);
243+
property.SetValue(tObject,
244+
new FastDeepCloner(value, _fieldType, _ignorePropertiesWithAttribute, _initPublicOnly,
245+
_alreadyCloned).DeepClone(), null);
246+
}
247+
}
248+
}
249+
}
250+
251+
return tObject;
252+
}
253+
#endregion
254+
255+
private FastDeepCloner(object desireObjectToBeCloned, FieldType fielType = FieldType.FieldInfo, Type ignorePropertiesWithAttribute = null, bool? initPublicOnly = null, IDictionary<string, bool> alreadyCloned = null)
256+
{
257+
DataBind(desireObjectToBeCloned, fielType, ignorePropertiesWithAttribute, initPublicOnly, alreadyCloned);
258+
}
259+
260+
/// <summary>
261+
/// Determines if the specified type is an internal type.
262+
/// </summary>
263+
/// <param name="underlyingSystemType"></param>
264+
/// <returns><c>true</c> if type is internal, else <c>false</c>.</returns>
265+
private static bool IsInternalType(Type underlyingSystemType)
266+
{
267+
return underlyingSystemType == typeof(string) ||
268+
underlyingSystemType == typeof(decimal) ||
269+
underlyingSystemType == typeof(int) ||
270+
underlyingSystemType == typeof(double) ||
271+
underlyingSystemType == typeof(float) ||
272+
underlyingSystemType == typeof(bool) ||
273+
underlyingSystemType == typeof(long) ||
274+
underlyingSystemType == typeof(DateTime) ||
275+
underlyingSystemType == typeof(ushort) ||
276+
underlyingSystemType == typeof(short) ||
277+
underlyingSystemType == typeof(sbyte) ||
278+
underlyingSystemType == typeof(byte) ||
279+
underlyingSystemType == typeof(ulong) ||
280+
underlyingSystemType == typeof(uint) ||
281+
underlyingSystemType == typeof(char) ||
282+
underlyingSystemType == typeof(TimeSpan);
283+
}
284+
}
285+
286+
public enum FieldType
287+
{
288+
FieldInfo,
289+
PropertyInfo
290+
}
291+
}

DeepCloner.Tests/PerformanceSpec.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88

99
using CloneExtensions;
1010

11-
using NClone;
11+
using Force.DeepCloner.Tests.Imported;
1212

13-
using NUnit.Framework;
13+
using NClone;
1414

1515
using Nuclex.Cloning;
1616

17+
using NUnit.Framework;
18+
1719
namespace Force.DeepCloner.Tests
1820
{
1921
[TestFixture]
@@ -92,6 +94,8 @@ public void Test_Construct_Variants(bool isSafe)
9294
// for (var i = 0; i < 1000; i++) ReflectionCloner.DeepFieldClone(c1);
9395
for (var i = 0; i < 1000; i++) GeorgeCloney.CloneExtension.DeepCloneWithoutSerialization(c1);
9496

97+
for (var i = 0; i < 1000; i++) new FastDeepCloner(c1, FieldType.FieldInfo).Clone();
98+
9599
// test
96100
var sw = new Stopwatch();
97101
sw.Start();
@@ -125,6 +129,10 @@ public void Test_Construct_Variants(bool isSafe)
125129
Console.WriteLine("GeorgeCloney: " + sw.ElapsedMilliseconds);
126130
sw.Restart();
127131

132+
for (var i = 0; i < 1000000; i++) new FastDeepCloner(c1, FieldType.FieldInfo).Clone();
133+
Console.WriteLine("FastDeepCloner: " + sw.ElapsedMilliseconds);
134+
sw.Restart();
135+
128136
// inaccurate variant, but test should complete in reasonable time
129137
for (var i = 0; i < 100000; i++) CloneViaFormatter(c1);
130138
Console.WriteLine("Binary Formatter: " + (sw.ElapsedMilliseconds * 10));

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Deep cloning any object:
2525
With a reference to same object:
2626
```
2727
// public class Tree { public Tree ParentTree; }
28-
var t = new Tree();
28+
var t = new Tree();
2929
t.ParentTree = t;
3030
var cloned = t.DeepClone();
3131
Console.WriteLine(cloned.ParentTree == cloned); // True
@@ -94,6 +94,7 @@ DeepClone / Safe | 267 | Safe variant based on on expressions
9494
[Clone.Behave!](https://github.com/kalisohn/CloneBehave) | 8551 | Very slow, also has a dependency to fasterflect
9595
[GeorgeCloney](https://github.com/laazyj/GeorgeCloney) | 1996 | Has a lot limitations and prefers to clone through BinaryFormatter
9696
[Nuclex.Cloning](https://github.com/junweilee/Nuclex.Cloning/) | n/a | Crashed with a null reference exception
97+
[.Net Object FastDeepCloner](https://github.com/Alenah091/FastDeepCloner/) | 1882 | Not analyzed carefully, only for .NET 4.5.1 or higher
9798
BinaryFormatter | 15000 | Another way of deep object cloning through serializing/deserializing object. Instead of Json serializers - it maintains full graph of serializing objects and also do not call any method for cloning object. But due serious overhead, this variant is very slow
9899

99100
**Shallow cloning**

0 commit comments

Comments
 (0)