Skip to content

Commit 4969337

Browse files
committed
Issue #25 fixed an issue with zer-length multi-dim array copying and fixed incorrect 2dim processing
1 parent 4f2ffe5 commit 4969337

7 files changed

Lines changed: 122 additions & 22 deletions

File tree

DeepCloner.Tests/ArraysSpec.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,25 @@ public void MultiDim_Array_Should_Be_Cloned2()
261261
Assert.That(clone[1, 0, 0], Is.EqualTo(3));
262262
Assert.That(clone[1, 1, 0], Is.EqualTo(4));
263263
}
264+
265+
[Test]
266+
public void MultiDim_Array_Should_Be_Cloned3()
267+
{
268+
const int cnt1 = 4;
269+
const int cnt2 = 5;
270+
const int cnt3 = 6;
271+
var arr = new int[cnt1, cnt2, cnt3];
272+
for (var i1 = 0; i1 < cnt1; i1++)
273+
for (var i2 = 0; i2 < cnt2; i2++)
274+
for (var i3 = 0; i3 < cnt3; i3++)
275+
arr[i1, i2, i3] = i1 * 100 + i2 * 10 + i3;
276+
var clone = arr.DeepClone();
277+
Assert.That(ReferenceEquals(arr, clone), Is.False);
278+
for (var i1 = 0; i1 < cnt1; i1++)
279+
for (var i2 = 0; i2 < cnt2; i2++)
280+
for (var i3 = 0; i3 < cnt3; i3++)
281+
Assert.That(arr[i1, i2, i3], Is.EqualTo(i1 * 100 + i2 * 10 + i3));
282+
}
264283

265284
[Test]
266285
public void MultiDim_Array_Of_Classes_Should_Be_Cloned()
@@ -323,5 +342,21 @@ public void Array_As_IEnumerable_Should_Be_Cloned()
323342
Assert.That(clone[2], Is.EqualTo(3));
324343
// ReSharper restore PossibleMultipleEnumeration
325344
}
345+
346+
[Test]
347+
public void MultiDimensional_Array_Should_Be_Cloned()
348+
{
349+
// Issue #25
350+
Array.CreateInstance(typeof(int), new[] { 0, 0 }).DeepClone();
351+
Array.CreateInstance(typeof(int), new[] { 1, 0 }).DeepClone();
352+
Array.CreateInstance(typeof(int), new[] { 0, 1 }).DeepClone();
353+
Array.CreateInstance(typeof(int), new[] { 1, 1 }).DeepClone();
354+
355+
Array.CreateInstance(typeof(int), new[] { 0, 0, 0 }).DeepClone();
356+
Array.CreateInstance(typeof(int), new[] { 1, 0, 0 }).DeepClone();
357+
Array.CreateInstance(typeof(int), new[] { 0, 1, 0 }).DeepClone();
358+
Array.CreateInstance(typeof(int), new[] { 0, 0, 1 }).DeepClone();
359+
Array.CreateInstance(typeof(int), new[] { 1, 1, 1 }).DeepClone();
360+
}
326361
}
327362
}

DeepCloner.Tests/CopyToObjectSpec.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,41 @@ public void TwoDim_Array_Should_Be_Cloned(bool isDeep)
324324
Assert.That(arrTo[1, 0], Is.EqualTo(3));
325325
}
326326

327+
[Test]
328+
public void MultiDim_Array_Should_Be_Cloned3()
329+
{
330+
const int cnt1 = 4;
331+
const int cnt2 = 5;
332+
const int cnt3 = 6;
333+
var arr = new int[cnt1, cnt2, cnt3];
334+
for (var i1 = 0; i1 < cnt1; i1++)
335+
for (var i2 = 0; i2 < cnt2; i2++)
336+
for (var i3 = 0; i3 < cnt3; i3++)
337+
arr[i1, i2, i3] = i1 * 100 + i2 * 10 + i3;
338+
var clone = arr.DeepCloneTo(new int[cnt1, cnt2, cnt3]);
339+
Assert.That(ReferenceEquals(arr, clone), Is.False);
340+
for (var i1 = 0; i1 < cnt1; i1++)
341+
for (var i2 = 0; i2 < cnt2; i2++)
342+
for (var i3 = 0; i3 < cnt3; i3++)
343+
Assert.That(arr[i1, i2, i3], Is.EqualTo(i1 * 100 + i2 * 10 + i3));
344+
}
345+
346+
[Test]
347+
public void MultiDimensional_Array_Should_Be_Cloned()
348+
{
349+
// Issue #25
350+
Array.CreateInstance(typeof(int), new[] { 0, 0 }).DeepCloneTo(new int[0, 0]);
351+
Array.CreateInstance(typeof(int), new[] { 1, 0 }).DeepCloneTo(new int[1, 0]);
352+
Array.CreateInstance(typeof(int), new[] { 0, 1 }).DeepCloneTo(new int[0, 1]);
353+
Array.CreateInstance(typeof(int), new[] { 1, 1 }).DeepCloneTo(new int[1, 1]);
354+
355+
Array.CreateInstance(typeof(int), new[] { 0, 0, 0 }).DeepCloneTo(new int[0, 0, 0]);
356+
Array.CreateInstance(typeof(int), new[] { 1, 0, 0 }).DeepCloneTo(new int[1, 0, 0]);
357+
Array.CreateInstance(typeof(int), new[] { 0, 1, 0 }).DeepCloneTo(new int[0, 1, 0]);
358+
Array.CreateInstance(typeof(int), new[] { 0, 0, 1 }).DeepCloneTo(new int[0, 0, 1]);
359+
Array.CreateInstance(typeof(int), new[] { 1, 1, 1 }).DeepCloneTo(new int[1, 1, 1]);
360+
}
361+
327362
[Test]
328363
public void Shallow_Clone_Of_MultiDim_Array_Should_Not_Perform_Deep()
329364
{

DeepCloner.nuspec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ Better handling for stucts casted to object
1919
Increased speed for cloning simple tuples
2020
Added some protection for parallel cloning objects with readonly fields
2121
Optimized copying readonly fields for .net core
22+
Fixed copying multi-dim arrays with 0-length of some dimension
23+
2-dim arrays were processed by generic (non-optimized) way
2224
</releaseNotes>
2325
<copyright>Copyright by Force 2016-2021</copyright>
2426
<tags>.NET shallow deep clone DeepClone fast</tags>

DeepCloner/Helpers/ClonerToExprGenerator.cs

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,12 @@ private static object GenerateProcessArrayMethod(Type type, bool isDeep)
146146
else
147147
{
148148
// multidim or not zero-based arrays
149-
var methodInfo = typeof(ClonerToExprGenerator).GetPrivateStaticMethod(
150-
rank == 2 && type == elementType.MakeArrayType()
151-
? "Clone2DimArrayInternal"
152-
: "CloneAbstractArrayInternal");
149+
MethodInfo methodInfo;
150+
if (rank == 2 && type == elementType.MakeArrayType(2))
151+
methodInfo = typeof(ClonerToExprGenerator).GetPrivateStaticMethod("Clone2DimArrayInternal").MakeGenericMethod(elementType);
152+
else
153+
methodInfo = typeof(ClonerToExprGenerator).GetPrivateStaticMethod("CloneAbstractArrayInternal");
154+
153155
var callS = Expression.Call(methodInfo, Expression.Convert(from, type), Expression.Convert(to, type), state, Expression.Constant(isDeep));
154156
return Expression.Lambda(funcType, callS, from, to, state).Compile();
155157
}
@@ -201,6 +203,10 @@ internal static T[] Clone1DimArrayClassInternal<T>(T[] objFrom, T[] objTo, DeepC
201203
{
202204
// not null from called method, but will check it anyway
203205
if (objFrom == null || objTo == null) return null;
206+
if (objFrom.GetLowerBound(0) != 0 || objFrom.GetLowerBound(1) != 0
207+
|| objTo.GetLowerBound(0) != 0 || objTo.GetLowerBound(1) != 0)
208+
return (T[,]) CloneAbstractArrayInternal(objFrom, objTo, state, isDeep);
209+
204210
var l1 = Math.Min(objFrom.GetLength(0), objTo.GetLength(0));
205211
var l2 = Math.Min(objFrom.GetLength(1), objTo.GetLength(1));
206212
state.AddKnownRef(objFrom, objTo);
@@ -212,13 +218,13 @@ internal static T[] Clone1DimArrayClassInternal<T>(T[] objFrom, T[] objTo, DeepC
212218
return objTo;
213219
}
214220

215-
if (!isDeep)
216-
{
217-
for (var i = 0; i < l1; i++)
218-
for (var k = 0; k < l2; k++)
219-
objTo[i, k] = objFrom[i, k];
220-
return objTo;
221-
}
221+
if (!isDeep)
222+
{
223+
for (var i = 0; i < l1; i++)
224+
for (var k = 0; k < l2; k++)
225+
objTo[i, k] = objFrom[i, k];
226+
return objTo;
227+
}
222228

223229
if (typeof(T).IsValueType())
224230
{
@@ -253,6 +259,11 @@ internal static Array CloneAbstractArrayInternal(Array objFrom, Array objTo, Dee
253259
var idxesTo = Enumerable.Range(0, rank).Select(objTo.GetLowerBound).ToArray();
254260

255261
state.AddKnownRef(objFrom, objTo);
262+
263+
// unable to copy any element
264+
if (lengths.Any(x => x == 0))
265+
return objTo;
266+
256267
while (true)
257268
{
258269
if (isDeep)

DeepCloner/Helpers/DeepClonerExprGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ private static object GenerateProcessArrayMethod(Type type)
209209
// multidim or not zero-based arrays
210210
if (rank != 1 || type != elementType.MakeArrayType())
211211
{
212-
if (rank == 2 && type == elementType.MakeArrayType())
212+
if (rank == 2 && type == elementType.MakeArrayType(2))
213213
{
214214
// small optimization for 2 dim arrays
215215
methodInfo = typeof(DeepClonerGenerator).GetPrivateStaticMethod("Clone2DimArrayInternal").MakeGenericMethod(elementType);

DeepCloner/Helpers/DeepClonerGenerator.cs

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,14 @@ internal static T[] Clone1DimArrayClassInternal<T>(T[] obj, DeepCloneState state
109109
{
110110
// not null from called method, but will check it anyway
111111
if (obj == null) return null;
112+
113+
// we cannot determine by type multidim arrays (one dimension is possible)
114+
// so, will check for index here
115+
var lb1 = obj.GetLowerBound(0);
116+
var lb2 = obj.GetLowerBound(1);
117+
if (lb1 != 0 || lb2 != 0)
118+
return (T[,]) CloneAbstractArrayInternal(obj, state);
119+
112120
var l1 = obj.GetLength(0);
113121
var l2 = obj.GetLength(1);
114122
var outArray = new T[l1, l2];
@@ -143,27 +151,36 @@ internal static Array CloneAbstractArrayInternal(Array obj, DeepCloneState state
143151
if (obj == null) return null;
144152
var rank = obj.Rank;
145153

146-
var lowerBounds = Enumerable.Range(0, rank).Select(obj.GetLowerBound).ToArray();
147154
var lengths = Enumerable.Range(0, rank).Select(obj.GetLength).ToArray();
155+
156+
var lowerBounds = Enumerable.Range(0, rank).Select(obj.GetLowerBound).ToArray();
148157
var idxes = Enumerable.Range(0, rank).Select(obj.GetLowerBound).ToArray();
149158

150159
var outArray = Array.CreateInstance(obj.GetType().GetElementType(), lengths, lowerBounds);
160+
151161
state.AddKnownRef(obj, outArray);
162+
163+
// we're unable to set any value to this array, so, just return it
164+
if (lengths.Any(x => x == 0))
165+
return outArray;
166+
167+
var ofs = rank - 1;
152168
while (true)
153169
{
154170
outArray.SetValue(CloneClassInternal(obj.GetValue(idxes), state), idxes);
155-
var ofs = rank - 1;
156-
while (true)
171+
idxes[ofs]++;
172+
173+
if (idxes[ofs] >= lowerBounds[ofs] + lengths[ofs])
157174
{
158-
idxes[ofs]++;
159-
if (idxes[ofs] >= lowerBounds[ofs] + lengths[ofs])
175+
do
160176
{
177+
if (ofs == 0) return outArray;
161178
idxes[ofs] = lowerBounds[ofs];
162179
ofs--;
163-
if (ofs < 0) return outArray;
164-
}
165-
else
166-
break;
180+
idxes[ofs]++;
181+
} while (idxes[ofs] >= lowerBounds[ofs] + lengths[ofs]);
182+
183+
ofs = rank - 1;
167184
}
168185
}
169186
}

DeepCloner/Helpers/DeepClonerMsilGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ private static void GenerateProcessArrayMethod(ILGenerator il, Type type)
178178
if (rank != 1 || type != elementType.MakeArrayType())
179179
{
180180
MethodInfo methodInfo;
181-
if (rank == 2 && type == elementType.MakeArrayType())
181+
if (rank == 2 && type == elementType.MakeArrayType(2))
182182
{
183183
// small optimization for 2 dim arrays
184184
methodInfo = typeof(DeepClonerGenerator).GetMethod("Clone2DimArrayInternal", BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod(elementType);

0 commit comments

Comments
 (0)