Skip to content

Commit f0cd989

Browse files
committed
0.8.2 candidate
Fixed an issue with handling references to arrays (not tracked same objects) Added support for multidimensional arrays and non-zero-based arrays (with optimization for 2dim arrays and generic implementation of others)
1 parent f36cbae commit f0cd989

5 files changed

Lines changed: 223 additions & 8 deletions

File tree

DeepCloner.Tests/ArraysSpec.cs

Lines changed: 122 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23

34
using NUnit.Framework;
45

@@ -43,6 +44,8 @@ public void ClassArray_Should_Be_Cloned()
4344
Assert.That(cloned.Length, Is.EqualTo(2));
4445
Assert.That(cloned[0].X, Is.EqualTo(1));
4546
Assert.That(cloned[1].X, Is.EqualTo(2));
47+
Assert.That(cloned[0], Is.Not.EqualTo(arr[0]));
48+
Assert.That(cloned[1], Is.Not.EqualTo(arr[1]));
4649
}
4750

4851
public struct S1
@@ -57,7 +60,7 @@ public S1(int x)
5760

5861
public struct S2
5962
{
60-
public C1 C;
63+
public C1 C;
6164
}
6265

6366
[Test]
@@ -78,6 +81,8 @@ public void StructArray_With_Class_Should_Be_Cloned()
7881
Assert.That(cloned.Length, Is.EqualTo(2));
7982
Assert.That(cloned[0].C.X, Is.EqualTo(1));
8083
Assert.That(cloned[1].C.X, Is.EqualTo(2));
84+
Assert.That(cloned[0].C, Is.Not.EqualTo(arr[0].C));
85+
Assert.That(cloned[1].C, Is.Not.EqualTo(arr[1].C));
8186
}
8287

8388
[Test]
@@ -90,6 +95,16 @@ public void NullArray_hould_Be_Cloned()
9095
Assert.That(cloned[1], Is.Null);
9196
}
9297

98+
[Test]
99+
public void NullAsArray_hould_Be_Cloned()
100+
{
101+
var arr = (int[])null;
102+
// ReSharper disable ExpressionIsAlwaysNull
103+
var cloned = arr.DeepClone();
104+
// ReSharper restore ExpressionIsAlwaysNull
105+
Assert.That(cloned, Is.Null);
106+
}
107+
93108
[Test]
94109
public void IntList_Should_Be_Cloned()
95110
{
@@ -114,5 +129,110 @@ public void Dictionary_Should_Be_Cloned()
114129
Assert.That(cloned["a"], Is.EqualTo(1));
115130
Assert.That(cloned["b"], Is.EqualTo(2));
116131
}
132+
133+
[Test]
134+
public void Array_Of_Same_Arrays_Should_Be_Cloned()
135+
{
136+
var c1 = new[] { 1, 2, 3 };
137+
var arr = new[] { c1, c1, c1 };
138+
var cloned = arr.DeepClone();
139+
140+
Assert.That(cloned.Length, Is.EqualTo(3));
141+
Assert.That(ReferenceEquals(arr[0], cloned[0]), Is.False);
142+
Assert.That(ReferenceEquals(cloned[0], cloned[1]), Is.True);
143+
Assert.That(ReferenceEquals(cloned[1], cloned[2]), Is.True);
144+
}
145+
146+
public class AC
147+
{
148+
public int[] A { get; set; }
149+
150+
public int[] B { get; set; }
151+
}
152+
153+
[Test]
154+
public void Class_With_Same_Arrays_Should_Be_Cloned()
155+
{
156+
var ac = new AC();
157+
ac.A = ac.B = new int[3];
158+
var clone = ac.DeepClone();
159+
Assert.That(ReferenceEquals(ac.A, clone.A), Is.False);
160+
Assert.That(ReferenceEquals(clone.A, clone.B), Is.True);
161+
}
162+
163+
[Test]
164+
public void Class_With_Null_Array_hould_Be_Cloned()
165+
{
166+
var ac = new AC();
167+
var cloned = ac.DeepClone();
168+
Assert.That(cloned.A, Is.Null);
169+
Assert.That(cloned.B, Is.Null);
170+
}
171+
172+
[Test]
173+
public void MultiDim_Array_Should_Be_Cloned()
174+
{
175+
var arr = new int[2, 2];
176+
arr[0, 0] = 1;
177+
arr[0, 1] = 2;
178+
arr[1, 0] = 3;
179+
arr[1, 1] = 4;
180+
var clone = arr.DeepClone();
181+
Assert.That(clone[0, 0], Is.EqualTo(1));
182+
Assert.That(clone[0, 1], Is.EqualTo(2));
183+
Assert.That(clone[1, 0], Is.EqualTo(3));
184+
Assert.That(clone[1, 1], Is.EqualTo(4));
185+
}
186+
187+
[Test]
188+
public void MultiDim_Array_Should_Be_Cloned2()
189+
{
190+
var arr = new int[2, 2, 1];
191+
arr[0, 0, 0] = 1;
192+
arr[0, 1, 0] = 2;
193+
arr[1, 0, 0] = 3;
194+
arr[1, 1, 0] = 4;
195+
var clone = arr.DeepClone();
196+
Assert.That(clone[0, 0, 0], Is.EqualTo(1));
197+
Assert.That(clone[0, 1, 0], Is.EqualTo(2));
198+
Assert.That(clone[1, 0, 0], Is.EqualTo(3));
199+
Assert.That(clone[1, 1, 0], Is.EqualTo(4));
200+
}
201+
202+
[Test]
203+
public void MultiDim_Array_Of_Classes_Should_Be_Cloned()
204+
{
205+
var arr = new AC[2, 2];
206+
arr[0, 0] = arr[1, 1] = new AC();
207+
var clone = arr.DeepClone();
208+
Assert.That(clone[0, 0], Is.Not.Null);
209+
Assert.That(clone[1, 1], Is.Not.Null);
210+
Assert.That(clone[1, 1], Is.EqualTo(clone[0, 0]));
211+
Assert.That(clone[1, 1], Is.Not.EqualTo(arr[0, 0]));
212+
}
213+
214+
[Test]
215+
public void NonZero_Based_Array_Should_Be_Cloned()
216+
{
217+
var arr = Array.CreateInstance(typeof(int), new[] { 2 }, new[] { 1 });
218+
219+
arr.SetValue(1, 1);
220+
arr.SetValue(2, 2);
221+
var clone = arr.DeepClone();
222+
Assert.That(clone.GetValue(1), Is.EqualTo(1));
223+
Assert.That(clone.GetValue(2), Is.EqualTo(2));
224+
}
225+
226+
[Test]
227+
public void NonZero_Based_MultiDim_Array_Should_Be_Cloned()
228+
{
229+
var arr = Array.CreateInstance(typeof(int), new[] { 2, 2 }, new[] { 1, 1 });
230+
231+
arr.SetValue(1, 1, 1);
232+
arr.SetValue(2, 2, 2);
233+
var clone = arr.DeepClone();
234+
Assert.That(clone.GetValue(1, 1), Is.EqualTo(1));
235+
Assert.That(clone.GetValue(2, 2), Is.EqualTo(2));
236+
}
117237
}
118238
}

DeepCloner.nuspec

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<metadata>
44
<id>DeepCloner</id>
55
<title>DeepCloner</title>
6-
<version>0.8.1</version>
6+
<version>0.8.2</version>
77
<authors>force</authors>
88
<owners>force</owners>
99
<licenseUrl>https://github.com/force-net/DeepCloner/blob/develop/LICENSE</licenseUrl>
@@ -12,8 +12,8 @@
1212
<requireLicenseAcceptance>false</requireLicenseAcceptance>
1313
<description>Small Library for fast deep or shallow cloning .NET objects</description>
1414
<releaseNotes>
15-
Fixed an issue with cloning strings (strings are forbidden for cloning)
16-
Added check for permissions to show correct error.
15+
Fixed an issue with handling references to arrays
16+
Added support for multidimensional arrays and non-zero-based arrays
1717
</releaseNotes>
1818
<copyright>Copyright by Force 2016</copyright>
1919
<tags>.NET shallow deep clone</tags>

DeepCloner/Helpers/DeepClonerGenerator.cs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System;
2+
using System.Linq;
3+
using System.Reflection;
24

35
namespace Force.DeepCloner.Helpers
46
{
@@ -54,6 +56,68 @@ private static T CloneStructInternal<T>(T obj, DeepCloneState state) // where T
5456
return cloner(obj, state);
5557
}
5658

59+
// relatively frequent case. specially handled
60+
internal static T[,] Clone2DimArrayInternal<T>(T[,] obj, DeepCloneState state)
61+
{
62+
// not null from called method, but will check it anyway
63+
if (obj == null) return null;
64+
var l1 = obj.GetLength(0);
65+
var l2 = obj.GetLength(1);
66+
var outArray = new T[l1, l2];
67+
if (DeepClonerSafeTypes.IsTypeSafe(typeof(T), null))
68+
{
69+
Array.Copy(obj, outArray, obj.Length);
70+
return obj;
71+
}
72+
73+
if (typeof(T).IsValueType)
74+
{
75+
var cloner = GetCloner<T>();
76+
for (var i = 0; i < l1; i++)
77+
for (var k = 0; k < l2; k++)
78+
outArray[i, k] = cloner(obj[i, k], state);
79+
}
80+
else
81+
{
82+
for (var i = 0; i < l1; i++)
83+
for (var k = 0; k < l2; k++)
84+
outArray[i, k] = (T)CloneClassInternal(obj[i, k], state);
85+
}
86+
87+
return outArray;
88+
}
89+
90+
// rare cases, very slow cloning. currently it's ok
91+
internal static Array CloneAbstractArrayInternal(Array obj, DeepCloneState state)
92+
{
93+
// not null from called method, but will check it anyway
94+
if (obj == null) return null;
95+
var rank = obj.Rank;
96+
var lowerBounds = Enumerable.Range(0, rank).Select(obj.GetLowerBound).ToArray();
97+
var lengths = Enumerable.Range(0, rank).Select(obj.GetLength).ToArray();
98+
var idxes = Enumerable.Range(0, rank).Select(obj.GetLowerBound).ToArray();
99+
100+
var outArray = Array.CreateInstance(obj.GetType().GetElementType(), lengths, lowerBounds);
101+
102+
while (true)
103+
{
104+
outArray.SetValue(CloneClassInternal(obj.GetValue(idxes), state), idxes);
105+
var ofs = rank - 1;
106+
while (true)
107+
{
108+
idxes[ofs]++;
109+
if (idxes[ofs] >= lowerBounds[ofs] + lengths[ofs])
110+
{
111+
idxes[ofs] = lowerBounds[ofs];
112+
ofs--;
113+
if (ofs < 0) return outArray;
114+
}
115+
else
116+
break;
117+
}
118+
}
119+
}
120+
57121
private static Func<T, DeepCloneState, T> GetCloner<T>()
58122
{
59123
return (Func<T, DeepCloneState, T>)DeepClonerCache.GetOrAddStructAsObject(typeof(T), t => DeepClonerMsilGenerator.GenerateClonerInternal(t, false));

DeepCloner/Helpers/DeepClonerMsilGenerator.cs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
34
using System.Reflection;
45
using System.Reflection.Emit;
56
using System.Threading;
@@ -112,17 +113,47 @@ private static void GenerateProcessMethod(ILGenerator il, Type type, bool unboxS
112113

113114
private static void GenerateProcessArrayMethod(ILGenerator il, Type type)
114115
{
116+
var elementType = type.GetElementType();
117+
var rank = type.GetArrayRank();
118+
// multidim or not zero-based arrays
119+
if (rank != 1 || type != elementType.MakeArrayType())
120+
{
121+
MethodInfo methodInfo;
122+
if (rank == 2)
123+
{
124+
// small optimization for 2 dim arrays
125+
methodInfo = typeof(DeepClonerGenerator).GetMethod("Clone2DimArrayInternal", BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod(elementType);
126+
}
127+
else
128+
{
129+
methodInfo = typeof(DeepClonerGenerator).GetMethod("CloneAbstractArrayInternal", BindingFlags.NonPublic | BindingFlags.Static);
130+
}
131+
132+
il.Emit(OpCodes.Ldarg_0);
133+
il.Emit(OpCodes.Ldarg_1);
134+
il.Emit(OpCodes.Call, methodInfo);
135+
il.Emit(OpCodes.Ret);
136+
return;
137+
}
138+
139+
115140
// TODO: processing array of structs can be simplified
116141
var typeLocal = il.DeclareLocal(type);
117142
var lenLocal = il.DeclareLocal(typeof(int));
143+
118144
il.Emit(OpCodes.Ldarg_0);
119145
il.Emit(OpCodes.Call, type.GetProperty("Length").GetGetMethod());
120146
il.Emit(OpCodes.Dup);
121147
il.Emit(OpCodes.Stloc, lenLocal);
122-
var elementType = type.GetElementType();
123148
il.Emit(OpCodes.Newarr, elementType);
124149
il.Emit(OpCodes.Stloc, typeLocal);
125150

151+
// add ref
152+
il.Emit(OpCodes.Ldarg_1);
153+
il.Emit(OpCodes.Ldarg_0);
154+
il.Emit(OpCodes.Ldloc, typeLocal);
155+
il.Emit(OpCodes.Call, typeof(DeepCloneState).GetMethod("AddKnownRef"));
156+
126157
if (DeepClonerSafeTypes.IsTypeSafe(elementType, null))
127158
{
128159
// Array.Copy(from, to, from.Length);

DeepCloner/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,5 @@
3232
// by using the '*' as shown below:
3333

3434
[assembly: AssemblyVersion("0.8.0.0")] // change this value only when api is changing
35-
[assembly: AssemblyFileVersion("0.8.1.0")]
36-
[assembly: AssemblyInformationalVersion("0.8.1.0")]
35+
[assembly: AssemblyFileVersion("0.8.2.0")]
36+
[assembly: AssemblyInformationalVersion("0.8.2.0")]

0 commit comments

Comments
 (0)