Skip to content

Commit 3a8dcc2

Browse files
committed
more tests
comparison with other libraries fixed multiple copying of objects fixed safe deep cloning of readonly fields (through some hacks)
1 parent 992bb95 commit 3a8dcc2

11 files changed

+235
-24
lines changed

DeepCloner.Tests/ArraysSpec.cs

+26
Original file line numberDiff line numberDiff line change
@@ -243,5 +243,31 @@ public void NonZero_Based_MultiDim_Array_Should_Be_Cloned()
243243
Assert.That(clone.GetValue(1, 1), Is.EqualTo(1));
244244
Assert.That(clone.GetValue(2, 2), Is.EqualTo(2));
245245
}
246+
247+
[Test]
248+
public void Array_As_Generic_Array_Should_Be_Cloned()
249+
{
250+
var arr = new[] { 1, 2, 3 };
251+
var genArr = (Array)arr;
252+
var clone = (int[])genArr.DeepClone();
253+
Assert.That(clone.Length, Is.EqualTo(3));
254+
Assert.That(clone[0], Is.EqualTo(1));
255+
Assert.That(clone[1], Is.EqualTo(2));
256+
Assert.That(clone[2], Is.EqualTo(3));
257+
}
258+
259+
[Test]
260+
public void Array_As_IEnumerable_Should_Be_Cloned()
261+
{
262+
var arr = new[] { 1, 2, 3 };
263+
var genArr = (IEnumerable<int>)arr;
264+
var clone = (int[])genArr.DeepClone();
265+
// ReSharper disable PossibleMultipleEnumeration
266+
Assert.That(clone.Length, Is.EqualTo(3));
267+
Assert.That(clone[0], Is.EqualTo(1));
268+
Assert.That(clone[1], Is.EqualTo(2));
269+
Assert.That(clone[2], Is.EqualTo(3));
270+
// ReSharper restore PossibleMultipleEnumeration
271+
}
246272
}
247273
}

DeepCloner.Tests/DeepCloner.Tests.csproj

+15
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,24 @@
3333
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
3434
</PropertyGroup>
3535
<ItemGroup>
36+
<Reference Include="CloneBehave">
37+
<HintPath>..\packages\Clone.Behave.1.0.1\lib\CloneBehave.dll</HintPath>
38+
</Reference>
3639
<Reference Include="CloneExtensions">
3740
<HintPath>..\packages\CloneExtensions.1.2\lib\portable-net40+sl50+win+wp80\CloneExtensions.dll</HintPath>
3841
</Reference>
42+
<Reference Include="Fasterflect">
43+
<HintPath>..\packages\fasterflect.2.1.3\lib\net40\Fasterflect.dll</HintPath>
44+
</Reference>
45+
<Reference Include="GeorgeCloney">
46+
<HintPath>..\packages\GeorgeCloney.1.1.1.17\lib\net40\GeorgeCloney.dll</HintPath>
47+
</Reference>
48+
<Reference Include="NClone">
49+
<HintPath>..\packages\NClone.1.1.1\lib\net40\NClone.dll</HintPath>
50+
</Reference>
51+
<Reference Include="Nuclex.Cloning">
52+
<HintPath>..\packages\Nuclex.Cloning.1.0.0.0\lib\net40\Nuclex.Cloning.dll</HintPath>
53+
</Reference>
3954
<Reference Include="nunit.framework">
4055
<HintPath>..\packages\NUnit.3.0.1\lib\net40\nunit.framework.dll</HintPath>
4156
</Reference>

DeepCloner.Tests/InheritanceSpec.cs

+6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ public class C1 : IDisposable
2222
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed. Suppression is OK here.")]
2323
public int Y;
2424

25+
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed. Suppression is OK here.")]
26+
public object O; // make it not safe
27+
2528
public void Dispose()
2629
{
2730
}
@@ -43,6 +46,9 @@ public class C1P : IDisposable
4346
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed. Suppression is OK here.")]
4447
public int Y { get; set; }
4548

49+
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed. Suppression is OK here.")]
50+
public object O; // make it not safe
51+
4652
public void Dispose()
4753
{
4854
}

DeepCloner.Tests/PerformanceSpec.cs

+34-1
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@
44
using System.Runtime.InteropServices;
55
using System.Runtime.Serialization.Formatters.Binary;
66

7+
using CloneBehave;
8+
79
using CloneExtensions;
810

11+
using NClone;
12+
913
using NUnit.Framework;
1014

15+
using Nuclex.Cloning;
16+
1117
namespace Force.DeepCloner.Tests
1218
{
1319
[TestFixture]
@@ -80,6 +86,11 @@ public void Test_Construct_Variants(bool isSafe)
8086
for (var i = 0; i < 1000; i++) c1.GetClone();
8187
for (var i = 0; i < 1000; i++) c1.DeepClone();
8288
for (var i = 0; i < 1000; i++) CloneViaFormatter(c1);
89+
for (var i = 0; i < 1000; i++) Clone.ObjectGraph(c1);
90+
for (var i = 0; i < 1000; i++) new CloneEngine().Clone(c1);
91+
// null reference
92+
// for (var i = 0; i < 1000; i++) ReflectionCloner.DeepFieldClone(c1);
93+
for (var i = 0; i < 1000; i++) GeorgeCloney.CloneExtension.DeepCloneWithoutSerialization(c1);
8394

8495
// test
8596
var sw = new Stopwatch();
@@ -94,7 +105,24 @@ public void Test_Construct_Variants(bool isSafe)
94105
sw.Restart();
95106

96107
for (var i = 0; i < 1000000; i++) c1.GetClone();
97-
Console.WriteLine("Clone Extensions: " + sw.ElapsedMilliseconds);
108+
Console.WriteLine("CloneExtensions: " + sw.ElapsedMilliseconds);
109+
sw.Restart();
110+
111+
for (var i = 0; i < 1000000; i++) Clone.ObjectGraph(c1);
112+
Console.WriteLine("NClone: " + sw.ElapsedMilliseconds);
113+
sw.Restart();
114+
115+
for (var i = 0; i < 1000000; i++) new CloneEngine().Clone(c1);
116+
Console.WriteLine("Clone.Behave: " + sw.ElapsedMilliseconds);
117+
sw.Restart();
118+
119+
// null reference
120+
/*for (var i = 0; i < 1000000; i++) ReflectionCloner.DeepFieldClone(c1);
121+
Console.WriteLine("Nuclex.Cloning: " + sw.ElapsedMilliseconds);
122+
sw.Restart();*/
123+
124+
for (var i = 0; i < 1000000; i++) GeorgeCloney.CloneExtension.DeepCloneWithoutSerialization(c1);
125+
Console.WriteLine("GeorgeCloney: " + sw.ElapsedMilliseconds);
98126
sw.Restart();
99127

100128
// inaccurate variant, but test should complete in reasonable time
@@ -179,6 +207,7 @@ public void Test_Shallow_Variants()
179207
BaseTest.SwitchTo(true);
180208
for (var i = 0; i < 1000; i++) c1.ShallowClone();
181209
for (var i = 0; i < 1000; i++) c1.GetClone();
210+
for (var i = 0; i < 1000; i++) ReflectionCloner.ShallowFieldClone(c1);
182211

183212
// test
184213
var sw = new Stopwatch();
@@ -203,6 +232,10 @@ public void Test_Shallow_Variants()
203232
Console.WriteLine("Shallow Safe: " + sw.ElapsedMilliseconds);
204233
sw.Restart();
205234

235+
for (var i = 0; i < 1000000; i++) ReflectionCloner.ShallowFieldClone(c1);
236+
Console.WriteLine("Nuclex.Cloning: " + sw.ElapsedMilliseconds);
237+
sw.Restart();
238+
206239
for (var i = 0; i < 1000000; i++) c1.GetClone(CloningFlags.Shallow);
207240
Console.WriteLine("Clone Extensions: " + sw.ElapsedMilliseconds);
208241
sw.Restart();

DeepCloner.Tests/PermissionSpec.cs

+30-6
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
using System;
2-
using System.Collections.Generic;
32
using System.Security;
43
using System.Security.Permissions;
54

5+
using CloneExtensions;
6+
67
using NUnit.Framework;
78

89
namespace Force.DeepCloner.Tests
@@ -25,32 +26,55 @@ public void EnsurePermission()
2526
// assembly execute
2627
permissions.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
2728

28-
permissions.AddPermission(new ReflectionPermission(ReflectionPermissionFlag.RestrictedMemberAccess | ReflectionPermissionFlag.MemberAccess));
29-
// permissions.AddPermission(new ReflectionPermission(ReflectionPermissionFlag.RestrictedMemberAccess));
29+
// permissions.AddPermission(new ReflectionPermission(ReflectionPermissionFlag.RestrictedMemberAccess | ReflectionPermissionFlag.MemberAccess));
30+
permissions.AddPermission(new ReflectionPermission(ReflectionPermissionFlag.MemberAccess));
3031

3132
var test = AppDomain.CreateDomain("sandbox", null, setup, permissions);
3233

3334
var instance = (Executor)test.CreateInstanceFromAndUnwrap(this.GetType().Assembly.Location, typeof(Executor).FullName);
35+
instance.CloneExtensionsClone();
3436
instance.DoShallowClone();
3537
instance.DoDeepClone();
3638
}
3739

3840
public class Test
3941
{
4042
public int X { get; set; }
43+
44+
private readonly object y = new object();
45+
46+
private readonly UnsafeStructTest z;
47+
48+
public object GetY()
49+
{
50+
return y;
51+
}
52+
}
53+
54+
public struct UnsafeStructTest
55+
{
56+
public object Y { get; set; }
4157
}
4258

4359
public class Executor : MarshalByRefObject
4460
{
4561
public void DoDeepClone()
4662
{
47-
new List<int> { 1, 2, 3 }.DeepClone();
63+
var test = new Test();
64+
var clone = test.DeepClone();
65+
if (clone.GetY() == test.GetY())
66+
throw new Exception("Deep Clone fail");
4867
}
4968

50-
public void DoShallowClone()
69+
public void DoShallowClone()
5170
{
52-
new List<int> { 1, 2, 3 }.ShallowClone();
71+
new Test().ShallowClone();
5372
}
73+
74+
public void CloneExtensionsClone()
75+
{
76+
new Test().GetClone();
77+
}
5478
}
5579
}
5680
}

DeepCloner.Tests/SimpleObjectSpec.cs

+60
Original file line numberDiff line numberDiff line change
@@ -186,5 +186,65 @@ public void String_In_Class_Should_Not_Be_Cloned()
186186
Assert.That(cloned.X, Is.EqualTo(c.X));
187187
Assert.True(ReferenceEquals(cloned.X, c.X));
188188
}
189+
190+
public sealed class C6
191+
{
192+
public readonly int X = 1;
193+
194+
private readonly object y = new object();
195+
196+
private readonly StructWithObject z;
197+
198+
public object GetY()
199+
{
200+
return y;
201+
}
202+
}
203+
204+
public struct StructWithObject
205+
{
206+
public readonly object Z;
207+
}
208+
209+
[Test]
210+
public void Object_With_Readonly_Fields_Should_Be_Cloned()
211+
{
212+
var c = new C6();
213+
var clone = c.DeepClone();
214+
Assert.That(clone, Is.Not.EqualTo(c));
215+
Assert.That(clone.X, Is.EqualTo(1));
216+
Assert.That(clone.GetY(), Is.Not.Null);
217+
Assert.That(clone.GetY(), Is.Not.EqualTo(c.GetY()));
218+
}
219+
220+
public class VirtualClass1
221+
{
222+
public virtual int A { get; set; }
223+
224+
public virtual int B { get; set; }
225+
226+
// not safe
227+
public object X { get; set; }
228+
}
229+
230+
public class VirtualClass2 : VirtualClass1
231+
{
232+
public override int B { get; set; }
233+
}
234+
235+
[Test(Description = "Nothings special, just for checking")]
236+
public void Class_With_Virtual_Methods_Should_Be_Cloned()
237+
{
238+
var v2 = new VirtualClass2();
239+
v2.A = 1;
240+
v2.B = 2;
241+
var v1 = v2 as VirtualClass1;
242+
v1.A = 3;
243+
var clone = v1.DeepClone() as VirtualClass2;
244+
v2.B = 0;
245+
v2.A = 0;
246+
Assert.That(clone.B, Is.EqualTo(2));
247+
Assert.That(clone.A, Is.EqualTo(3));
248+
}
189249
}
190250
}

DeepCloner.Tests/packages.config

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<packages>
3+
<package id="Clone.Behave" version="1.0.1" targetFramework="net40" />
34
<package id="CloneExtensions" version="1.2" targetFramework="net40" />
5+
<package id="fasterflect" version="2.1.3" targetFramework="net40" />
6+
<package id="GeorgeCloney" version="1.1.1.17" targetFramework="net40" />
7+
<package id="NClone" version="1.1.1" targetFramework="net40" />
8+
<package id="Nuclex.Cloning" version="1.0.0.0" targetFramework="net40" />
49
<package id="NUnit" version="3.0.1" targetFramework="net40" />
510
</packages>

DeepCloner.nuspec

+3-4
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,9 @@
1212
<requireLicenseAcceptance>false</requireLicenseAcceptance>
1313
<description>Small Library for fast deep or shallow cloning .NET objects. It allows to copy everything and has a lot of performance tricks for fast copying.</description>
1414
<releaseNotes>
15-
Fixed an issue with handling references to arrays
16-
Added support for multidimensional arrays and non-zero-based arrays
17-
Added fallback for non-fulltrust code through expressions
18-
Due build issue, release 0.9.0 was invalid, this is fixed variant
15+
Seriously improved cloning of simple object (constructor analyzing + reference counting)
16+
Fixed small issue with possible multiple copying of same fields in object
17+
Safe variant now correctly clones readonly fields
1918
</releaseNotes>
2019
<copyright>Copyright by Force 2016</copyright>
2120
<tags>.NET shallow deep clone DeepClone fast</tags>

DeepCloner/Helpers/DeepClonerExprGenerator.cs

+25-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,25 @@ internal static object GenerateClonerInternal(Type realType, bool asObject)
1212
return GenerateProcessMethod(realType, asObject && realType.IsValueType);
1313
}
1414

15+
// slow, but hardcore method to set readonly field
16+
internal static void ForceSetField(FieldInfo field, object obj, object value)
17+
{
18+
var fieldInfo = field.GetType().GetField("m_fieldAttributes", BindingFlags.NonPublic | BindingFlags.Instance);
19+
20+
// TODO: think about it
21+
// nothing to do :( we should a throw an exception, but it is no good for user
22+
if (fieldInfo == null)
23+
return;
24+
var ov = fieldInfo.GetValue(field);
25+
if (!(ov is FieldAttributes))
26+
return;
27+
var v = (FieldAttributes)ov;
28+
29+
fieldInfo.SetValue(field, v & ~FieldAttributes.InitOnly);
30+
field.SetValue(obj, value);
31+
fieldInfo.SetValue(field, v);
32+
}
33+
1534
private static object GenerateProcessMethod(Type type, bool unboxStruct)
1635
{
1736
if (type.IsArray)
@@ -67,7 +86,7 @@ private static object GenerateProcessMethod(Type type, bool unboxStruct)
6786
{
6887
// don't do anything with this dark magic!
6988
if (tp == typeof(ContextBoundObject)) break;
70-
fi.AddRange(tp.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public));
89+
fi.AddRange(tp.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly));
7190
tp = tp.BaseType;
7291
}
7392
while (tp != null);
@@ -89,10 +108,13 @@ private static object GenerateProcessMethod(Type type, bool unboxStruct)
89108
call = Expression.Convert(call, fieldInfo.FieldType);
90109

91110
// should handle specially
111+
// todo: think about optimization, but it rare case
92112
if (fieldInfo.IsInitOnly)
93113
{
94-
var setMethod = fieldInfo.GetType().GetMethod("SetValue", new[] { typeof(object), typeof(object) });
95-
expressionList.Add(Expression.Call(Expression.Constant(fieldInfo), setMethod, toLocal, call));
114+
// var setMethod = fieldInfo.GetType().GetMethod("SetValue", new[] { typeof(object), typeof(object) });
115+
// expressionList.Add(Expression.Call(Expression.Constant(fieldInfo), setMethod, toLocal, call));
116+
var setMethod = typeof(DeepClonerExprGenerator).GetMethod("ForceSetField", BindingFlags.NonPublic | BindingFlags.Static);
117+
expressionList.Add(Expression.Call(setMethod, Expression.Constant(fieldInfo), Expression.Convert(toLocal, typeof(object)), Expression.Convert(call, typeof(object))));
96118
}
97119
else
98120
{

DeepCloner/Helpers/DeepClonerMsilGenerator.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ private static void GenerateProcessMethod(ILGenerator il, Type type, bool unboxS
102102
{
103103
// don't do anything with this dark magic!
104104
if (tp == typeof(ContextBoundObject)) break;
105-
fi.AddRange(tp.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public));
105+
fi.AddRange(tp.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly));
106106
tp = tp.BaseType;
107107
}
108108
while (tp != null);

0 commit comments

Comments
 (0)