├── .gitignore ├── DeepCloner.Core.sln ├── DeepCloner.Tests ├── ArraysSpec.cs ├── BaseTest.cs ├── CloneExtensionsSpec.cs ├── ClrExceptionSpec.cs ├── ConstructorsSpec.cs ├── CopyToObjectSpec.cs ├── DeepCloner.Tests.Core.csproj ├── DeepCloner.Tests.csproj ├── GenericsSpec.cs ├── Imported │ └── FastDeepCloner.cs ├── InheritanceSpec.cs ├── LoopCheckSpec.cs ├── Objects │ ├── DoableStruct1.cs │ ├── IDoable.cs │ └── TestObject1.cs ├── PerformanceSpec.cs ├── PermissionSpec.cs ├── Properties │ └── AssemblyInfo.cs ├── ShallowClonerSpec.cs ├── SimpleObjectSpec.cs ├── SpecificScenariosTest.cs ├── SystemTypesSpec.cs └── packages.config ├── DeepCloner.nuspec ├── DeepCloner.sln ├── DeepCloner ├── DeepCloner.Core.csproj ├── DeepCloner.csproj ├── DeepClonerExtensions.cs ├── Helpers │ ├── ClonerToExprGenerator.cs │ ├── DeepCloneState.cs │ ├── DeepClonerCache.cs │ ├── DeepClonerExprGenerator.cs │ ├── DeepClonerGenerator.cs │ ├── DeepClonerMsilGenerator.cs │ ├── DeepClonerMsilHelper.cs │ ├── DeepClonerSafeTypes.cs │ ├── ReflectionHelper.cs │ ├── ShallowClonerGenerator.cs │ ├── ShallowObjectCloner.cs │ └── TypeCreationHelper.cs └── Properties │ └── AssemblyInfo.cs ├── LICENSE ├── README.md ├── deepcloner-nuget-ico.png ├── pack.cmd └── public.snk /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | *.user 4 | *.suo 5 | packages 6 | .tools 7 | .idea 8 | *.iml 9 | private.snk -------------------------------------------------------------------------------- /DeepCloner.Core.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.0.0 5 | MinimumVisualStudioVersion = 10.0.0.1 6 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "DeepCloner.Core", "DeepCloner\DeepCloner.Core.csproj", "{CB0E426A-9468-48FA-9679-A782801F1D15}" 7 | EndProject 8 | Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "DeepCloner.Tests", "DeepCloner.Tests\DeepCloner.Tests.Core.csproj", "{4939517A-4872-4CD3-9618-B3C69B68B688}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{61E9C5A9-C238-44DB-B8FC-C9440B1E6E16}" 11 | ProjectSection(SolutionItems) = preProject 12 | README.md = README.md 13 | EndProjectSection 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {CB0E426A-9468-48FA-9679-A782801F1D15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {CB0E426A-9468-48FA-9679-A782801F1D15}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {CB0E426A-9468-48FA-9679-A782801F1D15}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {CB0E426A-9468-48FA-9679-A782801F1D15}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {4939517A-4872-4CD3-9618-B3C69B68B688}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {4939517A-4872-4CD3-9618-B3C69B68B688}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {4939517A-4872-4CD3-9618-B3C69B68B688}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {4939517A-4872-4CD3-9618-B3C69B68B688}.Release|Any CPU.Build.0 = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | EndGlobal 34 | -------------------------------------------------------------------------------- /DeepCloner.Tests/ArraysSpec.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | using NUnit.Framework; 8 | 9 | namespace Force.DeepCloner.Tests 10 | { 11 | #if !NETCORE 12 | [TestFixture(false)] 13 | #endif 14 | [TestFixture(true)] 15 | public class ArraysSpec : BaseTest 16 | { 17 | public ArraysSpec(object isSafeInit) 18 | : base((bool)isSafeInit) 19 | { 20 | } 21 | 22 | [Test] 23 | public void IntArray_Should_Be_Cloned() 24 | { 25 | var arr = new[] { 1, 2, 3 }; 26 | var cloned = arr.DeepClone(); 27 | Assert.That(cloned.Length, Is.EqualTo(3)); 28 | CollectionAssert.AreEqual(arr, cloned); 29 | } 30 | 31 | [Test] 32 | public void StringArray_Should_Be_Cloned() 33 | { 34 | var arr = new[] { "1", "2", "3" }; 35 | var cloned = arr.DeepClone(); 36 | Assert.That(cloned.Length, Is.EqualTo(3)); 37 | CollectionAssert.AreEqual(arr, cloned); 38 | } 39 | 40 | [Test] 41 | public void StringArray_Should_Be_Cloned_Two_Arrays() 42 | { 43 | // checking that cached object correctly clones arrays of different length 44 | var arr = new[] { "111111111111111111111", "2", "3" }; 45 | var cloned = arr.DeepClone(); 46 | Assert.That(cloned.Length, Is.EqualTo(3)); 47 | CollectionAssert.AreEqual(arr, cloned); 48 | // strings should not be copied 49 | Assert.That(ReferenceEquals(arr[1], cloned[1]), Is.True); 50 | 51 | arr = new[] { "1", "2", "3", "4" }; 52 | cloned = arr.DeepClone(); 53 | Assert.That(cloned.Length, Is.EqualTo(4)); 54 | CollectionAssert.AreEqual(arr, cloned); 55 | 56 | arr = new string[0]; 57 | cloned = arr.DeepClone(); 58 | Assert.That(cloned.Length, Is.EqualTo(0)); 59 | 60 | if (1.Equals(1)) arr = null; 61 | Assert.That(arr.DeepClone(), Is.Null); 62 | } 63 | 64 | [Test] 65 | public void StringArray_Casted_As_Object_Should_Be_Cloned() 66 | { 67 | // checking that cached object correctly clones arrays of different length 68 | var arr = (object)new[] { "1", "2", "3" }; 69 | var cloned = arr.DeepClone() as string[]; 70 | Assert.That(cloned.Length, Is.EqualTo(3)); 71 | CollectionAssert.AreEqual((string[])arr, cloned); 72 | // strings should not be copied 73 | Assert.That(ReferenceEquals(((string[])arr)[1], cloned[1]), Is.True); 74 | } 75 | 76 | [Test] 77 | public void ByteArray_Should_Be_Cloned() 78 | { 79 | // checking that cached object correctly clones arrays of different length 80 | var arr = Encoding.ASCII.GetBytes("test"); 81 | var cloned = arr.DeepClone(); 82 | CollectionAssert.AreEqual(arr, cloned); 83 | 84 | arr = Encoding.ASCII.GetBytes("test testtest testtest testtest testtest testtest testtest testtest testtest testtest testtest testtest testtest testte"); 85 | cloned = arr.DeepClone(); 86 | CollectionAssert.AreEqual(arr, cloned); 87 | } 88 | 89 | public class C1 90 | { 91 | public C1(int x) 92 | { 93 | X = x; 94 | } 95 | 96 | public int X { get; set; } 97 | } 98 | 99 | [Test] 100 | public void ClassArray_Should_Be_Cloned() 101 | { 102 | var arr = new[] { new C1(1), new C1(2) }; 103 | var cloned = arr.DeepClone(); 104 | Assert.That(cloned.Length, Is.EqualTo(2)); 105 | Assert.That(cloned[0].X, Is.EqualTo(1)); 106 | Assert.That(cloned[1].X, Is.EqualTo(2)); 107 | Assert.That(cloned[0], Is.Not.EqualTo(arr[0])); 108 | Assert.That(cloned[1], Is.Not.EqualTo(arr[1])); 109 | } 110 | 111 | public struct S1 112 | { 113 | public S1(int x) 114 | { 115 | X = x; 116 | } 117 | 118 | public int X; 119 | } 120 | 121 | public struct S2 122 | { 123 | public C1 C; 124 | } 125 | 126 | [Test] 127 | public void StructArray_Should_Be_Cloned() 128 | { 129 | var arr = new[] { new S1(1), new S1(2) }; 130 | var cloned = arr.DeepClone(); 131 | Assert.That(cloned.Length, Is.EqualTo(2)); 132 | Assert.That(cloned[0].X, Is.EqualTo(1)); 133 | Assert.That(cloned[1].X, Is.EqualTo(2)); 134 | } 135 | 136 | [Test] 137 | public void StructArray_With_Class_Should_Be_Cloned() 138 | { 139 | var arr = new[] { new S2 { C = new C1(1) }, new S2 { C = new C1(2) } }; 140 | var cloned = arr.DeepClone(); 141 | Assert.That(cloned.Length, Is.EqualTo(2)); 142 | Assert.That(cloned[0].C.X, Is.EqualTo(1)); 143 | Assert.That(cloned[1].C.X, Is.EqualTo(2)); 144 | Assert.That(cloned[0].C, Is.Not.EqualTo(arr[0].C)); 145 | Assert.That(cloned[1].C, Is.Not.EqualTo(arr[1].C)); 146 | } 147 | 148 | [Test] 149 | public void NullArray_hould_Be_Cloned() 150 | { 151 | var arr = new C1[] { null, null }; 152 | var cloned = arr.DeepClone(); 153 | Assert.That(cloned.Length, Is.EqualTo(2)); 154 | Assert.That(cloned[0], Is.Null); 155 | Assert.That(cloned[1], Is.Null); 156 | } 157 | 158 | [Test] 159 | public void NullAsArray_hould_Be_Cloned() 160 | { 161 | var arr = (int[])null; 162 | // ReSharper disable ExpressionIsAlwaysNull 163 | var cloned = arr.DeepClone(); 164 | // ReSharper restore ExpressionIsAlwaysNull 165 | Assert.That(cloned, Is.Null); 166 | } 167 | 168 | [Test] 169 | public void IntList_Should_Be_Cloned() 170 | { 171 | // TODO: better performance for this type 172 | var arr = new List { 1, 2, 3 }; 173 | var cloned = arr.DeepClone(); 174 | Assert.That(cloned.Count, Is.EqualTo(3)); 175 | Assert.That(cloned[0], Is.EqualTo(1)); 176 | Assert.That(cloned[1], Is.EqualTo(2)); 177 | Assert.That(cloned[2], Is.EqualTo(3)); 178 | } 179 | 180 | [Test] 181 | public void Dictionary_Should_Be_Cloned() 182 | { 183 | // TODO: better performance for this type 184 | var d = new Dictionary(); 185 | d["a"] = 1; 186 | d["b"] = 2; 187 | var cloned = d.DeepClone(); 188 | Assert.That(cloned.Count, Is.EqualTo(2)); 189 | Assert.That(cloned["a"], Is.EqualTo(1)); 190 | Assert.That(cloned["b"], Is.EqualTo(2)); 191 | } 192 | 193 | [Test] 194 | public void Array_Of_Same_Arrays_Should_Be_Cloned() 195 | { 196 | var c1 = new[] { 1, 2, 3 }; 197 | var arr = new[] { c1, c1, c1, c1, c1 }; 198 | var cloned = arr.DeepClone(); 199 | 200 | Assert.That(cloned.Length, Is.EqualTo(5)); 201 | // lot of objects for checking reference dictionary optimization 202 | Assert.That(ReferenceEquals(arr[0], cloned[0]), Is.False); 203 | Assert.That(ReferenceEquals(cloned[0], cloned[1]), Is.True); 204 | Assert.That(ReferenceEquals(cloned[1], cloned[2]), Is.True); 205 | Assert.That(ReferenceEquals(cloned[1], cloned[3]), Is.True); 206 | Assert.That(ReferenceEquals(cloned[1], cloned[4]), Is.True); 207 | } 208 | 209 | public class AC 210 | { 211 | public int[] A { get; set; } 212 | 213 | public int[] B { get; set; } 214 | } 215 | 216 | [Test] 217 | public void Class_With_Same_Arrays_Should_Be_Cloned() 218 | { 219 | var ac = new AC(); 220 | ac.A = ac.B = new int[3]; 221 | var clone = ac.DeepClone(); 222 | Assert.That(ReferenceEquals(ac.A, clone.A), Is.False); 223 | Assert.That(ReferenceEquals(clone.A, clone.B), Is.True); 224 | } 225 | 226 | [Test] 227 | public void Class_With_Null_Array_hould_Be_Cloned() 228 | { 229 | var ac = new AC(); 230 | var cloned = ac.DeepClone(); 231 | Assert.That(cloned.A, Is.Null); 232 | Assert.That(cloned.B, Is.Null); 233 | } 234 | 235 | [Test] 236 | public void MultiDim_Array_Should_Be_Cloned() 237 | { 238 | var arr = new int[2, 2]; 239 | arr[0, 0] = 1; 240 | arr[0, 1] = 2; 241 | arr[1, 0] = 3; 242 | arr[1, 1] = 4; 243 | var clone = arr.DeepClone(); 244 | Assert.That(ReferenceEquals(arr, clone), Is.False); 245 | Assert.That(clone[0, 0], Is.EqualTo(1)); 246 | Assert.That(clone[0, 1], Is.EqualTo(2)); 247 | Assert.That(clone[1, 0], Is.EqualTo(3)); 248 | Assert.That(clone[1, 1], Is.EqualTo(4)); 249 | } 250 | 251 | [Test] 252 | public void MultiDim_Array_Should_Be_Cloned2() 253 | { 254 | var arr = new int[2, 2, 1]; 255 | arr[0, 0, 0] = 1; 256 | arr[0, 1, 0] = 2; 257 | arr[1, 0, 0] = 3; 258 | arr[1, 1, 0] = 4; 259 | var clone = arr.DeepClone(); 260 | Assert.That(ReferenceEquals(arr, clone), Is.False); 261 | Assert.That(clone[0, 0, 0], Is.EqualTo(1)); 262 | Assert.That(clone[0, 1, 0], Is.EqualTo(2)); 263 | Assert.That(clone[1, 0, 0], Is.EqualTo(3)); 264 | Assert.That(clone[1, 1, 0], Is.EqualTo(4)); 265 | } 266 | 267 | [Test] 268 | public void MultiDim_Array_Should_Be_Cloned3() 269 | { 270 | const int cnt1 = 4; 271 | const int cnt2 = 5; 272 | const int cnt3 = 6; 273 | var arr = new int[cnt1, cnt2, cnt3]; 274 | for (var i1 = 0; i1 < cnt1; i1++) 275 | for (var i2 = 0; i2 < cnt2; i2++) 276 | for (var i3 = 0; i3 < cnt3; i3++) 277 | arr[i1, i2, i3] = i1 * 100 + i2 * 10 + i3; 278 | var clone = arr.DeepClone(); 279 | Assert.That(ReferenceEquals(arr, clone), Is.False); 280 | for (var i1 = 0; i1 < cnt1; i1++) 281 | for (var i2 = 0; i2 < cnt2; i2++) 282 | for (var i3 = 0; i3 < cnt3; i3++) 283 | Assert.That(arr[i1, i2, i3], Is.EqualTo(i1 * 100 + i2 * 10 + i3)); 284 | } 285 | 286 | [Test] 287 | public void MultiDim_Array_Of_Classes_Should_Be_Cloned() 288 | { 289 | var arr = new AC[2, 2]; 290 | arr[0, 0] = arr[1, 1] = new AC(); 291 | var clone = arr.DeepClone(); 292 | Assert.That(clone[0, 0], Is.Not.Null); 293 | Assert.That(clone[1, 1], Is.Not.Null); 294 | Assert.That(clone[1, 1], Is.EqualTo(clone[0, 0])); 295 | Assert.That(clone[1, 1], Is.Not.EqualTo(arr[0, 0])); 296 | } 297 | 298 | [Test] 299 | public void NonZero_Based_Array_Should_Be_Cloned() 300 | { 301 | var arr = Array.CreateInstance(typeof(int), new[] { 2 }, new[] { 1 }); 302 | 303 | arr.SetValue(1, 1); 304 | arr.SetValue(2, 2); 305 | var clone = arr.DeepClone(); 306 | Assert.That(clone.GetValue(1), Is.EqualTo(1)); 307 | Assert.That(clone.GetValue(2), Is.EqualTo(2)); 308 | } 309 | 310 | [Test] 311 | public void NonZero_Based_MultiDim_Array_Should_Be_Cloned() 312 | { 313 | var arr = Array.CreateInstance(typeof(int), new[] { 2, 2 }, new[] { 1, 1 }); 314 | 315 | arr.SetValue(1, 1, 1); 316 | arr.SetValue(2, 2, 2); 317 | var clone = arr.DeepClone(); 318 | Assert.That(clone.GetValue(1, 1), Is.EqualTo(1)); 319 | Assert.That(clone.GetValue(2, 2), Is.EqualTo(2)); 320 | } 321 | 322 | [Test] 323 | public void Array_As_Generic_Array_Should_Be_Cloned() 324 | { 325 | var arr = new[] { 1, 2, 3 }; 326 | var genArr = (Array)arr; 327 | var clone = (int[])genArr.DeepClone(); 328 | Assert.That(clone.Length, Is.EqualTo(3)); 329 | Assert.That(clone[0], Is.EqualTo(1)); 330 | Assert.That(clone[1], Is.EqualTo(2)); 331 | Assert.That(clone[2], Is.EqualTo(3)); 332 | } 333 | 334 | [Test] 335 | public void Array_As_IEnumerable_Should_Be_Cloned() 336 | { 337 | var arr = new[] { 1, 2, 3 }; 338 | var genArr = (IEnumerable)arr; 339 | var clone = (int[])genArr.DeepClone(); 340 | // ReSharper disable PossibleMultipleEnumeration 341 | Assert.That(clone.Length, Is.EqualTo(3)); 342 | Assert.That(clone[0], Is.EqualTo(1)); 343 | Assert.That(clone[1], Is.EqualTo(2)); 344 | Assert.That(clone[2], Is.EqualTo(3)); 345 | // ReSharper restore PossibleMultipleEnumeration 346 | } 347 | 348 | [Test] 349 | public void MultiDimensional_Array_Should_Be_Cloned() 350 | { 351 | // Issue #25 352 | Array.CreateInstance(typeof(int), new[] { 0, 0 }).DeepClone(); 353 | Array.CreateInstance(typeof(int), new[] { 1, 0 }).DeepClone(); 354 | Array.CreateInstance(typeof(int), new[] { 0, 1 }).DeepClone(); 355 | Array.CreateInstance(typeof(int), new[] { 1, 1 }).DeepClone(); 356 | 357 | Array.CreateInstance(typeof(int), new[] { 0, 0, 0 }).DeepClone(); 358 | Array.CreateInstance(typeof(int), new[] { 1, 0, 0 }).DeepClone(); 359 | Array.CreateInstance(typeof(int), new[] { 0, 1, 0 }).DeepClone(); 360 | Array.CreateInstance(typeof(int), new[] { 0, 0, 1 }).DeepClone(); 361 | Array.CreateInstance(typeof(int), new[] { 1, 1, 1 }).DeepClone(); 362 | } 363 | 364 | [Test] 365 | public void Issue_17_Spec() 366 | { 367 | var set = new HashSet { "value" }; 368 | Assert.That(set.Contains("value"), Is.True); 369 | 370 | var cloned = set.DeepClone(); 371 | Assert.That(cloned.Contains("value"), Is.True); 372 | 373 | var copyOfSet = new HashSet(set, set.Comparer); 374 | Assert.That(copyOfSet.Contains("value"), Is.True); 375 | 376 | var copyOfCloned = new HashSet(cloned, cloned.Comparer); 377 | Assert.That(copyOfCloned.ToArray()[0] == "value", Is.True); 378 | 379 | Assert.That(copyOfCloned.Contains("value"), Is.True); 380 | } 381 | 382 | [Test] 383 | public void Check_Comparer_does_not_Clone() 384 | { 385 | Check_Comparer_does_not_Clone_Internal(); 386 | Check_Comparer_does_not_Clone_Internal(); 387 | Check_Comparer_does_not_Clone_Internal(); 388 | Check_Comparer_does_not_Clone_Internal(); 389 | Check_Comparer_does_not_Clone_Internal(); 390 | Check_Comparer_does_not_Clone_Internal(); 391 | Check_Comparer_does_not_Clone_Internal(); 392 | Check_Comparer_does_not_Clone_Internal>(); 393 | Assert.That(StringComparer.Ordinal == StringComparer.Ordinal.DeepClone(), Is.True); 394 | Assert.That(StringComparer.OrdinalIgnoreCase == StringComparer.OrdinalIgnoreCase.DeepClone(), Is.True); 395 | Assert.That(StringComparer.InvariantCulture == StringComparer.InvariantCulture.DeepClone(), Is.True); 396 | Assert.That(StringComparer.InvariantCultureIgnoreCase == StringComparer.InvariantCultureIgnoreCase.DeepClone(), Is.True); 397 | Assert.That(StringComparer.CurrentCulture == StringComparer.CurrentCulture.DeepClone(), Is.True); 398 | Assert.That(StringComparer.CurrentCultureIgnoreCase == StringComparer.CurrentCultureIgnoreCase.DeepClone(), Is.True); 399 | } 400 | 401 | private void Check_Comparer_does_not_Clone_Internal() 402 | { 403 | var comparer = EqualityComparer.Default; 404 | var cloned = comparer.DeepClone(); 405 | 406 | // checking by reference 407 | Assert.That(comparer == cloned, Is.True); 408 | } 409 | } 410 | } 411 | -------------------------------------------------------------------------------- /DeepCloner.Tests/BaseTest.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | using Force.DeepCloner.Helpers; 4 | 5 | namespace Force.DeepCloner.Tests 6 | { 7 | public class BaseTest 8 | { 9 | public BaseTest(bool isSafeInit) 10 | { 11 | SwitchTo(isSafeInit); 12 | } 13 | 14 | public static void SwitchTo(bool isSafeInit) 15 | { 16 | typeof(ShallowObjectCloner).GetMethod("SwitchTo", BindingFlags.NonPublic | BindingFlags.Static) 17 | .Invoke(null, new object[] { isSafeInit }); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /DeepCloner.Tests/CloneExtensionsSpec.cs: -------------------------------------------------------------------------------- 1 | #if !NETCORE 2 | using CloneExtensions; 3 | 4 | using NUnit.Framework; 5 | 6 | namespace Force.DeepCloner.Tests 7 | { 8 | [TestFixture(false)] 9 | [TestFixture(true)] 10 | public class CloneExtensionsSpec : BaseTest 11 | { 12 | public CloneExtensionsSpec(bool isSafeInit) 13 | : base(isSafeInit) 14 | { 15 | } 16 | 17 | public class C1 18 | { 19 | public int X { get; set; } 20 | } 21 | 22 | public class C2 : C1 23 | { 24 | public new int X { get; set; } 25 | } 26 | 27 | public class C3 : C1 28 | { 29 | public int Y { get; set; } 30 | } 31 | 32 | public class C4 33 | { 34 | public C1 A { get; set; } 35 | 36 | public C1 B { get; set; } 37 | } 38 | 39 | public struct S5 40 | { 41 | public C1 A { get; set; } 42 | 43 | public C1 B { get; set; } 44 | } 45 | 46 | public class C6 47 | { 48 | public C6 A { get; set; } 49 | } 50 | 51 | [Test, Ignore("Not works")] 52 | public void Ensure_Parent_Prop_Clone() 53 | { 54 | var c2 = new C2(); 55 | var c1 = c2 as C1; 56 | c2.X = 2; 57 | c1.X = 1; 58 | var c2Clone = c2.GetClone(); 59 | Assert.That(c2Clone.X, Is.EqualTo(1)); 60 | Assert.That((c2Clone as C1).X, Is.EqualTo(1)); 61 | } 62 | 63 | [Test] 64 | public void Ensure_Parent_Prop_Clone2() 65 | { 66 | var c3 = new C3(); 67 | c3.Y = 2; 68 | c3.X = 1; 69 | var c3Clone = c3.GetClone(); 70 | Assert.That(c3Clone.Y, Is.EqualTo(2)); 71 | Assert.That(c3Clone.X, Is.EqualTo(1)); 72 | } 73 | 74 | [Test, Ignore("Not works")] 75 | public void Ensure_Cast_To_Parent_Clone() 76 | { 77 | var c3 = new C3(); 78 | var c1 = c3 as C1; 79 | c1.X = 1; 80 | c3.Y = 2; 81 | var c1Clone = c1.GetClone(); 82 | Assert.That(c1Clone.X, Is.EqualTo(1)); 83 | Assert.That((c1Clone as C3).Y, Is.EqualTo(2)); 84 | } 85 | 86 | [Test] 87 | public void Class_Should_Be_Deep_Copied() 88 | { 89 | var c4 = new C4(); 90 | var c1 = new C1 { X = 1 }; 91 | c4.A = c1; 92 | c4.B = c1; 93 | var c4Clone = c4.GetClone(); 94 | c1.X = 2; 95 | Assert.That(c4Clone.A.X, Is.EqualTo(1)); 96 | Assert.That(c4Clone.B.X, Is.EqualTo(1)); 97 | Assert.That(c4Clone.A, Is.EqualTo(c4Clone.B)); 98 | } 99 | 100 | [Test] 101 | public void Struct_Should_Be_Deep_Copied() 102 | { 103 | var s5 = new S5(); 104 | var c1 = new C1 { X = 1 }; 105 | s5.A = c1; 106 | s5.B = c1; 107 | var c4Clone = s5.GetClone(); 108 | c1.X = 2; 109 | Assert.That(c4Clone.A.X, Is.EqualTo(1)); 110 | Assert.That(c4Clone.B.X, Is.EqualTo(1)); 111 | Assert.That(c4Clone.A, Is.EqualTo(c4Clone.B)); 112 | } 113 | 114 | [Test] 115 | public void Array_Should_Be_Deep_Copied() 116 | { 117 | var c1 = new C1 { X = 1 }; 118 | var a = new[] { c1, c1 }; 119 | 120 | var aClone = a.GetClone(); 121 | Assert.That(aClone[0].X, Is.EqualTo(1)); 122 | Assert.That(aClone[1].X, Is.EqualTo(1)); 123 | Assert.That(aClone[0], Is.EqualTo(aClone[1])); 124 | } 125 | 126 | [Test] 127 | public void CrossRef_Should_be_Cloned() 128 | { 129 | var c = new C6(); 130 | c.A = c; 131 | 132 | var aClone = c.GetClone(); 133 | Assert.That(aClone.A, Is.EqualTo(aClone)); 134 | Assert.That(aClone.A, Is.Not.EqualTo(c)); 135 | } 136 | 137 | [Test, Ignore("Should manually declare constructor")] 138 | public void AnonymousType_Should_be_Cloned() 139 | { 140 | var c = new { X = 1 }.GetClone(); 141 | Assert.That(c.X, Is.EqualTo(1)); 142 | } 143 | } 144 | } 145 | #endif -------------------------------------------------------------------------------- /DeepCloner.Tests/ClrExceptionSpec.cs: -------------------------------------------------------------------------------- 1 | #if !NETCORE 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | using NUnit.Framework; 6 | 7 | using ServiceStack.FluentValidation; 8 | 9 | namespace Force.DeepCloner.Tests 10 | { 11 | [TestFixture] 12 | public class ClrExpectionSpec 13 | { 14 | public sealed class ItemToBeCloned2 : BaseClassForTest2 15 | { 16 | public SomeCollection2 SomeCollectionProperty { get; set; } 17 | 18 | public ItemToBeCloned2() 19 | { 20 | SomeCollectionProperty = new SomeCollection2(); 21 | } 22 | 23 | protected override IValidator GetValidator() 24 | { 25 | return new FVInventoryValidator(); 26 | } 27 | 28 | private class FVInventoryValidator : AbstractValidator 29 | { 30 | public FVInventoryValidator() 31 | { 32 | RuleFor(x => x.SomeCollectionProperty).SetValidator(new SomeCollection2.SomeCollectionValidator2()); 33 | } 34 | } 35 | 36 | public class SomeCollection2 : List 37 | { 38 | internal SomeCollection2() 39 | { 40 | } 41 | 42 | #region Validation 43 | 44 | internal sealed class SomeCollectionValidator2 : AbstractValidator 45 | { 46 | public SomeCollectionValidator2() 47 | { 48 | /* 49 | * case 1: if commented out - no crash 50 | * case 2: if left as is then works only with BaseClassForTest2.Validate #region #1 51 | */ 52 | RuleFor(x => 1 == 1); 53 | } 54 | } 55 | 56 | #endregion 57 | } 58 | } 59 | 60 | public abstract class BaseClassForTest2 61 | { 62 | private IValidator _validator; 63 | 64 | protected virtual IValidator GetValidator() 65 | { 66 | return null; 67 | } 68 | 69 | public void Validate() 70 | { 71 | #region #1 This works 72 | 73 | //var validator = GetValidator(); 74 | //Console.WriteLine( validator ); 75 | 76 | #endregion 77 | 78 | #region #2 This crashes 79 | 80 | _validator = GetValidator(); 81 | 82 | #endregion 83 | } 84 | } 85 | 86 | [Test] 87 | [Repeat(1000)] 88 | public void TestMethod2() 89 | { 90 | // typeof(ShallowObjectCloner).GetMethod("SwitchTo", BindingFlags.NonPublic | BindingFlags.Static) 91 | // .Invoke(null, new object[] { true }); 92 | 93 | // ServiceStack.FluentValidation.AbstractValidator 94 | var toBeCloned = new ItemToBeCloned2(); 95 | toBeCloned.Validate(); 96 | 97 | var cloned = DeepClonerExtensions.DeepClone(toBeCloned); 98 | 99 | Console.WriteLine(cloned); 100 | } 101 | } 102 | } 103 | #endif -------------------------------------------------------------------------------- /DeepCloner.Tests/ConstructorsSpec.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using NUnit.Framework; 4 | 5 | namespace Force.DeepCloner.Tests 6 | { 7 | #if !NETCORE 8 | [TestFixture(false)] 9 | #endif 10 | [TestFixture(true)] 11 | public class ConstructorsSpec : BaseTest 12 | { 13 | public ConstructorsSpec(bool isSafeInit) 14 | : base(isSafeInit) 15 | { 16 | } 17 | 18 | public class T1 19 | { 20 | private T1() 21 | { 22 | } 23 | 24 | public static T1 Create() 25 | { 26 | return new T1(); 27 | } 28 | 29 | public int X { get; set; } 30 | } 31 | 32 | public class T2 33 | { 34 | public T2(int arg1, int arg2) 35 | { 36 | } 37 | 38 | public int X { get; set; } 39 | } 40 | 41 | public class ExClass 42 | { 43 | public ExClass() 44 | { 45 | throw new Exception(); 46 | } 47 | 48 | public ExClass(string x) 49 | { 50 | // does not throw here 51 | } 52 | 53 | public override bool Equals(object obj) 54 | { 55 | throw new Exception(); 56 | } 57 | 58 | public override int GetHashCode() 59 | { 60 | throw new Exception(); 61 | } 62 | 63 | public override string ToString() 64 | { 65 | throw new Exception(); 66 | } 67 | } 68 | 69 | #if !NETCORE 70 | public class ClonableClass : ICloneable 71 | { 72 | public object X { get; set; } 73 | 74 | public object Clone() 75 | { 76 | throw new NotImplementedException(); 77 | } 78 | } 79 | 80 | [Test] 81 | public void Cloner_Should_Not_Call_Any_Method_Of_Clonable_Class() 82 | { 83 | // just for check, ensure no hidden behaviour in MemberwiseClone 84 | Assert.DoesNotThrow(() => new ClonableClass().DeepClone()); 85 | Assert.DoesNotThrow(() => new { X = new ClonableClass() }.DeepClone()); 86 | } 87 | #endif 88 | 89 | [Test] 90 | public void Object_With_Private_Constructor_Should_Be_Cloned() 91 | { 92 | var t1 = T1.Create(); 93 | t1.X = 42; 94 | var cloned = t1.DeepClone(); 95 | t1.X = 0; 96 | Assert.That(cloned.X, Is.EqualTo(42)); 97 | } 98 | 99 | [Test] 100 | public void Object_With_Complex_Constructor_Should_Be_Cloned() 101 | { 102 | var t2 = new T2(1, 2); 103 | t2.X = 42; 104 | var cloned = t2.DeepClone(); 105 | t2.X = 0; 106 | Assert.That(cloned.X, Is.EqualTo(42)); 107 | } 108 | 109 | [Test] 110 | public void Anonymous_Object_Should_Be_Cloned() 111 | { 112 | var t2 = new { A = 1, B = "x" }; 113 | var cloned = t2.DeepClone(); 114 | Assert.That(cloned.A, Is.EqualTo(1)); 115 | Assert.That(cloned.B, Is.EqualTo("x")); 116 | } 117 | 118 | #if !NETCORE 119 | private class C3 : ContextBoundObject 120 | { 121 | } 122 | 123 | private class C4 : MarshalByRefObject 124 | { 125 | } 126 | 127 | [Test] 128 | public void ContextBound_Object_Should_Be_Cloned() 129 | { 130 | // FormatterServices.CreateUninitializedObject cannot use context-bound objects 131 | var c = new C3(); 132 | var cloned = c.DeepClone(); 133 | Assert.That(cloned, Is.Not.Null); 134 | } 135 | 136 | [Test] 137 | public void MarshalByRef_Object_Should_Be_Cloned() 138 | { 139 | // FormatterServices.CreateUninitializedObject cannot use context-bound objects 140 | var c = new C4(); 141 | var cloned = c.DeepClone(); 142 | Assert.That(cloned, Is.Not.Null); 143 | } 144 | #endif 145 | 146 | [Test] 147 | public void Cloner_Should_Not_Call_Any_Method_Of_Class_Be_Cloned() 148 | { 149 | Assert.DoesNotThrow(() => new ExClass("x").DeepClone()); 150 | var exClass = new ExClass("x"); 151 | Assert.DoesNotThrow(() => new[] { exClass, exClass }.DeepClone()); 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /DeepCloner.Tests/CopyToObjectSpec.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using NUnit.Framework; 4 | 5 | namespace Force.DeepCloner.Tests 6 | { 7 | [TestFixture] 8 | public class CopyToObjectSpec 9 | { 10 | public class C1 11 | { 12 | public int A { get; set; } 13 | 14 | public virtual string B { get; set; } 15 | 16 | public byte[] C { get; set; } 17 | } 18 | 19 | public class C2 : C1 20 | { 21 | public decimal D { get; set; } 22 | 23 | public new int A { get; set; } 24 | } 25 | 26 | public class C4 : C1 27 | { 28 | } 29 | 30 | public class C3 31 | { 32 | public C1 A { get; set; } 33 | 34 | public C1 B { get; set; } 35 | } 36 | 37 | public interface I1 38 | { 39 | int A { get; set; } 40 | } 41 | 42 | public struct S1 : I1 43 | { 44 | public int A { get; set; } 45 | } 46 | 47 | [Test] 48 | [TestCase(false)] 49 | [TestCase(true)] 50 | public void Simple_Class_Should_Be_Cloned(bool isDeep) 51 | { 52 | var cFrom = new C1 53 | { 54 | A = 12, 55 | B = "testestest", 56 | C = new byte[] { 1, 2, 3 } 57 | }; 58 | 59 | var cTo = new C1 60 | { 61 | A = 11, 62 | B = "tes", 63 | C = new byte[] { 1 } 64 | }; 65 | 66 | var cToRef = cTo; 67 | 68 | if (isDeep) 69 | cFrom.DeepCloneTo(cTo); 70 | else 71 | cFrom.ShallowCloneTo(cTo); 72 | 73 | Assert.That(ReferenceEquals(cTo, cToRef), Is.True); 74 | Assert.That(cTo.A, Is.EqualTo(12)); 75 | Assert.That(cTo.B, Is.EqualTo("testestest")); 76 | Assert.That(cTo.C.Length, Is.EqualTo(3)); 77 | Assert.That(cTo.C[2], Is.EqualTo(3)); 78 | } 79 | 80 | [Test] 81 | [TestCase(false)] 82 | [TestCase(true)] 83 | public void Descendant_Class_Should_Be_Cloned(bool isDeep) 84 | { 85 | var cFrom = new C1 86 | { 87 | A = 12, 88 | B = "testestest", 89 | C = new byte[] { 1, 2, 3 } 90 | }; 91 | 92 | var cTo = new C2 93 | { 94 | A = 11, 95 | D = 42.3m 96 | }; 97 | 98 | var cToRef = cTo; 99 | 100 | if (isDeep) 101 | cFrom.DeepCloneTo(cTo); 102 | else 103 | cFrom.ShallowCloneTo(cTo); 104 | 105 | Assert.That(ReferenceEquals(cTo, cToRef), Is.True); 106 | Assert.That(cTo.A, Is.EqualTo(11)); 107 | Assert.That(((C1)cTo).A, Is.EqualTo(12)); 108 | Assert.That(cTo.D, Is.EqualTo(42.3m)); 109 | } 110 | 111 | [Test] 112 | public void Class_With_Subclass_Should_Be_Shallow_CLoned() 113 | { 114 | var c1 = new C1 { A = 12 }; 115 | var cFrom = new C3 { A = c1, B = c1 }; 116 | var cTo = cFrom.ShallowCloneTo(new C3()); 117 | Assert.That(ReferenceEquals(cFrom.A, cTo.A), Is.True); 118 | Assert.That(ReferenceEquals(cFrom.B, cTo.B), Is.True); 119 | Assert.That(ReferenceEquals(cTo.A, cTo.B), Is.True); 120 | } 121 | 122 | [Test] 123 | public void Class_With_Subclass_Should_Be_Deep_CLoned() 124 | { 125 | var c1 = new C1 { A = 12 }; 126 | var cFrom = new C3 { A = c1, B = c1 }; 127 | var cTo = cFrom.DeepCloneTo(new C3()); 128 | Assert.That(ReferenceEquals(cFrom.A, cTo.A), Is.False); 129 | Assert.That(ReferenceEquals(cFrom.B, cTo.B), Is.False); 130 | Assert.That(ReferenceEquals(cTo.A, cTo.B), Is.True); 131 | } 132 | 133 | [Test] 134 | [TestCase(false)] 135 | [TestCase(true)] 136 | public void Copy_To_Null_Should_Return_Null(bool isDeep) 137 | { 138 | var c1 = new C1(); 139 | if (isDeep) 140 | Assert.That(c1.DeepCloneTo((C1)null), Is.Null); 141 | else 142 | Assert.That(c1.ShallowCloneTo((C1)null), Is.Null); 143 | } 144 | 145 | [Test] 146 | [TestCase(false)] 147 | [TestCase(true)] 148 | public void Copy_From_Null_Should_Throw_Error(bool isDeep) 149 | { 150 | C1 c1 = null; 151 | if (isDeep) 152 | // ReSharper disable once ExpressionIsAlwaysNull 153 | Assert.Throws(() => c1.DeepCloneTo(new C1())); 154 | else 155 | // ReSharper disable once ExpressionIsAlwaysNull 156 | Assert.Throws(() => c1.ShallowCloneTo(new C1())); 157 | } 158 | 159 | [Test] 160 | [TestCase(false)] 161 | [TestCase(true)] 162 | public void Invalid_Inheritance_Should_Throw_Error(bool isDeep) 163 | { 164 | C1 c1 = new C4(); 165 | if (isDeep) 166 | // ReSharper disable once ExpressionIsAlwaysNull 167 | Assert.Throws(() => c1.DeepCloneTo(new C2())); 168 | else 169 | // ReSharper disable once ExpressionIsAlwaysNull 170 | Assert.Throws(() => c1.ShallowCloneTo(new C2())); 171 | } 172 | 173 | [Test] 174 | [TestCase(false)] 175 | [TestCase(true)] 176 | public void Struct_As_Interface_ShouldNot_Be_Cloned(bool isDeep) 177 | { 178 | S1 sFrom = new S1 { A = 42 }; 179 | S1 sTo = new S1(); 180 | var objTo = (I1)sTo; 181 | objTo.A = 23; 182 | if (isDeep) 183 | // ReSharper disable once ExpressionIsAlwaysNull 184 | Assert.Throws(() => ((I1)sFrom).DeepCloneTo(objTo)); 185 | else 186 | // ReSharper disable once ExpressionIsAlwaysNull 187 | Assert.Throws(() => ((I1)sFrom).ShallowCloneTo(objTo)); 188 | } 189 | 190 | [Test] 191 | public void String_Should_Not_Be_Cloned() 192 | { 193 | var s1 = "abc"; 194 | var s2 = "def"; 195 | Assert.Throws(() => s1.ShallowCloneTo(s2)); 196 | } 197 | 198 | [Test] 199 | [TestCase(false)] 200 | [TestCase(true)] 201 | public void Array_Should_Be_Cloned_Correct_Size(bool isDeep) 202 | { 203 | var arrFrom = new[] { 1, 2, 3 }; 204 | var arrTo = new[] { 4, 5, 6 }; 205 | if (isDeep) arrFrom.DeepCloneTo(arrTo); 206 | else arrFrom.ShallowCloneTo(arrTo); 207 | Assert.That(arrTo.Length, Is.EqualTo(3)); 208 | Assert.That(arrTo[0], Is.EqualTo(1)); 209 | Assert.That(arrTo[1], Is.EqualTo(2)); 210 | Assert.That(arrTo[2], Is.EqualTo(3)); 211 | } 212 | 213 | [Test] 214 | [TestCase(false)] 215 | [TestCase(true)] 216 | public void Array_Should_Be_Cloned_From_Is_Bigger(bool isDeep) 217 | { 218 | var arrFrom = new[] { 1, 2, 3 }; 219 | var arrTo = new[] { 4, 5 }; 220 | if (isDeep) arrFrom.DeepCloneTo(arrTo); 221 | else arrFrom.ShallowCloneTo(arrTo); 222 | Assert.That(arrTo.Length, Is.EqualTo(2)); 223 | Assert.That(arrTo[0], Is.EqualTo(1)); 224 | Assert.That(arrTo[1], Is.EqualTo(2)); 225 | } 226 | 227 | [Test] 228 | [TestCase(false)] 229 | [TestCase(true)] 230 | public void Array_Should_Be_Cloned_From_Is_Smaller(bool isDeep) 231 | { 232 | var arrFrom = new[] { 1, 2 }; 233 | var arrTo = new[] { 4, 5, 6 }; 234 | if (isDeep) arrFrom.DeepCloneTo(arrTo); 235 | else arrFrom.ShallowCloneTo(arrTo); 236 | Assert.That(arrTo.Length, Is.EqualTo(3)); 237 | Assert.That(arrTo[0], Is.EqualTo(1)); 238 | Assert.That(arrTo[1], Is.EqualTo(2)); 239 | Assert.That(arrTo[2], Is.EqualTo(6)); 240 | } 241 | 242 | [Test] 243 | public void Shallow_Array_Should_Be_Cloned() 244 | { 245 | var c1 = new C1(); 246 | var arrFrom = new[] { c1, c1, c1 }; 247 | var arrTo = new C1[4]; 248 | arrFrom.ShallowCloneTo(arrTo); 249 | Assert.That(arrTo.Length, Is.EqualTo(4)); 250 | Assert.That(arrTo[0], Is.EqualTo(c1)); 251 | Assert.That(arrTo[1], Is.EqualTo(c1)); 252 | Assert.That(arrTo[2], Is.EqualTo(c1)); 253 | Assert.That(arrTo[3], Is.Null); 254 | } 255 | 256 | [Test] 257 | public void Deep_Array_Should_Be_Cloned() 258 | { 259 | var c1 = new C4(); 260 | var c3 = new C3 { A = c1, B = c1 }; 261 | var arrFrom = new[] { c3, c3, c3 }; 262 | var arrTo = new C3[4]; 263 | arrFrom.DeepCloneTo(arrTo); 264 | Assert.That(arrTo.Length, Is.EqualTo(4)); 265 | Assert.That(arrTo[0], Is.Not.EqualTo(c1)); 266 | Assert.That(arrTo[0], Is.EqualTo(arrTo[1])); 267 | Assert.That(arrTo[1], Is.EqualTo(arrTo[2])); 268 | Assert.That(arrTo[2].A, Is.Not.EqualTo(c1)); 269 | Assert.That(arrTo[2].A, Is.EqualTo(arrTo[2].B)); 270 | Assert.That(arrTo[3], Is.Null); 271 | } 272 | 273 | [Test] 274 | [TestCase(false)] 275 | [TestCase(true)] 276 | public void Non_Zero_Based_Array_Should_Be_Cloned(bool isDeep) 277 | { 278 | var arrFrom = Array.CreateInstance(typeof(int), new[] { 2 }, new[] { 1 }); 279 | // with offset. its ok 280 | var arrTo = Array.CreateInstance(typeof(int), new[] { 2 }, new[] { 0 }); 281 | arrFrom.SetValue(1, 1); 282 | arrFrom.SetValue(2, 2); 283 | if (isDeep) arrFrom.DeepCloneTo(arrTo); 284 | else arrFrom.ShallowCloneTo(arrTo); 285 | Assert.That(arrTo.Length, Is.EqualTo(2)); 286 | Assert.That(arrTo.GetValue(0), Is.EqualTo(1)); 287 | Assert.That(arrTo.GetValue(1), Is.EqualTo(2)); 288 | } 289 | 290 | [Test] 291 | [TestCase(false)] 292 | [TestCase(true)] 293 | public void MultiDim_Array_Should_Be_Cloned(bool isDeep) 294 | { 295 | var arrFrom = Array.CreateInstance(typeof(int), new[] { 2, 2 }, new[] { 1, 1 }); 296 | // with offset. its ok 297 | var arrTo = Array.CreateInstance(typeof(int), new[] { 1, 1 }, new[] { 0, 0 }); 298 | arrFrom.SetValue(1, 1, 1); 299 | arrFrom.SetValue(2, 2, 2); 300 | if (isDeep) arrFrom.DeepCloneTo(arrTo); 301 | else arrFrom.ShallowCloneTo(arrTo); 302 | Assert.That(arrTo.Length, Is.EqualTo(1)); 303 | Assert.That(arrTo.GetValue(0, 0), Is.EqualTo(1)); 304 | } 305 | 306 | [Test] 307 | [TestCase(false)] 308 | [TestCase(true)] 309 | public void TwoDim_Array_Should_Be_Cloned(bool isDeep) 310 | { 311 | var arrFrom = new[,] { { 1, 2 }, { 3, 4 } }; 312 | // with offset. its ok 313 | var arrTo = new int[3, 1]; 314 | if (isDeep) arrFrom.DeepCloneTo(arrTo); 315 | else arrFrom.ShallowCloneTo(arrTo); 316 | Assert.That(arrTo[0, 0], Is.EqualTo(1)); 317 | Assert.That(arrTo[1, 0], Is.EqualTo(3)); 318 | 319 | arrTo = new int[2, 2]; 320 | if (isDeep) arrFrom.DeepCloneTo(arrTo); 321 | else arrFrom.ShallowCloneTo(arrTo); 322 | Assert.That(arrTo[0, 0], Is.EqualTo(1)); 323 | Assert.That(arrTo[0, 1], Is.EqualTo(2)); 324 | Assert.That(arrTo[1, 0], Is.EqualTo(3)); 325 | } 326 | 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 | 362 | [Test] 363 | public void Shallow_Clone_Of_MultiDim_Array_Should_Not_Perform_Deep() 364 | { 365 | var c1 = new C1(); 366 | var arrFrom = new[,] { { c1, c1 }, { c1, c1 } }; 367 | // with offset. its ok 368 | var arrTo = new C1[3, 1]; 369 | arrFrom.ShallowCloneTo(arrTo); 370 | Assert.That(ReferenceEquals(c1, arrTo[0, 0]), Is.True); 371 | Assert.That(ReferenceEquals(c1, arrTo[1, 0]), Is.True); 372 | 373 | var arrFrom2 = new C1[1, 1, 1]; 374 | arrFrom2[0, 0, 0] = c1; 375 | var arrTo2 = new C1[1, 1, 1]; 376 | arrFrom2.ShallowCloneTo(arrTo2); 377 | Assert.That(ReferenceEquals(c1, arrTo2[0, 0, 0]), Is.True); 378 | } 379 | 380 | [Test] 381 | public void Deep_Clone_Of_MultiDim_Array_Should_Perform_Deep() 382 | { 383 | var c1 = new C1(); 384 | var arrFrom = new[,] { { c1, c1 }, { c1, c1 } }; 385 | // with offset. its ok 386 | var arrTo = new C1[3, 1]; 387 | arrFrom.DeepCloneTo(arrTo); 388 | Assert.That(ReferenceEquals(c1, arrTo[0, 0]), Is.False); 389 | Assert.That(ReferenceEquals(arrTo[0, 0], arrTo[1, 0]), Is.True); 390 | 391 | var arrFrom2 = new C1[1, 1, 2]; 392 | arrFrom2[0, 0, 0] = c1; 393 | arrFrom2[0, 0, 1] = c1; 394 | var arrTo2 = new C1[1, 1, 2]; 395 | arrFrom2.DeepCloneTo(arrTo2); 396 | Assert.That(ReferenceEquals(c1, arrTo2[0, 0, 0]), Is.False); 397 | Assert.That(ReferenceEquals(arrTo2[0, 0, 1], arrTo2[0, 0, 0]), Is.True); 398 | } 399 | 400 | [Test] 401 | public void Dictionary_Should_Be_Deeply_Cloned() 402 | { 403 | var d1 = new Dictionary{ { "A", "B" }, { "C", "D" } }; 404 | var d2 = new Dictionary(); 405 | d1.DeepCloneTo(d2); 406 | d1["A"] = "E"; 407 | Assert.That(d2.Count, Is.EqualTo(2)); 408 | Assert.That(d2["A"], Is.EqualTo("B")); 409 | Assert.That(d2["C"], Is.EqualTo("D")); 410 | 411 | // big dictionary 412 | d1.Clear(); 413 | for (var i = 0; i < 1000; i++) 414 | d1[i.ToString()] = i.ToString(); 415 | d1.DeepCloneTo(d2); 416 | Assert.That(d2.Count, Is.EqualTo(1000)); 417 | Assert.That(d2["557"], Is.EqualTo("557")); 418 | } 419 | 420 | public class D1 421 | { 422 | public int A { get; set; } 423 | } 424 | 425 | public class D2 : D1 426 | { 427 | public int B { get; set; } 428 | 429 | public D2(D1 d1) 430 | { 431 | B = 14; 432 | d1.DeepCloneTo(this); 433 | } 434 | } 435 | 436 | [Test] 437 | public void Inner_Implementation_In_Class_Should_Work() 438 | { 439 | var baseObject = new D1 { A = 12 }; 440 | var wrapper = new D2(baseObject); 441 | Assert.That(wrapper.A, Is.EqualTo(12)); 442 | Assert.That(wrapper.B, Is.EqualTo(14)); 443 | } 444 | } 445 | } -------------------------------------------------------------------------------- /DeepCloner.Tests/DeepCloner.Tests.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.0;netcoreapp3.1;netcoreapp6.0;net461 4 | portable 5 | DeepCloner.NET.Tests 6 | Library 7 | true 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | $(DefineConstants);NETCORE;NETCORE13 46 | 47 | 48 | $(DefineConstants);NETCORE;NETCORE20 49 | 50 | 51 | $(DefineConstants);NETCORE;NETCORE31 52 | 53 | 54 | $(DefineConstants);NETCORE;NETCORE60 55 | 56 | 57 | $(DefineConstants);COREVERSION 58 | 59 | 60 | true 61 | 62 | 63 | true 64 | 65 | -------------------------------------------------------------------------------- /DeepCloner.Tests/DeepCloner.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {3453CB0A-BE66-4770-B92E-7A5917AD56A1} 8 | Library 9 | Properties 10 | Force.DeepCloner.Tests 11 | DeepCloner.Tests 12 | v4.6.1 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | true 25 | AnyCPU 26 | false 27 | 28 | 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | true 36 | false 37 | 38 | 39 | 40 | ..\packages\Clone.Behave.1.0.1\lib\CloneBehave.dll 41 | 42 | 43 | ..\packages\DesertOctopus.0.1.1\lib\net452\DesertOctopus.dll 44 | 45 | 46 | ..\packages\CloneExtensions.1.2\lib\portable-net40+sl50+win+wp80\CloneExtensions.dll 47 | 48 | 49 | ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll 50 | True 51 | 52 | 53 | ..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll 54 | True 55 | 56 | 57 | ..\packages\FastDeepCloner.1.0.9\lib\portable-net45+sl4+wp8+win8\FastDeepCloner.dll 58 | True 59 | 60 | 61 | ..\packages\fasterflect.2.1.3\lib\net40\Fasterflect.dll 62 | 63 | 64 | ..\packages\GeorgeCloney.1.1.1.17\lib\net40\GeorgeCloney.dll 65 | 66 | 67 | False 68 | ..\packages\NClone.1.1.1\lib\net45\NClone.dll 69 | 70 | 71 | ..\packages\Nuclex.Cloning.1.0.0.0\lib\net40\Nuclex.Cloning.dll 72 | 73 | 74 | False 75 | ..\packages\NUnit.3.6.0\lib\net45\nunit.framework.dll 76 | 77 | 78 | ..\packages\ServiceStack.5.0.0\lib\net45\ServiceStack.dll 79 | True 80 | 81 | 82 | ..\packages\ServiceStack.Client.5.0.0\lib\net45\ServiceStack.Client.dll 83 | True 84 | 85 | 86 | ..\packages\ServiceStack.Common.5.0.0\lib\net45\ServiceStack.Common.dll 87 | True 88 | 89 | 90 | ..\packages\ServiceStack.Interfaces.5.0.0\lib\net45\ServiceStack.Interfaces.dll 91 | True 92 | 93 | 94 | ..\packages\ServiceStack.Text.5.0.0\lib\net45\ServiceStack.Text.dll 95 | True 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | {6BB0A0AB-67F9-45E4-B60C-4CEED098D463} 136 | DeepCloner 137 | 138 | 139 | 140 | 147 | -------------------------------------------------------------------------------- /DeepCloner.Tests/GenericsSpec.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using NUnit.Framework; 4 | 5 | namespace Force.DeepCloner.Tests 6 | { 7 | #if !NETCORE 8 | [TestFixture(false)] 9 | #endif 10 | [TestFixture(true)] 11 | public class GenericsSpec : BaseTest 12 | { 13 | public GenericsSpec(bool isSafeInit) 14 | : base(isSafeInit) 15 | { 16 | } 17 | 18 | [Test] 19 | public void Tuple_Should_Be_Cloned() 20 | { 21 | var c = new Tuple(1, 2).DeepClone(); 22 | Assert.That(c.Item1, Is.EqualTo(1)); 23 | Assert.That(c.Item2, Is.EqualTo(2)); 24 | 25 | c = new Tuple(1, 2).ShallowClone(); 26 | Assert.That(c.Item1, Is.EqualTo(1)); 27 | Assert.That(c.Item2, Is.EqualTo(2)); 28 | 29 | var cc = new Tuple(1, 2, 3, 4, 5, 6, 7).DeepClone(); 30 | Assert.That(cc.Item7, Is.EqualTo(7)); 31 | 32 | var tuple = new Tuple>(1, new Generic()); 33 | tuple.Item2.Value = tuple; 34 | var ccc = tuple.DeepClone(); 35 | Assert.That(ccc, Is.EqualTo(ccc.Item2.Value)); 36 | } 37 | 38 | [Test] 39 | public void Generic_Should_Be_Cloned() 40 | { 41 | var c = new Generic(); 42 | c.Value = 12; 43 | Assert.That(c.DeepClone().Value, Is.EqualTo(12)); 44 | 45 | var c2 = new Generic(); 46 | c2.Value = 12; 47 | Assert.That(c2.DeepClone().Value, Is.EqualTo(12)); 48 | } 49 | 50 | public class C1 51 | { 52 | public int X { get; set; } 53 | } 54 | 55 | public class C2 : C1 56 | { 57 | public int Y { get; set; } 58 | } 59 | 60 | public class Generic 61 | { 62 | public T Value { get; set; } 63 | } 64 | 65 | [Test] 66 | public void Tuple_Should_Be_Cloned_With_Inheritance_And_Same_Object() 67 | { 68 | var c2 = new C2 { X = 1, Y = 2 }; 69 | var c = new Tuple(c2, c2).DeepClone(); 70 | var cs = new Tuple(c2, c2).ShallowClone(); 71 | c2.X = 42; 72 | c2.Y = 42; 73 | Assert.That(c.Item1.X, Is.EqualTo(1)); 74 | Assert.That(c.Item2.Y, Is.EqualTo(2)); 75 | Assert.That(c.Item2, Is.EqualTo(c.Item1)); 76 | 77 | Assert.That(cs.Item1.X, Is.EqualTo(42)); 78 | Assert.That(cs.Item2.Y, Is.EqualTo(42)); 79 | Assert.That(cs.Item2, Is.EqualTo(cs.Item1)); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /DeepCloner.Tests/Imported/FastDeepCloner.cs: -------------------------------------------------------------------------------- 1 | #if !NETCORE 2 | // copied from https://raw.githubusercontent.com/Alenah091/FastDeepCloner/master/FastDeepCloner.cs because I need .NET 4.0 for tests 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Runtime.Serialization; 9 | 10 | namespace Force.DeepCloner.Tests.Imported 11 | { 12 | /// 13 | /// Supports cloning, which creates a new instance of a class with the same value as an existing instance. 14 | /// Used to deep clone objects, whether they are serializable or not. 15 | /// 16 | public class FastDeepCloner_Copy 17 | { 18 | #region Private fields 19 | private const BindingFlags Binding = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy; 20 | private Type _primaryType; 21 | private object _desireObjectToBeCloned; 22 | private int _length; 23 | private bool _isArray; 24 | private bool _isDictionary; 25 | private bool _isList; 26 | private int _rank; 27 | private bool? _initPublicOnly; 28 | private Type _ignorePropertiesWithAttribute; 29 | private static IDictionary> _cachedFields; 30 | private static IDictionary> _cachedPropertyInfo; 31 | private FieldType_Copy _fieldType; 32 | private IDictionary _alreadyCloned; 33 | #endregion 34 | 35 | #region Constructors 36 | public FastDeepCloner_Copy(object desireObjectToBeCloned, FieldType_Copy fieldType) 37 | { 38 | if (desireObjectToBeCloned == null) 39 | { 40 | throw new ArgumentNullException("desireObjectToBeCloned"); 41 | } 42 | 43 | DataBind(desireObjectToBeCloned, fieldType, null, false); 44 | } 45 | 46 | public FastDeepCloner_Copy(object desireObjectToBeCloned, FieldType_Copy fieldType = FieldType_Copy.FieldInfo, Type ignorePropertiesWithAttribute = null, bool? initPublicOnly = null) 47 | { 48 | if (desireObjectToBeCloned == null) 49 | { 50 | throw new ArgumentNullException("desireObjectToBeCloned"); 51 | } 52 | 53 | DataBind(desireObjectToBeCloned, fieldType, ignorePropertiesWithAttribute, initPublicOnly); 54 | } 55 | #endregion 56 | 57 | #region Public method clone 58 | /// 59 | /// Creates a new object that is a copy of the current instance. 60 | /// 61 | /// A new object that is a copy of this instance. 62 | public object Clone() 63 | { 64 | return DeepClone(); 65 | } 66 | 67 | /// 68 | /// Creates a new object that is a copy of the current instance. 69 | /// 70 | /// A new object that is a copy of this instance. 71 | public T Clone() 72 | { 73 | return (T)DeepClone(); 74 | } 75 | #endregion 76 | 77 | #region Private method deep clone 78 | private void DataBind(object desireObjectToBeCloned, FieldType_Copy fieldType = FieldType_Copy.FieldInfo, Type ignorePropertiesWithAttribute = null, bool? initPublicOnly = null, IDictionary alreadyCloned = null) 79 | { 80 | if (desireObjectToBeCloned == null) 81 | return; 82 | if (_cachedFields == null) 83 | _cachedFields = new Dictionary>(); 84 | if (_cachedPropertyInfo == null) 85 | _cachedPropertyInfo = new Dictionary>(); 86 | 87 | _alreadyCloned = alreadyCloned ?? new Dictionary(); 88 | _ignorePropertiesWithAttribute = ignorePropertiesWithAttribute; 89 | _primaryType = desireObjectToBeCloned.GetType(); 90 | _desireObjectToBeCloned = desireObjectToBeCloned; 91 | _isArray = _primaryType.IsArray; 92 | _initPublicOnly = initPublicOnly; 93 | _fieldType = fieldType; 94 | 95 | if (_isArray) 96 | { 97 | var array = (Array)desireObjectToBeCloned; 98 | _length = array.Length; 99 | _rank = array.Rank; 100 | } 101 | else if ((desireObjectToBeCloned as IList) != null) 102 | _isList = true; 103 | else if (typeof(IDictionary).IsAssignableFrom(_primaryType)) 104 | _isDictionary = true; 105 | } 106 | 107 | /// 108 | /// Clone the object properties and its children recursively. 109 | /// 110 | /// 111 | private object DeepClone() 112 | { 113 | if (_desireObjectToBeCloned == null) 114 | return null; 115 | // If the item is array of type more than one dimension then use Array.Clone 116 | if (_isArray && _rank > 1) 117 | return ((Array)_desireObjectToBeCloned).Clone(); 118 | 119 | object tObject; 120 | // Clone IList or Array 121 | if (_isArray || _isList) 122 | { 123 | tObject = _isArray ? Array.CreateInstance(_primaryType.GetElementType(), _length) : Activator.CreateInstance(typeof(List<>).MakeGenericType(_primaryType.GetProperties().Last().PropertyType)); 124 | var i = 0; 125 | foreach (var item in (IList)_desireObjectToBeCloned) 126 | { 127 | object clonedIteam = null; 128 | if (item != null) 129 | { 130 | var underlyingSystemType = item.GetType().UnderlyingSystemType; 131 | clonedIteam = (item is string || !underlyingSystemType.IsClass || IsInternalType(underlyingSystemType)) 132 | ? item 133 | : new FastDeepCloner_Copy(item, _fieldType, _ignorePropertiesWithAttribute, _initPublicOnly, _alreadyCloned).DeepClone(); 134 | } 135 | if (!_isArray) 136 | ((IList)tObject).Add(clonedIteam); 137 | else 138 | ((Array)tObject).SetValue(clonedIteam, i); 139 | 140 | i++; 141 | } 142 | } 143 | else if (_isDictionary) // Clone IDictionary 144 | { 145 | tObject = Activator.CreateInstance(_primaryType); 146 | var dictionary = (IDictionary)_desireObjectToBeCloned; 147 | foreach (var key in dictionary.Keys) 148 | { 149 | var item = dictionary[key]; 150 | object clonedIteam = null; 151 | if (item != null) 152 | { 153 | var underlyingSystemType = item.GetType().UnderlyingSystemType; 154 | clonedIteam = (item is string || !underlyingSystemType.IsClass || IsInternalType(underlyingSystemType)) 155 | ? item 156 | : new FastDeepCloner_Copy(item, _fieldType, _ignorePropertiesWithAttribute, _initPublicOnly, _alreadyCloned).DeepClone(); 157 | } 158 | ((IDictionary)tObject).Add(key, clonedIteam); 159 | } 160 | } 161 | else 162 | { 163 | // Create an empty object and ignore its constructor. 164 | tObject = FormatterServices.GetUninitializedObject(_primaryType); 165 | var fullPath = _primaryType.Name; 166 | if (_fieldType == FieldType_Copy.PropertyInfo) 167 | { 168 | if (!_cachedPropertyInfo.ContainsKey(_primaryType)) 169 | { 170 | var properties = new List(); 171 | if (_primaryType.BaseType != null && _primaryType.BaseType.Name != "Object") 172 | { 173 | properties.AddRange(_primaryType.BaseType.GetProperties(Binding)); 174 | properties.AddRange(_primaryType.GetProperties(Binding | BindingFlags.DeclaredOnly)); 175 | } 176 | else properties.AddRange(_primaryType.GetProperties(Binding)); 177 | 178 | _cachedPropertyInfo.Add(_primaryType, properties); 179 | if (_ignorePropertiesWithAttribute != null) 180 | _cachedPropertyInfo[_primaryType].RemoveAll( 181 | x => x.GetCustomAttributes(_ignorePropertiesWithAttribute, false).FirstOrDefault() != null); 182 | } 183 | } 184 | else if (!_cachedFields.ContainsKey(_primaryType)) 185 | { 186 | var properties = new List(); 187 | if (_primaryType.BaseType != null && _primaryType.BaseType.Name != "Object") 188 | { 189 | properties.AddRange(_primaryType.BaseType.GetFields(Binding)); 190 | properties.AddRange(_primaryType.GetFields(Binding | BindingFlags.DeclaredOnly)); 191 | } 192 | else properties.AddRange(_primaryType.GetFields(Binding)); 193 | 194 | _cachedFields.Add(_primaryType, properties); 195 | if (_ignorePropertiesWithAttribute != null) 196 | _cachedFields[_primaryType].RemoveAll( 197 | x => x.GetCustomAttributes(_ignorePropertiesWithAttribute, false).FirstOrDefault() != null); 198 | } 199 | 200 | if (_fieldType == FieldType_Copy.FieldInfo) 201 | { 202 | foreach (var property in _cachedFields[_primaryType]) 203 | { 204 | // Validate if the property is a writable one. 205 | if (property.IsInitOnly || property.FieldType == typeof(System.IntPtr)) 206 | continue; 207 | if (_initPublicOnly.HasValue && _initPublicOnly.Value && !property.IsPublic) 208 | continue; 209 | if (_alreadyCloned.ContainsKey(fullPath + property.Name)) 210 | continue; 211 | var value = property.GetValue(_desireObjectToBeCloned); 212 | if (value == null) 213 | continue; 214 | 215 | if (!property.FieldType.IsClass || value is string) 216 | property.SetValue(tObject, value); 217 | else 218 | { 219 | _alreadyCloned.Add(fullPath + property.Name, true); 220 | property.SetValue(tObject, 221 | new FastDeepCloner_Copy(value, _fieldType, _ignorePropertiesWithAttribute, _initPublicOnly, 222 | _alreadyCloned).DeepClone()); 223 | } 224 | } 225 | } 226 | else 227 | { 228 | foreach (var property in _cachedPropertyInfo[_primaryType]) 229 | { 230 | // Validate if the property is a writable one. 231 | if (!property.CanWrite || !property.CanRead || property.PropertyType == typeof(System.IntPtr)) 232 | continue; 233 | if (_alreadyCloned.ContainsKey(fullPath + property.Name)) 234 | continue; 235 | var value = property.GetValue(_desireObjectToBeCloned, null); 236 | if (value == null) 237 | continue; 238 | 239 | if (!property.PropertyType.IsClass || value is string) 240 | property.SetValue(tObject, value, null); 241 | else 242 | { 243 | _alreadyCloned.Add(fullPath + property.Name, true); 244 | property.SetValue(tObject, 245 | new FastDeepCloner_Copy(value, _fieldType, _ignorePropertiesWithAttribute, _initPublicOnly, 246 | _alreadyCloned).DeepClone(), null); 247 | } 248 | } 249 | } 250 | } 251 | 252 | return tObject; 253 | } 254 | #endregion 255 | 256 | private FastDeepCloner_Copy(object desireObjectToBeCloned, FieldType_Copy fielType = FieldType_Copy.FieldInfo, Type ignorePropertiesWithAttribute = null, bool? initPublicOnly = null, IDictionary alreadyCloned = null) 257 | { 258 | DataBind(desireObjectToBeCloned, fielType, ignorePropertiesWithAttribute, initPublicOnly, alreadyCloned); 259 | } 260 | 261 | /// 262 | /// Determines if the specified type is an internal type. 263 | /// 264 | /// 265 | /// true if type is internal, else false. 266 | private static bool IsInternalType(Type underlyingSystemType) 267 | { 268 | return underlyingSystemType == typeof(string) || 269 | underlyingSystemType == typeof(decimal) || 270 | underlyingSystemType == typeof(int) || 271 | underlyingSystemType == typeof(double) || 272 | underlyingSystemType == typeof(float) || 273 | underlyingSystemType == typeof(bool) || 274 | underlyingSystemType == typeof(long) || 275 | underlyingSystemType == typeof(DateTime) || 276 | underlyingSystemType == typeof(ushort) || 277 | underlyingSystemType == typeof(short) || 278 | underlyingSystemType == typeof(sbyte) || 279 | underlyingSystemType == typeof(byte) || 280 | underlyingSystemType == typeof(ulong) || 281 | underlyingSystemType == typeof(uint) || 282 | underlyingSystemType == typeof(char) || 283 | underlyingSystemType == typeof(TimeSpan); 284 | } 285 | } 286 | 287 | public enum FieldType_Copy 288 | { 289 | FieldInfo, 290 | PropertyInfo 291 | } 292 | } 293 | #endif -------------------------------------------------------------------------------- /DeepCloner.Tests/InheritanceSpec.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | 4 | using NUnit.Framework; 5 | 6 | namespace Force.DeepCloner.Tests 7 | { 8 | #if !NETCORE 9 | [TestFixture(false)] 10 | #endif 11 | [TestFixture(true)] 12 | public class InheritanceSpec : BaseTest 13 | { 14 | public InheritanceSpec(bool isSafeInit) 15 | : base(isSafeInit) 16 | { 17 | } 18 | 19 | public class C1 : IDisposable 20 | { 21 | [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed. Suppression is OK here.")] 22 | public int X; 23 | 24 | [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed. Suppression is OK here.")] 25 | public int Y; 26 | 27 | [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed. Suppression is OK here.")] 28 | public object O; // make it not safe 29 | 30 | public void Dispose() 31 | { 32 | } 33 | } 34 | 35 | public class C2 : C1 36 | { 37 | [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed. Suppression is OK here.")] 38 | public new int X; 39 | 40 | [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed. Suppression is OK here.")] 41 | public int Z; 42 | } 43 | 44 | public class C1P : IDisposable 45 | { 46 | public int X { get; set; } 47 | 48 | [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed. Suppression is OK here.")] 49 | public int Y { get; set; } 50 | 51 | [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed. Suppression is OK here.")] 52 | public object O; // make it not safe 53 | 54 | public void Dispose() 55 | { 56 | } 57 | } 58 | 59 | public class C2P : C1P 60 | { 61 | [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed. Suppression is OK here.")] 62 | public new int X { get; set; } 63 | 64 | [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed. Suppression is OK here.")] 65 | public int Z { get; set; } 66 | } 67 | 68 | public struct S1 : IDisposable 69 | { 70 | public C1 X { get; set; } 71 | 72 | public int F; 73 | 74 | public void Dispose() 75 | { 76 | } 77 | } 78 | 79 | public struct S2 : IDisposable 80 | { 81 | public IDisposable X { get; set; } 82 | 83 | public void Dispose() 84 | { 85 | } 86 | } 87 | 88 | public class C3 89 | { 90 | public C1 X { get; set; } 91 | } 92 | 93 | [Test] 94 | public void Descendant_Should_Be_Cloned() 95 | { 96 | var c2 = new C2(); 97 | c2.X = 1; 98 | c2.Y = 2; 99 | c2.Z = 3; 100 | var c1 = c2 as C1; 101 | c1.X = 4; 102 | var cloned = c1.DeepClone(); 103 | Assert.That(cloned, Is.TypeOf()); 104 | Assert.That(cloned.X, Is.EqualTo(4)); 105 | Assert.That(cloned.Y, Is.EqualTo(2)); 106 | Assert.That(((C2)cloned).Z, Is.EqualTo(3)); 107 | Assert.That(((C2)cloned).X, Is.EqualTo(1)); 108 | } 109 | 110 | [Test] 111 | public void Class_Should_Be_Cloned_With_Parents() 112 | { 113 | var c2 = new C2P(); 114 | c2.X = 1; 115 | c2.Y = 2; 116 | c2.Z = 3; 117 | var c1 = c2 as C1P; 118 | c1.X = 4; 119 | var cloned = c2.DeepClone(); 120 | c2.X = 100; 121 | c2.Y = 100; 122 | c2.Z = 100; 123 | c1.X = 100; 124 | Assert.That(cloned, Is.TypeOf()); 125 | Assert.That(((C1P)cloned).X, Is.EqualTo(4)); 126 | Assert.That(cloned.Y, Is.EqualTo(2)); 127 | Assert.That(cloned.Z, Is.EqualTo(3)); 128 | Assert.That(cloned.X, Is.EqualTo(1)); 129 | } 130 | 131 | public struct S3 132 | { 133 | public C1P X { get; set; } 134 | 135 | public C1P Y { get; set; } 136 | } 137 | 138 | [Test] 139 | public void Struct_Should_Be_Cloned_With_Class_With_Parents() 140 | { 141 | var c2 = new S3(); 142 | c2.X = new C1P(); 143 | c2.Y = new C2P(); 144 | 145 | c2.X.X = 1; 146 | c2.X.Y = 2; 147 | c2.Y.X = 3; 148 | c2.Y.Y = 4; 149 | ((C2P)c2.Y).X = 5; 150 | ((C2P)c2.Y).Z = 6; 151 | var cloned = c2.DeepClone(); 152 | c2.X.X = 100; 153 | c2.X.Y = 200; 154 | c2.Y.X = 300; 155 | c2.Y.Y = 400; 156 | ((C2P)c2.Y).X = 500; 157 | ((C2P)c2.Y).Z = 600; 158 | Assert.That(cloned, Is.TypeOf()); 159 | Assert.That(cloned.X.X, Is.EqualTo(1)); 160 | Assert.That(cloned.X.Y, Is.EqualTo(2)); 161 | Assert.That(cloned.Y.X, Is.EqualTo(3)); 162 | Assert.That(cloned.Y.Y, Is.EqualTo(4)); 163 | Assert.That(((C2P)cloned.Y).X, Is.EqualTo(5)); 164 | Assert.That(((C2P)cloned.Y).Z, Is.EqualTo(6)); 165 | } 166 | 167 | [Test] 168 | public void Descendant_In_Array_Should_Be_Cloned() 169 | { 170 | var c1 = new C1(); 171 | var c2 = new C2(); 172 | var arr = new[] { c1, c2 }; 173 | 174 | var cloned = arr.DeepClone(); 175 | Assert.That(cloned[0], Is.TypeOf()); 176 | Assert.That(cloned[1], Is.TypeOf()); 177 | } 178 | 179 | [Test] 180 | public void Struct_Casted_To_Interface_Should_Be_Cloned() 181 | { 182 | var s1 = new S1(); 183 | s1.F = 1; 184 | var disp = s1 as IDisposable; 185 | var cloned = disp.DeepClone(); 186 | s1.F = 2; 187 | Assert.That(cloned, Is.TypeOf()); 188 | Assert.That(((S1)cloned).F, Is.EqualTo(1)); 189 | } 190 | 191 | public IDisposable CCC(IDisposable xx) 192 | { 193 | var x = (S1)xx; 194 | return x; 195 | } 196 | 197 | [Test] 198 | public void Class_Casted_To_Object_Should_Be_Cloned() 199 | { 200 | var c3 = new C3(); 201 | c3.X = new C1(); 202 | var obj = c3 as object; 203 | var cloned = obj.DeepClone(); 204 | Assert.That(cloned, Is.TypeOf()); 205 | Assert.That(c3, Is.Not.EqualTo(cloned)); 206 | Assert.That(((C3)cloned).X, Is.Not.Null); 207 | Assert.That(((C3)cloned).X, Is.Not.EqualTo(c3.X)); 208 | } 209 | 210 | [Test] 211 | public void Class_Casted_To_Interface_Should_Be_Cloned() 212 | { 213 | var c1 = new C1(); 214 | var disp = c1 as IDisposable; 215 | var cloned = disp.DeepClone(); 216 | Assert.That(c1, Is.Not.EqualTo(cloned)); 217 | Assert.That(cloned, Is.TypeOf()); 218 | } 219 | 220 | [Test] 221 | public void Struct_Casted_To_Interface_With_Class_As_Interface_Should_Be_Cloned() 222 | { 223 | var s2 = new S2(); 224 | s2.X = new C1(); 225 | var disp = s2 as IDisposable; 226 | var cloned = disp.DeepClone(); 227 | Assert.That(cloned, Is.TypeOf()); 228 | Assert.That(((S2)cloned).X, Is.TypeOf()); 229 | Assert.That(((S2)cloned).X, Is.Not.EqualTo(s2.X)); 230 | } 231 | 232 | [Test] 233 | public void Array_Of_Struct_Casted_To_Interface_Should_Be_Cloned() 234 | { 235 | var s1 = new S1(); 236 | var arr = new IDisposable[] { s1, s1 }; 237 | var clonedArr = arr.DeepClone(); 238 | Assert.That(clonedArr[0], Is.EqualTo(clonedArr[1])); 239 | } 240 | 241 | public class Safe1 242 | { 243 | } 244 | 245 | public class Safe2 246 | { 247 | } 248 | 249 | public class Unsafe1 : Safe1 250 | { 251 | [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed. Suppression is OK here.")] 252 | public object X; 253 | } 254 | 255 | public class V1 256 | { 257 | [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed. Suppression is OK here.")] 258 | public Safe1 Safe; 259 | } 260 | 261 | public class V2 262 | { 263 | [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed. Suppression is OK here.")] 264 | public Safe1 Safe; 265 | 266 | public V2(string x) 267 | { 268 | } 269 | } 270 | 271 | // these tests are overlapped by others, but for future can be helpful 272 | [Test] 273 | public void Class_With_Safe_Class_Should_Be_Cloned() 274 | { 275 | var v = new V1(); 276 | v.Safe = new Safe1(); 277 | var v2 = v.DeepClone(); 278 | Assert.That(v.Safe == v2.Safe, Is.False); 279 | } 280 | 281 | [Test] 282 | public void Class_With_Safe_Class_Should_Be_Cloned_No_Default_Constructor() 283 | { 284 | var v = new V2("X"); 285 | v.Safe = new Safe1(); 286 | var v2 = v.DeepClone(); 287 | Assert.That(v.Safe == v2.Safe, Is.False); 288 | } 289 | 290 | [Test] 291 | public void Class_With_UnSafe_Class_Should_Be_Cloned() 292 | { 293 | var v = new V1(); 294 | v.Safe = new Unsafe1(); 295 | var v2 = v.DeepClone(); 296 | Assert.That(v.Safe == v2.Safe, Is.False); 297 | Assert.That(v2.Safe.GetType(), Is.EqualTo(typeof(Unsafe1))); 298 | } 299 | 300 | [Test] 301 | public void Class_With_UnSafe_Class_Should_Be_Cloned_No_Default_Constructor() 302 | { 303 | var v = new V2("X"); 304 | v.Safe = new Unsafe1(); 305 | var v2 = v.DeepClone(); 306 | Assert.That(v.Safe == v2.Safe, Is.False); 307 | Assert.That(v2.Safe.GetType(), Is.EqualTo(typeof(Unsafe1))); 308 | } 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /DeepCloner.Tests/LoopCheckSpec.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace Force.DeepCloner.Tests 4 | { 5 | [TestFixture(false)] 6 | [TestFixture(true)] 7 | public class LoopCheckSpec : BaseTest 8 | { 9 | public LoopCheckSpec(bool isSafeInit) 10 | : base(isSafeInit) 11 | { 12 | } 13 | 14 | public class C1 15 | { 16 | public int F { get; set; } 17 | 18 | public C1 A { get; set; } 19 | } 20 | 21 | [Test] 22 | public void SimpleLoop_Should_Be_Handled() 23 | { 24 | var c1 = new C1(); 25 | var c2 = new C1(); 26 | c1.F = 1; 27 | c2.F = 2; 28 | c1.A = c2; 29 | c1.A.A = c1; 30 | var cloned = c1.DeepClone(); 31 | 32 | Assert.That(cloned.A, Is.Not.Null); 33 | Assert.That(cloned.A.A.F, Is.EqualTo(cloned.F)); 34 | Assert.That(cloned.A.A, Is.EqualTo(cloned)); 35 | } 36 | 37 | [Test] 38 | public void Object_Own_Loop_Should_Be_Handled() 39 | { 40 | var c1 = new C1(); 41 | c1.F = 1; 42 | c1.A = c1; 43 | var cloned = c1.DeepClone(); 44 | 45 | Assert.That(cloned.A, Is.Not.Null); 46 | Assert.That(cloned.A.F, Is.EqualTo(cloned.F)); 47 | Assert.That(cloned.A, Is.EqualTo(cloned)); 48 | } 49 | 50 | [Test] 51 | public void Array_Of_Same_Objects_Should_Be_Cloned() 52 | { 53 | var c1 = new C1(); 54 | var arr = new[] { c1, c1, c1 }; 55 | c1.F = 1; 56 | var cloned = arr.DeepClone(); 57 | 58 | Assert.That(cloned.Length, Is.EqualTo(3)); 59 | Assert.That(cloned[0], Is.EqualTo(cloned[1])); 60 | Assert.That(cloned[1], Is.EqualTo(cloned[2])); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /DeepCloner.Tests/Objects/DoableStruct1.cs: -------------------------------------------------------------------------------- 1 | namespace Force.DeepCloner.Tests.Objects 2 | { 3 | public struct DoableStruct1 : IDoable 4 | { 5 | public int X; 6 | 7 | public int Do() 8 | { 9 | return ++X; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /DeepCloner.Tests/Objects/IDoable.cs: -------------------------------------------------------------------------------- 1 | namespace Force.DeepCloner.Tests.Objects 2 | { 3 | public interface IDoable 4 | { 5 | int Do(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /DeepCloner.Tests/Objects/TestObject1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Force.DeepCloner.Tests.Objects 4 | { 5 | public class TestObject1 6 | { 7 | public byte Byte { get; set; } 8 | 9 | public short Short { get; set; } 10 | 11 | public ushort UShort { get; set; } 12 | 13 | public int Int { get; set; } 14 | 15 | public uint UInt { get; set; } 16 | 17 | public long Long { get; set; } 18 | 19 | public ulong ULong { get; set; } 20 | 21 | public float Float { get; set; } 22 | 23 | public double Double { get; set; } 24 | 25 | public decimal Decimal { get; set; } 26 | 27 | public char Char { get; set; } 28 | 29 | public string String { get; set; } 30 | 31 | public DateTime DateTime { get; set; } 32 | 33 | public bool Bool { get; set; } 34 | 35 | public IntPtr IntPtr { get; set; } 36 | 37 | public UIntPtr UIntPtr { get; set; } 38 | 39 | public AttributeTargets Enum { get; set; } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /DeepCloner.Tests/PerformanceSpec.cs: -------------------------------------------------------------------------------- 1 | #if !NETCORE 2 | using System; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Runtime.InteropServices; 6 | using System.Runtime.Serialization.Formatters.Binary; 7 | 8 | using CloneBehave; 9 | 10 | using CloneExtensions; 11 | 12 | using FastDeepCloner; 13 | 14 | using Force.DeepCloner.Tests.Imported; 15 | 16 | using NClone; 17 | 18 | using Nuclex.Cloning; 19 | 20 | using NUnit.Framework; 21 | 22 | namespace Force.DeepCloner.Tests 23 | { 24 | [TestFixture] 25 | public class PerformanceSpec 26 | { 27 | [Serializable] 28 | public class C1 29 | { 30 | public int V1 { get; set; } 31 | 32 | public string V2 { get; set; } 33 | 34 | public object O { get; set; } 35 | 36 | public C1 Clone() 37 | { 38 | return (C1)MemberwiseClone(); 39 | } 40 | } 41 | 42 | [Serializable] 43 | public class C1Complex 44 | { 45 | public int V1 { get; set; } 46 | 47 | public string V2 { get; set; } 48 | 49 | public object O { get; set; } 50 | 51 | public Guid? Guid { get; set; } 52 | 53 | public C1 C1 { get; set; } 54 | 55 | public int[] Array { get; set; } 56 | 57 | public C1Complex Clone() 58 | { 59 | return (C1Complex)MemberwiseClone(); 60 | } 61 | } 62 | 63 | private class C2 : C1 64 | { 65 | } 66 | 67 | // safe class 68 | public class C3 69 | { 70 | public int V1 { get; set; } 71 | 72 | public string V2 { get; set; } 73 | } 74 | 75 | private C1 ManualDeepClone(C1 x) 76 | { 77 | var y = new C1(); 78 | y.V1 = x.V1; 79 | y.V2 = x.V2; 80 | y.O = new object(); 81 | return y; 82 | } 83 | 84 | private C1Complex ManualDeepClone(C1Complex x) 85 | { 86 | var y = new C1Complex(); 87 | y.V1 = x.V1; 88 | y.V2 = x.V2; 89 | y.C1 = ManualDeepClone(x.C1); 90 | y.O = new object(); 91 | y.Array = new int[x.Array.Length]; 92 | y.Guid = x.Guid; 93 | Array.Copy(x.Array, 0, y.Array, 0, x.Array.Length); 94 | return y; 95 | } 96 | 97 | private C1 ManualShallowClone(C1 x) 98 | { 99 | var y = new C1(); 100 | y.V1 = x.V1; 101 | y.V2 = x.V2; 102 | y.O = x.O; 103 | return y; 104 | } 105 | 106 | private C1Complex ManualShallowClone(C1Complex x) 107 | { 108 | var y = new C1Complex(); 109 | y.V1 = x.V1; 110 | y.V2 = x.V2; 111 | y.O = x.O; 112 | y.Guid = x.Guid; 113 | y.Array = x.Array; 114 | y.C1 = x.C1; 115 | return y; 116 | } 117 | 118 | private T CloneViaFormatter(T obj) 119 | { 120 | var bf = new BinaryFormatter(); 121 | var ms = new MemoryStream(); 122 | bf.Serialize(ms, obj); 123 | ms.Seek(0, SeekOrigin.Begin); 124 | return (T)bf.Deserialize(ms); 125 | } 126 | 127 | private void DoTest(int count, string name, Action action) 128 | { 129 | double minCount = double.MaxValue; 130 | int iterCount = 5; 131 | while (iterCount-- > 0) 132 | { 133 | var sw = new Stopwatch(); 134 | sw.Start(); 135 | 136 | for (var i = 0; i < count; i++) action(); 137 | var elapsed = sw.ElapsedMilliseconds; 138 | minCount = Math.Min(minCount, elapsed); 139 | } 140 | 141 | Console.WriteLine(name + ": " + (1000 * 1000 * minCount / count) + " ns"); 142 | GC.Collect(2, GCCollectionMode.Forced, true, true); 143 | } 144 | 145 | [Test, Ignore("Manual")] 146 | public void Test_Construct_Variants() 147 | { 148 | var c = new C1 { V1 = 1, O = new object(), V2 = "xxx" }; 149 | var c1 = new C1Complex { C1 = c, Guid = Guid.NewGuid(), O = new object(), V1 = 42, V2 = "some test string", Array = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } }; 150 | const int CountWarm = 1000; 151 | const int CountReal = 100000; 152 | // warm up 153 | DoTest(CountWarm, "Manual", () => ManualDeepClone(c1)); 154 | BaseTest.SwitchTo(false); 155 | DoTest(CountWarm, "Deep Unsafe", () => c1.DeepClone()); 156 | BaseTest.SwitchTo(true); 157 | DoTest(CountWarm, "Deep Safe", () => c1.DeepClone()); 158 | DoTest(CountWarm, "CloneExtensions", () => c1.GetClone()); 159 | DoTest(CountWarm, "NClone", () => Clone.ObjectGraph(c1)); 160 | DoTest(CountWarm, "Clone.Behave", () => new CloneEngine().Clone(c1)); 161 | DoTest(CountWarm, "GeorgeCloney", () => GeorgeCloney.CloneExtension.DeepCloneWithoutSerialization(c1)); 162 | DoTest(CountWarm, "FastDeepCloner", () => FastDeepCloner.DeepCloner.Clone(c1, FieldType.FieldInfo)); 163 | DoTest(CountWarm, "DesertOctopus", () => DesertOctopus.ObjectCloner.Clone(c1)); 164 | DoTest(CountWarm, "Binary Formatter", () => CloneViaFormatter(c1)); 165 | 166 | Console.WriteLine("----------------"); 167 | DoTest(CountReal, "Manual", () => ManualDeepClone(c1)); 168 | BaseTest.SwitchTo(false); 169 | DoTest(CountReal, "Deep Unsafe", () => c1.DeepClone()); 170 | BaseTest.SwitchTo(true); 171 | DoTest(CountReal, "Deep Safe", () => c1.DeepClone()); 172 | DoTest(CountReal, "CloneExtensions", () => c1.GetClone()); 173 | DoTest(CountReal, "NClone", () => Clone.ObjectGraph(c1)); 174 | DoTest(CountReal, "Clone.Behave", () => new CloneEngine().Clone(c1)); 175 | DoTest(CountReal, "GeorgeCloney", () => GeorgeCloney.CloneExtension.DeepCloneWithoutSerialization(c1)); 176 | DoTest(CountReal, "FastDeepCloner", () => FastDeepCloner.DeepCloner.Clone(c1, FieldType.FieldInfo)); 177 | DoTest(CountReal, "DesertOctopus", () => DesertOctopus.ObjectCloner.Clone(c1)); 178 | // binary is slow, reduce 179 | DoTest(CountReal / 10, "Binary Formatter", () => CloneViaFormatter(c1)); 180 | } 181 | 182 | [Test, Ignore("Manual")] 183 | public void Test_Construct_Variants1() 184 | { 185 | // we cache cloners for type, so, this only variant with separate run 186 | BaseTest.SwitchTo(false); 187 | var c1 = new C1 { V1 = 1, O = new object(), V2 = "xxx" }; 188 | // warm up 189 | for (var i = 0; i < 1000; i++) c1.DeepClone(); 190 | // test 191 | var sw = new Stopwatch(); 192 | sw.Start(); 193 | 194 | for (var i = 0; i < 10000000; i++) c1.DeepClone(); 195 | Console.WriteLine("Deep: " + sw.ElapsedMilliseconds); 196 | sw.Restart(); 197 | } 198 | 199 | [Test, Ignore("Manual")] 200 | [TestCase(false)] 201 | [TestCase(true)] 202 | public void Test_Construct_Safe_Class_Variants(bool isSafe) 203 | { 204 | // we cache cloners for type, so, this only variant with separate run 205 | BaseTest.SwitchTo(isSafe); 206 | var c1 = new C3 { V1 = 1, V2 = "xxx" }; 207 | for (var i = 0; i < 1000; i++) c1.GetClone(); 208 | for (var i = 0; i < 1000; i++) c1.DeepClone(); 209 | 210 | // test 211 | var sw = new Stopwatch(); 212 | sw.Start(); 213 | 214 | for (var i = 0; i < 10000000; i++) c1.DeepClone(); 215 | Console.WriteLine("Deep: " + sw.ElapsedMilliseconds); 216 | sw.Restart(); 217 | 218 | for (var i = 0; i < 10000000; i++) c1.GetClone(); 219 | Console.WriteLine("Clone Extensions: " + sw.ElapsedMilliseconds); 220 | sw.Restart(); 221 | } 222 | 223 | [Test, Ignore("Manual")] 224 | public void Test_Parent_Casting_Variants() 225 | { 226 | var c2 = new C2(); 227 | var c1 = c2 as C1; 228 | // warm up 229 | for (var i = 0; i < 1000; i++) c1.DeepClone(); 230 | for (var i = 0; i < 1000; i++) c2.DeepClone(); 231 | 232 | // test 233 | var sw = new Stopwatch(); 234 | sw.Start(); 235 | 236 | for (var i = 0; i < 100000; i++) c2.DeepClone(); 237 | Console.WriteLine("Direct: " + sw.ElapsedMilliseconds); 238 | sw.Restart(); 239 | 240 | for (var i = 0; i < 100000; i++) c1.DeepClone(); 241 | Console.WriteLine("Parent: " + sw.ElapsedMilliseconds); 242 | } 243 | 244 | public struct S1 245 | { 246 | public C1 C; 247 | } 248 | 249 | [Test, Ignore("Manual")] 250 | public void Test_Array_Of_Structs_With_Class() 251 | { 252 | var c1 = new S1[100000]; 253 | // warm up 254 | for (var i = 0; i < 2; i++) c1.DeepClone(); 255 | 256 | // test 257 | var sw = new Stopwatch(); 258 | sw.Start(); 259 | 260 | for (var i = 0; i < 100; i++) c1.DeepClone(); 261 | Console.WriteLine("Deep: " + sw.ElapsedMilliseconds); 262 | } 263 | 264 | [Test, Ignore("Manual")] 265 | public void Test_Shallow_Variants() 266 | { 267 | var c = new C1 { V1 = 1, O = new object(), V2 = "xxx" }; 268 | var c1 = new C1Complex { C1 = c, Guid = Guid.NewGuid(), O = new object(), V1 = 42, V2 = "some test string", Array = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } }; 269 | const int CountWarm = 1000; 270 | const int CountReal = 10000000; 271 | 272 | // warm up 273 | DoTest(CountWarm, "Manual External", () => ManualShallowClone(c1)); 274 | DoTest(CountWarm, "Auto Internal", () => c1.Clone()); 275 | BaseTest.SwitchTo(false); 276 | DoTest(CountWarm, "Shallow Unsafe", () => c1.ShallowClone()); 277 | BaseTest.SwitchTo(true); 278 | DoTest(CountWarm, "Shallow Safe", () => c1.ShallowClone()); 279 | DoTest(CountWarm, "Nuclex.Cloning", () => ReflectionCloner.ShallowFieldClone(c1)); 280 | DoTest(CountWarm, "Clone.Extensions", () => c1.GetClone(CloningFlags.Shallow)); 281 | 282 | Console.WriteLine("------------------"); 283 | DoTest(CountReal, "Manual External", () => ManualShallowClone(c1)); 284 | DoTest(CountReal, "Auto Internal", () => c1.Clone()); 285 | BaseTest.SwitchTo(false); 286 | DoTest(CountReal, "Shallow Unsafe", () => c1.ShallowClone()); 287 | BaseTest.SwitchTo(true); 288 | DoTest(CountReal, "Shallow Safe", () => c1.ShallowClone()); 289 | DoTest(CountReal / 10, "Nuclex.Cloning", () => ReflectionCloner.ShallowFieldClone(c1)); 290 | DoTest(CountReal, "Clone.Extensions", () => c1.GetClone(CloningFlags.Shallow)); 291 | } 292 | 293 | public class TreeNode 294 | { 295 | public TreeNode Item1 { get; set; } 296 | 297 | public TreeNode Item2 { get; set; } 298 | } 299 | 300 | [Test, Ignore("Manual")] 301 | public void Test_Deep_Tree() 302 | { 303 | // we cache cloners for type, so, this only variant with separate run 304 | BaseTest.SwitchTo(false); 305 | 306 | var c1 = new TreeNode(); 307 | 308 | c1.Item1 = new TreeNode(); 309 | c1.Item2 = new TreeNode(); 310 | c1.Item1.Item1 = c1; 311 | c1.Item1.Item2 = c1.Item2; 312 | c1.Item2 = new TreeNode(); 313 | c1.Item2.Item1 = new TreeNode(); 314 | c1.Item2.Item2 = new TreeNode(); 315 | c1.Item2.Item1.Item1 = new TreeNode(); 316 | c1.Item2.Item1.Item2 = new TreeNode(); 317 | c1.Item2.Item1.Item1.Item1 = new TreeNode(); 318 | c1.Item2.Item1.Item1.Item2 = c1; 319 | 320 | var elem = c1.Item2.Item1.Item1.Item1; 321 | for (var i = 0; i < 100; i++) 322 | { 323 | elem.Item1 = new TreeNode(); 324 | elem.Item2 = new TreeNode(); 325 | elem.Item1.Item1 = elem.Item2; 326 | elem.Item1.Item2 = new TreeNode(); 327 | elem.Item2.Item2 = new TreeNode(); 328 | elem = elem.Item2.Item2; 329 | } 330 | 331 | 332 | var clone = c1.DeepClone(); 333 | Assert.That(ReferenceEquals(clone, clone.Item2.Item1.Item1.Item2), Is.True); 334 | //return; 335 | 336 | // warm up 337 | for (var i = 0; i < 1000; i++) c1.DeepClone(); 338 | // test 339 | var sw = new Stopwatch(); 340 | sw.Start(); 341 | 342 | for (var i = 0; i < 100000; i++) c1.DeepClone(); 343 | Console.WriteLine("Deep: " + sw.ElapsedMilliseconds); 344 | sw.Restart(); 345 | } 346 | 347 | [Test] 348 | public void Test_Tuples() 349 | { 350 | var c = new Tuple(42, "test", false); 351 | const int CountWarm = 1000; 352 | const int CountReal = 500000; 353 | // warm up 354 | DoTest(CountWarm, "Manual", () => new Tuple(c.Item1, c.Item2, c.Item3)); 355 | BaseTest.SwitchTo(false); 356 | DoTest(CountWarm, "Deep Unsafe", () => c.DeepClone()); 357 | BaseTest.SwitchTo(true); 358 | DoTest(CountWarm, "Deep Safe", () => c.DeepClone()); 359 | 360 | Console.WriteLine("----------------"); 361 | DoTest(CountReal, "Manual", () => new Tuple(c.Item1, c.Item2, c.Item3)); 362 | BaseTest.SwitchTo(false); 363 | DoTest(CountReal, "Deep Unsafe", () => c.DeepClone()); 364 | BaseTest.SwitchTo(true); 365 | DoTest(CountReal, "Deep Safe", () => c.DeepClone()); 366 | } 367 | } 368 | } 369 | #endif -------------------------------------------------------------------------------- /DeepCloner.Tests/PermissionSpec.cs: -------------------------------------------------------------------------------- 1 | #if !NETCORE 2 | using System; 3 | using System.Security; 4 | using System.Security.Permissions; 5 | 6 | using CloneExtensions; 7 | 8 | using NUnit.Framework; 9 | 10 | namespace Force.DeepCloner.Tests 11 | { 12 | [TestFixture] 13 | public class PermissionSpec 14 | { 15 | [Test/*, Ignore("Just manual check")*/] 16 | public void EnsurePermission() 17 | { 18 | var setup = new AppDomainSetup 19 | { 20 | ApplicationBase = AppDomain.CurrentDomain.BaseDirectory, 21 | ApplicationName = "sandbox", 22 | }; 23 | 24 | var permissions = new PermissionSet(PermissionState.None); 25 | // assembly load 26 | permissions.AddPermission(new FileIOPermission(PermissionState.Unrestricted)); 27 | // assembly execute 28 | permissions.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution)); 29 | 30 | // permissions.AddPermission(new ReflectionPermission(ReflectionPermissionFlag.RestrictedMemberAccess | ReflectionPermissionFlag.MemberAccess)); 31 | permissions.AddPermission(new ReflectionPermission(ReflectionPermissionFlag.MemberAccess)); 32 | 33 | var test = AppDomain.CreateDomain("sandbox", null, setup, permissions); 34 | 35 | var instance = (Executor)test.CreateInstanceFromAndUnwrap(this.GetType().Assembly.Location, typeof(Executor).FullName); 36 | instance.CloneExtensionsClone(); 37 | instance.DoShallowClone(); 38 | instance.DoDeepClone(); 39 | } 40 | 41 | public class Test 42 | { 43 | public int X { get; set; } 44 | 45 | private readonly object y = new object(); 46 | 47 | // this field to make class unsafe and readonly 48 | #pragma warning disable 169 49 | private readonly UnsafeStructTest z; 50 | #pragma warning restore 169 51 | 52 | public object GetY() 53 | { 54 | return y; 55 | } 56 | } 57 | 58 | public struct UnsafeStructTest 59 | { 60 | public object Y { get; set; } 61 | } 62 | 63 | public class Executor : MarshalByRefObject 64 | { 65 | public void DoDeepClone() 66 | { 67 | var test = new Test(); 68 | var clone = test.DeepClone(); 69 | if (clone.GetY() == test.GetY()) 70 | throw new Exception("Deep Clone fail"); 71 | 72 | var clone2 = new Tuple(12).DeepClone(); 73 | if (clone2.Item1 != 12) 74 | throw new Exception("Deep Clone fail"); 75 | } 76 | 77 | public void DoShallowClone() 78 | { 79 | new Test().ShallowClone(); 80 | } 81 | 82 | public void CloneExtensionsClone() 83 | { 84 | new Test().GetClone(); 85 | } 86 | } 87 | } 88 | } 89 | #endif -------------------------------------------------------------------------------- /DeepCloner.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("DeepCloner.Tests")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("DeepCloner.Tests")] 12 | [assembly: AssemblyCopyright("Copyright © 2016")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("68714d9b-2471-49e2-b720-3c9b47e14a16")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /DeepCloner.Tests/ShallowClonerSpec.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using Force.DeepCloner.Tests.Objects; 4 | 5 | using NUnit.Framework; 6 | 7 | namespace Force.DeepCloner.Tests 8 | { 9 | #if !NETCORE 10 | [TestFixture(false)] 11 | #endif 12 | [TestFixture(true)] 13 | public class ShallowClonerSpec : BaseTest 14 | { 15 | public ShallowClonerSpec(bool isSafeInit) 16 | : base(isSafeInit) 17 | { 18 | } 19 | 20 | [Test] 21 | public void SimpleObject_Should_Be_Cloned() 22 | { 23 | var obj = new TestObject1 { Int = 42, Byte = 42, Short = 42, Long = 42, DateTime = new DateTime(2001, 01, 01), Char = 'X', Decimal = 1.2m, Double = 1.3, Float = 1.4f, String = "test1", UInt = 42, ULong = 42, UShort = 42, Bool = true, IntPtr = new IntPtr(42), UIntPtr = new UIntPtr(42), Enum = AttributeTargets.Delegate }; 24 | 25 | var cloned = obj.ShallowClone(); 26 | Assert.That(cloned.Byte, Is.EqualTo(42)); 27 | Assert.That(cloned.Short, Is.EqualTo(42)); 28 | Assert.That(cloned.UShort, Is.EqualTo(42)); 29 | Assert.That(cloned.Int, Is.EqualTo(42)); 30 | Assert.That(cloned.UInt, Is.EqualTo(42)); 31 | Assert.That(cloned.Long, Is.EqualTo(42)); 32 | Assert.That(cloned.ULong, Is.EqualTo(42)); 33 | Assert.That(cloned.Decimal, Is.EqualTo(1.2)); 34 | Assert.That(cloned.Double, Is.EqualTo(1.3)); 35 | Assert.That(cloned.Float, Is.EqualTo(1.4f)); 36 | Assert.That(cloned.Char, Is.EqualTo('X')); 37 | Assert.That(cloned.String, Is.EqualTo("test1")); 38 | Assert.That(cloned.DateTime, Is.EqualTo(new DateTime(2001, 1, 1))); 39 | Assert.That(cloned.Bool, Is.EqualTo(true)); 40 | Assert.That(cloned.IntPtr, Is.EqualTo(new IntPtr(42))); 41 | Assert.That(cloned.UIntPtr, Is.EqualTo(new UIntPtr(42))); 42 | Assert.That(cloned.Enum, Is.EqualTo(AttributeTargets.Delegate)); 43 | } 44 | 45 | private class C1 46 | { 47 | public object X { get; set; } 48 | } 49 | 50 | [Test] 51 | public void Reference_Should_Not_Be_Copied() 52 | { 53 | var c1 = new C1(); 54 | c1.X = new object(); 55 | var clone = c1.ShallowClone(); 56 | Assert.That(clone.X, Is.EqualTo(c1.X)); 57 | } 58 | 59 | private struct S1 : IDisposable 60 | { 61 | public int X; 62 | 63 | public void Dispose() 64 | { 65 | } 66 | } 67 | 68 | [Test] 69 | public void Struct_Should_Be_Cloned() 70 | { 71 | var c1 = new S1(); 72 | c1.X = 1; 73 | var clone = c1.ShallowClone(); 74 | c1.X = 2; 75 | Assert.That(clone.X, Is.EqualTo(1)); 76 | } 77 | 78 | [Test] 79 | public void Struct_As_Object_Should_Be_Cloned() 80 | { 81 | var c1 = new S1(); 82 | c1.X = 1; 83 | var clone = (S1)((IDisposable)c1).ShallowClone(); 84 | c1.X = 2; 85 | Assert.That(clone.X, Is.EqualTo(1)); 86 | } 87 | 88 | [Test] 89 | public void Struct_As_Interface_Should_Be_Cloned() 90 | { 91 | var c1 = new DoableStruct1() as IDoable; 92 | Assert.That(c1.Do(), Is.EqualTo(1)); 93 | Assert.That(c1.Do(), Is.EqualTo(2)); 94 | var clone = c1.ShallowClone(); 95 | Assert.That(c1.Do(), Is.EqualTo(3)); 96 | Assert.That(clone.Do(), Is.EqualTo(3)); 97 | } 98 | 99 | [Test] 100 | public void Struct_As_Interface_Should_Be_Cloned_For_DeepClone_Too() 101 | { 102 | var c1 = new DoableStruct1() as IDoable; 103 | Assert.That(c1.Do(), Is.EqualTo(1)); 104 | Assert.That(c1.Do(), Is.EqualTo(2)); 105 | var clone = c1.DeepClone(); 106 | Assert.That(c1.Do(), Is.EqualTo(3)); 107 | Assert.That(clone.Do(), Is.EqualTo(3)); 108 | } 109 | 110 | [Test] 111 | public void Struct_As_Interface_Should_Be_Cloned_In_Object() 112 | { 113 | var c1 = new DoableStruct1() as IDoable; 114 | var t = new Tuple(c1); 115 | Assert.That(t.Item1.Do(), Is.EqualTo(1)); 116 | Assert.That(t.Item1.Do(), Is.EqualTo(2)); 117 | var clone = t.ShallowClone(); 118 | Assert.That(t.Item1.Do(), Is.EqualTo(3)); 119 | // shallow clone do not copy object 120 | Assert.That(clone.Item1.Do(), Is.EqualTo(4)); 121 | } 122 | 123 | [Test] 124 | public void Struct_As_Interface_Should_Be_Cloned_For_DeepClone_Too_In_Object() 125 | { 126 | var c1 = new DoableStruct1() as IDoable; 127 | var t = new Tuple(c1); 128 | Assert.That(t.Item1.Do(), Is.EqualTo(1)); 129 | Assert.That(t.Item1.Do(), Is.EqualTo(2)); 130 | var clone = t.DeepClone(); 131 | Assert.That(t.Item1.Do(), Is.EqualTo(3)); 132 | // deep clone copy object 133 | Assert.That(clone.Item1.Do(), Is.EqualTo(3)); 134 | } 135 | 136 | [Test] 137 | public void Primitive_Should_Be_Cloned() 138 | { 139 | Assert.That(((object)null).ShallowClone(), Is.Null); 140 | Assert.That(3.ShallowClone(), Is.EqualTo(3)); 141 | } 142 | 143 | [Test] 144 | public void Array_Should_Be_Cloned() 145 | { 146 | var a = new[] { 3, 4 }; 147 | var clone = a.ShallowClone(); 148 | Assert.That(clone.Length, Is.EqualTo(2)); 149 | Assert.That(clone[0], Is.EqualTo(3)); 150 | Assert.That(clone[1], Is.EqualTo(4)); 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /DeepCloner.Tests/SimpleObjectSpec.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Reflection; 4 | using System.Runtime.InteropServices; 5 | using Force.DeepCloner.Tests.Objects; 6 | 7 | using NUnit.Framework; 8 | 9 | namespace Force.DeepCloner.Tests 10 | { 11 | [TestFixture(true)] 12 | [TestFixture(false)] 13 | public class SimpleObjectSpec : BaseTest 14 | { 15 | public SimpleObjectSpec(bool isSafeInit) 16 | : base(isSafeInit) 17 | { 18 | } 19 | 20 | [Test] 21 | public void SimpleObject_Should_Be_Cloned() 22 | { 23 | var obj = new TestObject1 { Int = 42, Byte = 42, Short = 42, Long = 42, DateTime = new DateTime(2001, 01, 01), Char = 'X', Decimal = 1.2m, Double = 1.3, Float = 1.4f, String = "test1", UInt = 42, ULong = 42, UShort = 42, Bool = true, IntPtr = new IntPtr(42), UIntPtr = new UIntPtr(42), Enum = AttributeTargets.Delegate }; 24 | 25 | var cloned = obj.DeepClone(); 26 | Assert.That(cloned.Byte, Is.EqualTo(42)); 27 | Assert.That(cloned.Short, Is.EqualTo(42)); 28 | Assert.That(cloned.UShort, Is.EqualTo(42)); 29 | Assert.That(cloned.Int, Is.EqualTo(42)); 30 | Assert.That(cloned.UInt, Is.EqualTo(42)); 31 | Assert.That(cloned.Long, Is.EqualTo(42)); 32 | Assert.That(cloned.ULong, Is.EqualTo(42)); 33 | Assert.That(cloned.Decimal, Is.EqualTo(1.2)); 34 | Assert.That(cloned.Double, Is.EqualTo(1.3)); 35 | Assert.That(cloned.Float, Is.EqualTo(1.4f)); 36 | Assert.That(cloned.Char, Is.EqualTo('X')); 37 | Assert.That(cloned.String, Is.EqualTo("test1")); 38 | Assert.That(cloned.DateTime, Is.EqualTo(new DateTime(2001, 1, 1))); 39 | Assert.That(cloned.Bool, Is.EqualTo(true)); 40 | Assert.That(cloned.IntPtr, Is.EqualTo(new IntPtr(42))); 41 | Assert.That(cloned.UIntPtr, Is.EqualTo(new UIntPtr(42))); 42 | Assert.That(cloned.Enum, Is.EqualTo(AttributeTargets.Delegate)); 43 | } 44 | 45 | public struct S1 46 | { 47 | public int A; 48 | } 49 | 50 | public struct S2 51 | { 52 | public S3 S; 53 | } 54 | 55 | public struct S3 56 | { 57 | public bool B; 58 | } 59 | 60 | [Test(Description = "We have an special logic for simple structs, so, this test checks that this logic works correctly")] 61 | public void SimpleStruct_Should_Be_Cloned() 62 | { 63 | var s1 = new S1 { A = 1 }; 64 | var cloned = s1.DeepClone(); 65 | Assert.That(cloned.A, Is.EqualTo(1)); 66 | } 67 | 68 | [Test(Description = "We have an special logic for simple structs, so, this test checks that this logic works correctly")] 69 | public void Simple_Struct_With_Child_Should_Be_Cloned() 70 | { 71 | var s1 = new S2 { S = new S3 { B = true } }; 72 | var cloned = s1.DeepClone(); 73 | Assert.That(cloned.S.B, Is.EqualTo(true)); 74 | } 75 | 76 | public class ClassWithNullable 77 | { 78 | public int? A { get; set; } 79 | 80 | public long? B { get; set; } 81 | } 82 | 83 | [Test] 84 | public void Nullable_Shoild_Be_Cloned() 85 | { 86 | var c = new ClassWithNullable { B = 42 }; 87 | var cloned = c.DeepClone(); 88 | Assert.That(cloned.A, Is.Null); 89 | Assert.That(cloned.B, Is.EqualTo(42)); 90 | } 91 | 92 | public class C1 93 | { 94 | public C2 C { get; set; } 95 | } 96 | 97 | public class C2 98 | { 99 | } 100 | 101 | public class C3 102 | { 103 | public string X { get; set; } 104 | } 105 | 106 | [Test] 107 | public void Class_Should_Be_Cloned() 108 | { 109 | var c1 = new C1(); 110 | c1.C = new C2(); 111 | var cloned = c1.DeepClone(); 112 | Assert.That(cloned.C, Is.Not.Null); 113 | Assert.That(cloned.C, Is.Not.EqualTo(c1.C)); 114 | } 115 | 116 | public struct S4 117 | { 118 | public C2 C; 119 | 120 | public int F; 121 | } 122 | 123 | [Test] 124 | public void StructWithClass_Should_Be_Cloned() 125 | { 126 | var c1 = new S4(); 127 | c1.F = 1; 128 | c1.C = new C2(); 129 | var cloned = c1.DeepClone(); 130 | c1.F = 2; 131 | Assert.That(cloned.C, Is.Not.Null); 132 | Assert.That(cloned.F, Is.EqualTo(1)); 133 | } 134 | 135 | [Test] 136 | public void Privitive_Should_Be_Cloned() 137 | { 138 | Assert.That(3.DeepClone(), Is.EqualTo(3)); 139 | Assert.That('x'.DeepClone(), Is.EqualTo('x')); 140 | Assert.That("xxxxxxxxxx yyyyyyyyyyyyyy".DeepClone(), Is.EqualTo("xxxxxxxxxx yyyyyyyyyyyyyy")); 141 | Assert.That(string.Empty.DeepClone(), Is.EqualTo(string.Empty)); 142 | Assert.True(ReferenceEquals("y".DeepClone(), "y")); 143 | Assert.That(DateTime.MinValue.DeepClone(), Is.EqualTo(DateTime.MinValue)); 144 | Assert.That(AttributeTargets.Delegate.DeepClone(), Is.EqualTo(AttributeTargets.Delegate)); 145 | Assert.That(((object)null).DeepClone(), Is.Null); 146 | var obj = new object(); 147 | Assert.That(obj.DeepClone(), Is.Not.Null); 148 | Assert.That(true.DeepClone(), Is.True); 149 | Assert.That(((object)true).DeepClone(), Is.True); 150 | Assert.That(obj.DeepClone().GetType(), Is.EqualTo(typeof(object))); 151 | Assert.That(obj.DeepClone(), Is.Not.EqualTo(obj)); 152 | } 153 | 154 | [Test] 155 | public void Guid_Should_Be_Cloned() 156 | { 157 | var g = Guid.NewGuid(); 158 | Assert.That(g.DeepClone(), Is.EqualTo(g)); 159 | } 160 | 161 | private class UnsafeObject 162 | { 163 | [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed. Suppression is OK here.")] 164 | public unsafe void* Void; 165 | 166 | [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed. Suppression is OK here.")] 167 | public unsafe int* Int; 168 | } 169 | 170 | [Test] 171 | public void Unsafe_Should_Be_Cloned() 172 | { 173 | var u = new UnsafeObject(); 174 | var i = 1; 175 | var j = 2; 176 | unsafe 177 | { 178 | u.Int = &i; 179 | u.Void = &i; 180 | } 181 | 182 | var cloned = u.DeepClone(); 183 | unsafe 184 | { 185 | u.Int = &j; 186 | Assert.That(cloned.Int == &i, Is.True); 187 | Assert.That(cloned.Void == &i, Is.True); 188 | } 189 | } 190 | 191 | [Test] 192 | public void String_In_Class_Should_Not_Be_Cloned() 193 | { 194 | var c = new C3 { X = "aaa" }; 195 | var cloned = c.DeepClone(); 196 | Assert.That(cloned.X, Is.EqualTo(c.X)); 197 | Assert.True(ReferenceEquals(cloned.X, c.X)); 198 | } 199 | 200 | public sealed class C6 201 | { 202 | public readonly int X = 1; 203 | 204 | private readonly object y = new object(); 205 | 206 | // it is struct - and it can't be null, but it's readonly and should be copied 207 | // also it private to ensure it copied correctly 208 | #pragma warning disable 169 209 | private readonly StructWithObject z; 210 | #pragma warning restore 169 211 | 212 | public object GetY() 213 | { 214 | return y; 215 | } 216 | } 217 | 218 | public struct StructWithObject 219 | { 220 | public readonly object Z; 221 | } 222 | 223 | [Test] 224 | public void Object_With_Readonly_Fields_Should_Be_Cloned() 225 | { 226 | var c = new C6(); 227 | var clone = c.DeepClone(); 228 | Assert.That(clone, Is.Not.EqualTo(c)); 229 | Assert.That(clone.X, Is.EqualTo(1)); 230 | Assert.That(clone.GetY(), Is.Not.Null); 231 | Assert.That(clone.GetY(), Is.Not.EqualTo(c.GetY())); 232 | Assert.That(clone.GetY(), Is.Not.EqualTo(c.GetY())); 233 | } 234 | 235 | public class VirtualClass1 236 | { 237 | public virtual int A { get; set; } 238 | 239 | public virtual int B { get; set; } 240 | 241 | // not safe 242 | public object X { get; set; } 243 | } 244 | 245 | public class VirtualClass2 : VirtualClass1 246 | { 247 | public override int B { get; set; } 248 | } 249 | 250 | [Test(Description = "Nothings special, just for checking")] 251 | public void Class_With_Virtual_Methods_Should_Be_Cloned() 252 | { 253 | var v2 = new VirtualClass2(); 254 | v2.A = 1; 255 | v2.B = 2; 256 | var v1 = v2 as VirtualClass1; 257 | v1.A = 3; 258 | var clone = v1.DeepClone() as VirtualClass2; 259 | v2.B = 0; 260 | v2.A = 0; 261 | Assert.That(clone.B, Is.EqualTo(2)); 262 | Assert.That(clone.A, Is.EqualTo(3)); 263 | } 264 | 265 | [Test(Description = "DBNull is compared by value, so, we don't need to clone it")] 266 | public void DbNull_Should_Not_Be_Cloned() 267 | { 268 | var v = DBNull.Value; 269 | Assert.That(v == v.DeepClone(), Is.True); 270 | Assert.That(v == v.ShallowClone(), Is.True); 271 | } 272 | 273 | public class EmptyClass {} 274 | 275 | [Test(Description = "Empty class does not have any mutable properties, so, it safe to use same class in cloning"), 276 | Ignore("Think about logic, which is better to clone or not to clone, I do not know, but it changes current logic seriously")] 277 | // e.g. new object() frequently use for locks - if we leave same object - we'll receive same lock in different classes 278 | // todo: think about another reasons 279 | public void Empty_Should_Not_Be_Cloned() 280 | { 281 | var v = new EmptyClass(); 282 | Assert.That(ReferenceEquals(v, v.DeepClone()), Is.True); 283 | Assert.That(ReferenceEquals(v, v.ShallowClone()), Is.True); 284 | } 285 | 286 | [Test(Description = "Reflection classes should not be cloned")] 287 | public void MethodInfo_Should_Not_Be_Cloned() 288 | { 289 | #if NETCORE13 290 | var v = GetType().GetTypeInfo().GetMethod("MethodInfo_Should_Not_Be_Cloned"); 291 | #else 292 | var v = GetType().GetMethod("MethodInfo_Should_Not_Be_Cloned"); 293 | #endif 294 | Assert.That(ReferenceEquals(v, v.DeepClone()), Is.True); 295 | Assert.That(ReferenceEquals(v, v.ShallowClone()), Is.True); 296 | } 297 | 298 | public class Readonly1 299 | { 300 | public readonly object X; 301 | 302 | public object Z = new object(); 303 | 304 | public Readonly1(string x) 305 | { 306 | X = x; 307 | } 308 | } 309 | 310 | [Test] 311 | public void Readonly_Field_Should_Remain_ReadOnly() 312 | { 313 | var c = new Readonly1("Z").DeepClone(); 314 | Assert.That(c.X, Is.EqualTo("Z")); 315 | Assert.That(typeof(Readonly1).GetField("X").IsInitOnly, Is.True); 316 | } 317 | 318 | [Test] 319 | public void System_Type_Should_Not_Be_Cloned() 320 | { 321 | // it used for dictionaries as key. there are no sense to copy it 322 | var t = GetType(); // RuntimeType 323 | var clone = t.DeepClone(); 324 | Assert.That(ReferenceEquals(t, clone)); 325 | } 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /DeepCloner.Tests/SpecificScenariosTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.ComponentModel.DataAnnotations.Schema; 5 | #if !NETCORE 6 | using System.Data.Entity; 7 | #else 8 | using Microsoft.EntityFrameworkCore; 9 | #endif 10 | using System.Diagnostics; 11 | using System.Globalization; 12 | using System.Linq; 13 | using System.Runtime.InteropServices; 14 | 15 | using NUnit.Framework; 16 | 17 | namespace Force.DeepCloner.Tests 18 | { 19 | #if !NETCORE 20 | [TestFixture(false)] 21 | #endif 22 | [TestFixture(true)] 23 | public class SpecificScenariosTest : BaseTest 24 | { 25 | public SpecificScenariosTest(bool isSafeInit) 26 | : base(isSafeInit) 27 | { 28 | } 29 | 30 | [Test] 31 | public void Test_ExpressionTree_OrderBy1() 32 | { 33 | var q = Enumerable.Range(1, 5).Reverse().AsQueryable().OrderBy(x => x); 34 | var q2 = q.DeepClone(); 35 | Assert.That(q2.ToArray()[0], Is.EqualTo(1)); 36 | Assert.That(q.ToArray().Length, Is.EqualTo(5)); 37 | } 38 | 39 | [Test] 40 | public void Test_ExpressionTree_OrderBy2() 41 | { 42 | var l = new List { 2, 1, 3, 4, 5 }.Select(y => new Tuple(y, y.ToString(CultureInfo.InvariantCulture))); 43 | var q = l.AsQueryable().OrderBy(x => x.Item1); 44 | var q2 = q.DeepClone(); 45 | Assert.That(q2.ToArray()[0].Item1, Is.EqualTo(1)); 46 | Assert.That(q.ToArray().Length, Is.EqualTo(5)); 47 | } 48 | 49 | [Test(Description = "Tests works on local SQL Server with AdventureWorks database")] 50 | [Ignore("Test on MS Server")] 51 | public void Clone_EfQuery1() 52 | { 53 | var at = new AdventureContext(); 54 | // var at2 = at.DeepClone(); 55 | // Console.WriteLine(at.ChangeTracker); 56 | // Console.WriteLine(at.ChangeTracker); 57 | var q = at.Currencies.Where(x => x.CurrencyCode == "AUD"); 58 | var q2 = q.DeepClone(); 59 | #if NETCORE 60 | // Console.WriteLine(Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions 61 | // .GetRequiredService( 62 | // ((Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure) at).Instance)); 63 | /* Console.WriteLine(((Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure) at).Instance); 64 | var serviceProvider = ((Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure) at.DeepClone()).Instance; 65 | Console.WriteLine(serviceProvider); 66 | Console.WriteLine(serviceProvider.GetService(typeof(Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IChangeTrackerFactory))); 67 | Console.WriteLine(Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions 68 | .GetRequiredService( 69 | serviceProvider));*/ 70 | #endif 71 | // var q2 = q.DeepClone(); 72 | // Console.WriteLine(q2.); 73 | // Assert.That(q.ToArray().Length, Is.EqualTo(1)); 74 | Assert.That(q2.ToArray().Length, Is.EqualTo(1)); 75 | } 76 | 77 | [Test(Description = "Tests works on local SQL Server with AdventureWorks database")] 78 | [Ignore("Test on MS Server")] 79 | public void Clone_EfQuery2() 80 | { 81 | var q = new AdventureContext().Currencies.OrderBy(x => x.Name); 82 | var q2 = q.DeepClone(); 83 | var cnt = q.Count(); 84 | Assert.That(q2.Count(), Is.EqualTo(cnt)); 85 | } 86 | 87 | [Test] 88 | public void Clone_ComObject1() 89 | { 90 | #if !NETCORE 91 | // ReSharper disable SuspiciousTypeConversion.Global 92 | var manager = (KnownFolders.IKnownFolderManager)new KnownFolders.KnownFolderManager(); 93 | // ReSharper restore SuspiciousTypeConversion.Global 94 | Guid knownFolderId1; 95 | Guid knownFolderId2; 96 | manager.FolderIdFromCsidl(0, out knownFolderId1); 97 | manager.DeepClone().FolderIdFromCsidl(0, out knownFolderId2); 98 | Assert.That(knownFolderId1, Is.EqualTo(knownFolderId2)); 99 | #endif 100 | } 101 | 102 | [Test] 103 | public void Clone_ComObject2() 104 | { 105 | #if !NETCORE 106 | Type t = Type.GetTypeFromProgID("SAPI.SpVoice"); 107 | var obj = Activator.CreateInstance(t); 108 | obj.DeepClone(); 109 | #endif 110 | } 111 | 112 | [Test] 113 | public void Lazy_Clone() 114 | { 115 | var lazy = new LazyClass(); 116 | var clone = lazy.DeepClone(); 117 | var v = LazyClass.Counter; 118 | Assert.That(clone.GetValue(), Is.EqualTo((v + 1).ToString(CultureInfo.InvariantCulture))); 119 | Assert.That(lazy.GetValue(), Is.EqualTo((v + 2).ToString(CultureInfo.InvariantCulture))); 120 | } 121 | 122 | public class LazyClass 123 | { 124 | public static int Counter; 125 | 126 | private readonly LazyRef _lazyValue = new LazyRef(() => (object)(++Counter).ToString(CultureInfo.InvariantCulture)); 127 | 128 | public string GetValue() 129 | { 130 | return _lazyValue.Value.ToString(); 131 | } 132 | } 133 | 134 | [Table("Currency", Schema = "Sales")] 135 | public class Currency 136 | { 137 | [Key] 138 | public string CurrencyCode { get; set; } 139 | 140 | [Column] 141 | public string Name { get; set; } 142 | } 143 | 144 | public class AdventureContext : DbContext 145 | { 146 | public AdventureContext() 147 | #if !NETCORE 148 | : base("Server=.;Integrated Security=SSPI;Database=AdventureWorks") 149 | #endif 150 | { 151 | } 152 | 153 | public DbSet Currencies { get; set; } 154 | 155 | #if NETCORE 156 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 157 | { 158 | optionsBuilder.UseSqlServer(@"Server=.;Database=AdventureWorks;Trusted_Connection=true;MultipleActiveResultSets=true"); 159 | } 160 | #endif 161 | } 162 | 163 | [Test] 164 | public void GenericComparer_Clone() 165 | { 166 | var comparer = new TestComparer(); 167 | comparer.DeepClone(); 168 | } 169 | 170 | [Test] 171 | public void Closure_Clone() 172 | { 173 | int a = 0; 174 | Func f = () => ++a; 175 | var fCopy = f.DeepClone(); 176 | Assert.That(f(), Is.EqualTo(1)); 177 | Assert.That(fCopy(), Is.EqualTo(1)); 178 | Assert.That(a, Is.EqualTo(1)); 179 | } 180 | 181 | #if !NETCORE 182 | [Test] 183 | public void Windows_Forms_Clone() 184 | { 185 | var form = new System.Windows.Forms.Form(); 186 | form.Controls.Add(new System.Windows.Forms.ComboBox()); 187 | form.Controls.Add(new System.Windows.Forms.Button()); 188 | form.Controls.Add(new System.Windows.Forms.CheckBox()); 189 | for (var i = 0; i < 100; i++) 190 | { 191 | var sw = Stopwatch.StartNew(); 192 | form.DeepClone(); 193 | Console.WriteLine(sw.ElapsedMilliseconds); 194 | } 195 | } 196 | #endif 197 | 198 | private class TestComparer : Comparer 199 | { 200 | // make object unsafe to work 201 | private object _fieldX = new object(); 202 | 203 | public override int Compare(int x, int y) 204 | { 205 | return x.CompareTo(y); 206 | } 207 | } 208 | 209 | #if !NETCORE 210 | public class KnownFolders 211 | { 212 | [Guid("8BE2D872-86AA-4d47-B776-32CCA40C7018"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 213 | internal interface IKnownFolderManager 214 | { 215 | void FolderIdFromCsidl(int csidl, [Out] out Guid knownFolderID); 216 | 217 | void FolderIdToCsidl([In] [MarshalAs(UnmanagedType.LPStruct)] Guid id, [Out] out int csidl); 218 | 219 | void GetFolderIds(); 220 | } 221 | 222 | [ComImport, Guid("4df0c730-df9d-4ae3-9153-aa6b82e9795a")] 223 | internal class KnownFolderManager 224 | { 225 | // make object unsafe to work 226 | #pragma warning disable 169 227 | private object _fieldX; 228 | #pragma warning restore 169 229 | } 230 | } 231 | #endif 232 | public sealed class LazyRef 233 | { 234 | private Func _initializer; 235 | private T _value; 236 | 237 | /// 238 | /// This API supports the Entity Framework Core infrastructure and is not intended to be used 239 | /// directly from your code. This API may change or be removed in future releases. 240 | /// 241 | public T Value 242 | { 243 | get 244 | { 245 | if (_initializer != null) 246 | { 247 | _value = _initializer(); 248 | _initializer = null; 249 | } 250 | return _value; 251 | } 252 | set 253 | { 254 | _value = value; 255 | _initializer = null; 256 | } 257 | } 258 | 259 | public LazyRef(Func initializer) 260 | { 261 | _initializer = initializer; 262 | } 263 | } 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /DeepCloner.Tests/SystemTypesSpec.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.IO; 4 | using System.Reflection; 5 | using System.Runtime.InteropServices; 6 | using System.Security.Cryptography.X509Certificates; 7 | using System.Text; 8 | using Microsoft.Win32.SafeHandles; 9 | 10 | using NUnit.Framework; 11 | 12 | namespace Force.DeepCloner.Tests 13 | { 14 | #if !NETCORE 15 | [TestFixture(false)] 16 | #endif 17 | [TestFixture(true)] 18 | public class SystemTypesSpec : BaseTest 19 | { 20 | public SystemTypesSpec(bool isSafeInit) 21 | : base(isSafeInit) 22 | { 23 | } 24 | 25 | [Test] 26 | public void StandardTypes_Should_Be_Cloned() 27 | { 28 | var b = new StringBuilder(); 29 | b.Append("test1"); 30 | var cloned = b.DeepClone(); 31 | Assert.That(cloned.ToString(), Is.EqualTo("test1")); 32 | var arr = new[] { 1, 2, 3 }; 33 | var enumerator = arr.GetEnumerator(); 34 | enumerator.MoveNext(); 35 | var enumCloned = enumerator.DeepClone(); 36 | enumerator.MoveNext(); 37 | Assert.That(enumCloned.Current, Is.EqualTo(1)); 38 | } 39 | 40 | [Test(Description = "Just for fun, do not clone such object in real situation")] 41 | public void Type_With_Native_Resource_Should_Be_Cloned() 42 | { 43 | var fileName = Path.GetTempFileName(); 44 | try 45 | { 46 | var writer = File.CreateText(fileName); 47 | writer.AutoFlush = true; 48 | writer.Write("1"); 49 | var cloned = writer.DeepClone(); 50 | writer.Write("2"); 51 | cloned.Write("3"); 52 | #if !NETCORE 53 | var f = typeof(FileStream).GetField("_handle", BindingFlags.NonPublic | BindingFlags.Instance); 54 | var f2 = typeof(SafeHandle).GetField("_state", BindingFlags.NonPublic | BindingFlags.Instance); 55 | Console.WriteLine(f2.GetValue(f.GetValue(writer.BaseStream))); 56 | Console.WriteLine(f2.GetValue(f.GetValue(cloned.BaseStream))); 57 | #endif 58 | 59 | writer.Dispose(); 60 | // cloned.Close(); 61 | // not a bug anymore, this is feature: we don't duplicate handles, because it can cause unpredictable results 62 | // ~~this was a bug, we should not throw there~~ 63 | 64 | Assert.Throws(cloned.Flush); 65 | var res = File.ReadAllText(fileName); 66 | #if NETCORE60 67 | // it uses RandomAccess.WriteAtOffset(this._fileHandle, buffer, this._filePosition); - and offset of cloned file 68 | // is preserved, so, 2 will disappear 69 | Assert.That(res, Is.EqualTo("13")); 70 | #else 71 | Assert.That(res, Is.EqualTo("123")); 72 | #endif 73 | } 74 | finally 75 | { 76 | if (File.Exists(fileName)) 77 | File.Delete(fileName); 78 | } 79 | } 80 | 81 | [Test] 82 | public void Funcs_Should_Be_Cloned() 83 | { 84 | var closure = new[] { "123" }; 85 | Func f = x => closure[0] + x.ToString(CultureInfo.InvariantCulture); 86 | var df = f.DeepClone(); 87 | var cf = f.ShallowClone(); 88 | closure[0] = "xxx"; 89 | Assert.That(f(3), Is.EqualTo("xxx3")); 90 | // we clone delegate together with a closure 91 | Assert.That(df(3), Is.EqualTo("1233")); 92 | Assert.That(cf(3), Is.EqualTo("xxx3")); 93 | } 94 | 95 | public class EventHandlerTest1 96 | { 97 | public event Action Event; 98 | 99 | public int Call(int x) 100 | { 101 | if (Event != null) 102 | { 103 | Event(x); 104 | return Event.GetInvocationList().Length; 105 | } 106 | 107 | return 0; 108 | } 109 | } 110 | 111 | [Test(Description = "Some libraries notifies about problems with this types")] 112 | public void Events_Should_Be_Cloned() 113 | { 114 | var eht = new EventHandlerTest1(); 115 | var summ = new int[1]; 116 | Action a1 = x => summ[0] += x; 117 | Action a2 = x => summ[0] += x; 118 | eht.Event += a1; 119 | eht.Event += a2; 120 | eht.Call(1); 121 | Assert.That(summ[0], Is.EqualTo(2)); 122 | var clone = eht.DeepClone(); 123 | clone.Call(1); 124 | // do not call 125 | Assert.That(summ[0], Is.EqualTo(2)); 126 | eht.Event -= a1; 127 | eht.Event -= a2; 128 | Assert.That(eht.Call(1), Is.EqualTo(0)); // 0 129 | Assert.That(summ[0], Is.EqualTo(2)); // nothing to increment 130 | Assert.That(clone.Call(1), Is.EqualTo(2)); 131 | } 132 | 133 | private const string CertData = 134 | @"MIIJMgIBAzCCCO4GCSqGSIb3DQEHAaCCCN8EggjbMIII1zCCBggGCSqGSIb3DQEHAaCCBfkEggX1MIIF8TCCBe0GCyqGSIb3DQEMCgECoIIE/jCCBPowHAYKKoZIhvcNAQwBAzAOBAh9hwkqcYF1GAICB9AEggTY2Kmn91A3dsmgtwHIvQkZK0F7ebag/vl5XIEQl1rgSxJCy4V5ifRsLHOK9Sb6HwHBAnfl+zQIrAlESRzKkhzf3YFk/v9G5BkduBc7dFixR/xZSVNDcbEMOkQgT55s3RmRQehKaGA1svR0IQs5gsy1lv6KDpEWjIToPhvd/J9IVKGCEaofWGYwmVj6HaWGSduNELW0EtvpKzz4lw3t/YBwSA/6hRwy33JcAkC6NfViLzIbz42F3kyHBHc/BNHgDN3aHlInp4uaGw2/X9BYyhwJO3SZaqSXtb5WkcAxTT4EvgZifjxCXyn8rUyABGUhwAsHIX4ihef0eQ4Hksd7/vUiFN22nGTy3Z9wTzIdiYtUa6qO+wcWAyVSUXX3sjjEyMnw9LTjx4KFTV6nI6qiA5GY8o+9YEZ6pBcLQmVtEdm3n6tSnogwCtysnvqQDIgAKUvYVjXJN8EelGSzsSiq7nxUIFMxL/kObHlf6DhsPgEKJhdqZD1XdPiwAuGgCtZGHL/yIo7w76oPwpInQRC+v2/Tpzd/7O7yZACOSpjTWdnIDeJ6V4OvCnIi0R3mHC+UDY4IoqStUlqgeVk7kn18LsM5gkbH6jyg9E07nCOWmibDVyabJUBBw0Z64AmBJsvAW65Y7wk2NS+LI8YyzMSaIJqLPdRtGAx2BcoLM1jiWoxiKkdi0LeG2iUmXWZgtFU1+eZvhvh/6fmDcO+BBUSjyfqXmbOtBHbstGW6B5l4n7HvmYKDtLWDpQJ6yWSpTQT4XBx+kB6CZuByhcTItE7dtch9erfGy2olsVQjdz2w+a7fX13oYMAZSmtmUUzCUJDsv7i+dDTK1eYd0VpojxuziMk3ntOSrXy8aJghY45nvijmUTf/Yd6RoTp4u2dSHDoBdPtxOEoQwyeKaUXIrWHj+e5T4EuCtegwNyfyXNSaMLGHbxrckluEW/KzHtRw9b2517IBkYYegP7UGTYtVaUf+dtpr/DGlz1gYgd8b3Q0sIPnVaa6N+Wx2J2KtvNYczbmGN8zvRK41y9qNM68WZTq5URIu3RZdsTi1yiT2ujfOI5iQ0kTGbAQptBaS0SLqd9EjHvECNRs/rVRRWkcdHDAdOvhl9H962ioKUWhk/hn5PAc8PmlkzpDkv055d8f5wqB5Eo2yp31HT3d/DrURvuyUgE63aPV8grRE/VWL3zTA8ywecKRAg85sVi9mNlWuYhM0iZ7HqVATbm+S0O87m5sho5WemFvdNp8ockDWxhyeJEIGnvRYeiWzzqxJ0GlN7dH7gZntWA4vnN/er8bbfNo7l8i879oW8UhOlZOg75P7eFvJLSuISadelXfJA3FheEJSNkF8AY3C+1lxpuKXZBkXc/VP8N1NKmvbPiFg2ayEXNORWdyXvq2qieXjcYvGOW8itgxHp/23hzGRYl9hIb2h2RpEb2+bnW0367tt8DD+nk0xwup+xAiuX8hyLdYXQxVkll2CgPX+Lin4TGnM1DJt3wq7rX8M2GOl16ewgS2MloDqg7sbMxdW3DLkALmvpiAXn0MoZ1AsxnMODTv5sn6u+qPkQ355X2VeWtS7NTMGGu9/jrEvoAV0iMALk5Fs0TFrq2lzn4IOqTx9KWpNQ+0g0G+VcECkWs1eTJo64rPh6lxklH99TLPybLLkzGB2zATBgkqhkiG9w0BCRUxBgQEAQAAADBdBgkrBgEEAYI3EQExUB5OAE0AaQBjAHIAbwBzAG8AZgB0ACAAUwB0AHIAbwBuAGcAIABDAHIAeQBwAHQAbwBnAHIAYQBwAGgAaQBjACAAUAByAG8AdgBpAGQAZQByMGUGCSqGSIb3DQEJFDFYHlYAUAB2AGsAVABtAHAAOgAzADIANABjADIAYwA5ADQALQBmAGUAYwA5AC0ANAA5ADgANAAtAGEAYgA5ADUALQBiAGMAMABhADAANABiADkANgA1AGIAYjCCAscGCSqGSIb3DQEHBqCCArgwggK0AgEAMIICrQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQITyQ8tLZ3CUkCAgfQgIICgK776Lj5p/uMOsH/t3jqyIu2xgDbhQaNSeoPiiMOAiNMuWp7TSVjamjwzRQKTMpbcD8LHrP66hGqo5LtGmbWSlYUYkUAkFbSylZQwzgHM26YduBpOTWeG4rG3mcbaWAaLAOqmJpPqlWbq7/ma268e1lH1BKsC4ST/45ASyjPd6r/wVfDL1/79lkErpZxryum522uaGVrZBWcfW4vcPrC3C7kAARe7ZR6GDJ13QSdzlg56dkLc8vNWJ6LpmldA1NlDGlHDCibVE8xd4mnXD9Pl1NAs3cKEh30Y3iitnRaSyH7JlB9BbqxZAC/C7IOo13VnZs5kLyDbNGAApom5SRYYvmaEwYb797ALPJO1CACRR4kH3QmdwlxHZvT1zP7zS3zJOo4/ywrIdI3gA3xVzYc0KEAmSFBGio3uTBYLBNMg862ZbDKHbtQeGcz4tTVJXbb2A/ByOM3IRHjeqVP0/tBFNSOL26AJv/+Jzmmsv7+diWgYJZRbglDIeBl1hQmlO3xW+bZzzG8vZWE+WIh+yyW0uPjNvD9G23Ek4PWZB7WMzlzLzMC3gOT7EreDW7bX3vEwam731U/Ph9sngvNqHTxVJkqWzmk81Jrwi4b4V7H4JqZHjVokRiwsO9YedulCYAkivniqemhhhU3QJEhE2RfGL/ccHPxY6LfEOd0QP/nlExnsWovgJs4EXPuxL1P620laKPWmfgnAXK27vrrLWBWEAWvURTtnonpg6GYL80qvQ0bp+tKsstHrpX8vHrzuoxs/spwxJMiQSBQKPR8x/xhfjLCu1TFL9AyLk8CsYTYY9AuXSCoVWlWG6wXOWk+E8vuxF4Lhi0BMHedzpsicHJFngkwOzAfMAcGBSsOAwIaBBTmYIWRzwMbTCwbMDwj0Rz0joL2wgQUlAAqKmWCAeVezNkowxc9NAASduICAgfQ/jCCBPowHAYKKoZIhvcNAQwBAzAOBAiToGUt4o7yUQICB9AEggTYXC4Y7UdoiulV2lNOUoRKlurwP0Ta149qtCedqjbw3Bzj+v3nh0xSlVKsSL73vtImZHzDyGGIcskhOL0f2wPJLCndrItyzmosNOo1RC0mzhMuwgClcZ9ja3XYXFucmioP70KVwQMj4XO36wNWaRu0tV7IPXqbKyF/36oX/f4FJy+sHXdOhBpFQ9xL69Z+6YHjELDVtMnU/q+m6WWKXcItsAct8CBc5vMhRnoIQZ6E1LQxtDvkeJnCynRD3fTepQ5HNIEogkDFzacvdHZPrTl+3qKPjUxdQkmqj+1jSZYiJlwTg5O7cJNNs2gr29LMz3ln39vfl7jGHkZ49a+RasW6UOB5gGfv/TWFGbqLOTgsfUHVm8RfIX2DjNKas/S4WppxwabjrDeC8f5j9/6o0hqKmbMe9vPzkIoyPpdnVvJtMI5J6OBhv/XN5kQO0HL5MlP/44482shV6MTtXTuqKbl/ROpqE5mLVNOywEh6YqISy3jlP0JXdRk95aC1gKEgtjs99qY65hVhzeuvxlwJiAECrzmX89j6VbwX9WN3q/S7e7EKgc89Ez9+WHXK2nrF8EWmbgHtghXG1lnv/IXW89oaymVA3bgMd077Xq9MQiYQDbuPy5CM7mGASTTIMMwLn3/eruc5RBbe2aW9m9axLQjmD2ubIifSRNHmrswRJE2MK+TAWU8ZQzdtYZCpztUI+WNnKHVpGUZRXfQ31bnf/n4UkkzouFG1MPqBrjktk5r1f7gYQsZwBzBSahq1PgPM+VMyjC6py5FRyxydKrlvb28WmtaaaRyp56gZ9bL9hn7XdTMgThl/hTRrR93R+XGzv+2at3V9KsElT2nLtn77cuGlXP0LPtegOOV5dbHq4lNT+WKlEyNBgmx0eMmx5jBGYjIUomKqe4XJDFMOxu5i/qXA3ptwK7jpRpESHzbBQNMcktuqLTGq3sD8b2X01xAFw8m7jtwNpNovQogSusgPiP2MTrppcECsoiV0h1wKo+qok+vX7VXDPRJwIJ6NwjOA8LcboRun9pC5JhQZb4pfH+KgIc/Qau/QdmGPpxq27trxjoDWp/hwm2/8eM8QvVLNq8GANVv+P2uKoqAe8jKDwYz5txbsCy8BVNa2rDXvNTMklyqMVAtwqAUV3SsFXJVStkBsB9ykUtc23hR+unXTdD3JU0tQLaOfCW1JIReDyevfSm4NLxfdpZGXt5Vjv+1X29Klg2ltTLxSeFZgsnEDCK0Pip7DizCaYAFwuilZmZV9xsIHSzwxipuq5EFKYK0GznRL+ql9L4JVjkG5NS0XKPIye71D7OQ3CkkB+l34WQ59dYejqtD+AAFf7y6p7fjvHVMaRAWgt6/pIVwIfbfgmouXceqfjRkdnaGWmP65TEorX6hwd39YjEc7+K03vh5hOPBYAgatCL+9zbF/f0p5yz4yPqrfIHVPpbcIQvrcXSK3ZItB+FI8yb8JiTk2rOuIwnoiaaJKY0ero+aOdvqpOxtnboou+jC4yRunkJNU/CdgNYzovGo5yyJOMIeI83la+p9OTi006Daqjt0VvHN2ffew8Atme4SI6i4WoyTqvuL56dt7/z4Fn/orVtMD2Km3zSL3rnO5jmD4EoecebuedWeAMJAwALZ/2lD4YqoVq7hSSbaRYEAUKAl0nTGB2zATBgkqhkiG9w0BCRUxBgQEAQAAADBdBgkrBgEEAYI3EQExUB5OAE0AaQBjAHIAbwBzAG8AZgB0ACAAUwB0AHIAbwBuAGcAIABDAHIAeQBwAHQAbwBnAHIAYQBwAGgAaQBjACAAUAByAG8AdgBpAGQAZQByMGUGCSqGSIb3DQEJFDFYHlYAUAB2AGsAVABtAHAAOgAxADkAOABlAGEAZQBmADAALQA1AGUAYQA2AC0ANABhADMAMgAtAGIAMgA0AGIALQBjAGYAYgBhAGYAOABiADUAMwAxADMAODCCAscGCSqGSIb3DQEHBqCCArgwggK0AgEAMIICrQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIr7FAkC0i08gCAgfQgIICgCyCz31mdwGFcfgpnKklVcPLT51eD9UNNfO/aqKFELSnGZRkrJmq9Yn0ZrLVdnPBKX1DV/Cq8h+bbBYiA23+ZxbkuBXmsCISiUbeb1W+OLfAFQB5/BbLpHcDwtqy8LzvlNcbsjbOAG1VFvjM9qzztPqfla8MokNKqdp19HD4A1bL1iyHGTjz4H6fDhNQzeBXMN+glxruGTSEwTmtFb2tW2gj9hF7+fMTw2WNH2nY9RYLycPJvRd6dLcUAJYxTeGjtIUBMLuqW563MGmv9zGkorY6o6kaGUuKrlNktnuVwNVdvFzfJyjSO3uQz33Bg3NWfUUaCYfyNXzVjqBvNSsNnBNGEZoAGuQjXs+RXea+0BkiARdtsF2o9yu1GvIMmemauke+X+LEmCRTw4qpXVG+V2eGa5swdRbTSjq3tf8uUiLx3EkBJFaRVS7L+v/CDUYre4ssIyelNV9ITK4nSKp6oDvC5XPXHkqRiEVX9epUmt3agw3sSJsF2ACw72/BbT4lNdgSyDc+a7hVgL8pWe8P0wmIzWsL9kQ/2gNZWQyf7Fj8PY7z7Ohcs9xs6MRkVlH0lfZXIaeM0cWT4RN24CyHJh06ZFIHpc1zO7vb9abUE7ZUGImFJNnRKJs1y4eCMsGwoiwd9rXycN6cLb/UPUa9hBaOl/DBBCDRFrW7N/eiDpvXFnXWJyImyFQdrVyXeLQruGqytsImzxT2CW7XptiaTNMU3LWtYCCgLLq126Ttojm+n/4eCFJewINQ7wBOZ34EZSskdqjMRismL3JwCh39oLpRxr5ag0zwFJrhsK7BLxFt5L2NU7imR52pDUMI3lYa4Uo29E/xSojoTOgGSw9qtXUwOzAfMAcGBSsOAwIaBBTpBXySTHXv/6BZw/IYgYps4U06dQQUU28+n0acqo4w5J8aNS5/dJS4IkYCAgfQ"; 135 | 136 | [Test(Description = "Without special handling it causes exception on destruction due native resources usage")] 137 | public void Certificate_Should_Be_Cloned() 138 | { 139 | var cert = new X509Certificate2(Convert.FromBase64String(CertData), "1"); 140 | cert.DeepClone(); 141 | cert.DeepClone(); 142 | GC.Collect(); 143 | #if !NETCORE 144 | GC.WaitForFullGCComplete(); 145 | #endif 146 | } 147 | 148 | #if !NETCORE 149 | [Test(Description = "Without special handling it causes exception on destruction due native resources usage")] 150 | public void ObjectHandle_Should_Be_Cloned() 151 | { 152 | var cert = new X509Certificate2(Convert.FromBase64String(CertData), "1"); 153 | var handle = (SafeHandleZeroOrMinusOneIsInvalid) 154 | cert.GetType().GetField("m_safeCertContext", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(cert); 155 | handle.DeepClone(); 156 | handle.DeepClone(); 157 | GC.Collect(); 158 | GC.WaitForFullGCComplete(); 159 | } 160 | #endif 161 | 162 | [Test(Description = "Without special handling it causes exception on destruction due native resources usage")] 163 | public void Certificate_Should_Be_Shallow_Cloned() 164 | { 165 | var cert = new X509Certificate2(Convert.FromBase64String(CertData), "1"); 166 | cert.ShallowClone(); 167 | cert.ShallowClone(); 168 | GC.Collect(); 169 | #if !NETCORE 170 | GC.WaitForFullGCComplete(); 171 | #endif 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /DeepCloner.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /DeepCloner.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | DeepCloner 5 | DeepCloner 6 | 0.10.4 7 | force 8 | force 9 | MIT 10 | https://github.com/force-net/DeepCloner 11 | 12 | http://files.force-net.com/deepcloner-nuget-ico.png 13 | images\deepcloner-nuget-ico.png 14 | false 15 | 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. 16 | Small Library for fast deep or shallow cloning .NET objects. 17 | 18 | Excluded default comparers from cloning. Main fix for .NET6 and HashSet<string> but some other cases can be fixed too. 19 | 20 | Copyright by Force 2016-2022 21 | .NET shallow deep clone DeepClone fast 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /DeepCloner.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeepCloner", "DeepCloner\DeepCloner.csproj", "{6BB0A0AB-67F9-45E4-B60C-4CEED098D463}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeepCloner.Tests", "DeepCloner.Tests\DeepCloner.Tests.csproj", "{3453CB0A-BE66-4770-B92E-7A5917AD56A1}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {6BB0A0AB-67F9-45E4-B60C-4CEED098D463}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {6BB0A0AB-67F9-45E4-B60C-4CEED098D463}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {6BB0A0AB-67F9-45E4-B60C-4CEED098D463}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {6BB0A0AB-67F9-45E4-B60C-4CEED098D463}.Release|Any CPU.Build.0 = Release|Any CPU 18 | {3453CB0A-BE66-4770-B92E-7A5917AD56A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {3453CB0A-BE66-4770-B92E-7A5917AD56A1}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {3453CB0A-BE66-4770-B92E-7A5917AD56A1}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {3453CB0A-BE66-4770-B92E-7A5917AD56A1}.Release|Any CPU.Build.0 = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(SolutionProperties) = preSolution 24 | HideSolutionNode = FALSE 25 | EndGlobalSection 26 | EndGlobal 27 | -------------------------------------------------------------------------------- /DeepCloner/DeepCloner.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 0.10.4 4 | netstandard1.3;net40 5 | true 6 | DeepCloner 7 | 11 | DeepCloner 12 | false 13 | 14 | 15 | true 16 | ../public.snk 17 | true 18 | true 19 | true 20 | 21 | 22 | $(DefineConstants);BUILDCORE 23 | 24 | 25 | $(DefineConstants);NETCORE;NETCORE13 26 | 27 | 28 | $(DefineConstants);NETCORE;NETCORE20 29 | 30 | -------------------------------------------------------------------------------- /DeepCloner/DeepCloner.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {6BB0A0AB-67F9-45E4-B60C-4CEED098D463} 8 | Library 9 | Properties 10 | Force.DeepCloner 11 | DeepCloner 12 | v4.0 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | none 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | bin\Release\DeepCloner.xml 32 | 33 | 34 | false 35 | 36 | 37 | true 38 | 39 | 40 | ..\public.snk 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | public.snk 65 | 66 | 67 | 68 | 75 | -------------------------------------------------------------------------------- /DeepCloner/DeepClonerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security; 3 | 4 | using Force.DeepCloner.Helpers; 5 | 6 | namespace Force.DeepCloner 7 | { 8 | /// 9 | /// Extensions for object cloning 10 | /// 11 | public static class DeepClonerExtensions 12 | { 13 | /// 14 | /// Performs deep (full) copy of object and related graph 15 | /// 16 | public static T DeepClone(this T obj) 17 | { 18 | return DeepClonerGenerator.CloneObject(obj); 19 | } 20 | 21 | /// 22 | /// Performs deep (full) copy of object and related graph to existing object 23 | /// 24 | /// existing filled object 25 | /// Method is valid only for classes, classes should be descendants in reality, not in declaration 26 | public static TTo DeepCloneTo(this TFrom objFrom, TTo objTo) where TTo : class, TFrom 27 | { 28 | return (TTo)DeepClonerGenerator.CloneObjectTo(objFrom, objTo, true); 29 | } 30 | 31 | /// 32 | /// Performs shallow copy of object to existing object 33 | /// 34 | /// existing filled object 35 | /// Method is valid only for classes, classes should be descendants in reality, not in declaration 36 | public static TTo ShallowCloneTo(this TFrom objFrom, TTo objTo) where TTo : class, TFrom 37 | { 38 | return (TTo)DeepClonerGenerator.CloneObjectTo(objFrom, objTo, false); 39 | } 40 | 41 | /// 42 | /// Performs shallow (only new object returned, without cloning of dependencies) copy of object 43 | /// 44 | public static T ShallowClone(this T obj) 45 | { 46 | return ShallowClonerGenerator.CloneObject(obj); 47 | } 48 | 49 | static DeepClonerExtensions() 50 | { 51 | if (!PermissionCheck()) 52 | { 53 | throw new SecurityException("DeepCloner should have enough permissions to run. Grant FullTrust or Reflection permission."); 54 | } 55 | } 56 | 57 | private static bool PermissionCheck() 58 | { 59 | // best way to check required permission: execute something and receive exception 60 | // .net security policy is weird for normal usage 61 | try 62 | { 63 | new object().ShallowClone(); 64 | } 65 | catch (VerificationException) 66 | { 67 | return false; 68 | } 69 | catch (MemberAccessException) 70 | { 71 | return false; 72 | } 73 | 74 | return true; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /DeepCloner/Helpers/ClonerToExprGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | 7 | namespace Force.DeepCloner.Helpers 8 | { 9 | internal static class ClonerToExprGenerator 10 | { 11 | internal static object GenerateClonerInternal(Type realType, bool isDeepClone) 12 | { 13 | if (realType.IsValueType()) 14 | throw new InvalidOperationException("Operation is valid only for reference types"); 15 | return GenerateProcessMethod(realType, isDeepClone); 16 | } 17 | 18 | private static object GenerateProcessMethod(Type type, bool isDeepClone) 19 | { 20 | if (type.IsArray) 21 | { 22 | return GenerateProcessArrayMethod(type, isDeepClone); 23 | } 24 | 25 | var methodType = typeof(object); 26 | 27 | var expressionList = new List(); 28 | 29 | ParameterExpression from = Expression.Parameter(methodType); 30 | var fromLocal = from; 31 | var to = Expression.Parameter(methodType); 32 | var toLocal = to; 33 | var state = Expression.Parameter(typeof(DeepCloneState)); 34 | 35 | // if (!type.IsValueType()) 36 | { 37 | fromLocal = Expression.Variable(type); 38 | toLocal = Expression.Variable(type); 39 | // fromLocal = (T)from 40 | expressionList.Add(Expression.Assign(fromLocal, Expression.Convert(from, type))); 41 | expressionList.Add(Expression.Assign(toLocal, Expression.Convert(to, type))); 42 | 43 | if (isDeepClone) 44 | { 45 | // added from -> to binding to ensure reference loop handling 46 | // structs cannot loop here 47 | // state.AddKnownRef(from, to) 48 | expressionList.Add(Expression.Call(state, typeof(DeepCloneState).GetMethod("AddKnownRef"), from, to)); 49 | } 50 | } 51 | 52 | List fi = new List(); 53 | var tp = type; 54 | do 55 | { 56 | #if !NETCORE 57 | // don't do anything with this dark magic! 58 | if (tp == typeof(ContextBoundObject)) break; 59 | #else 60 | if (tp.Name == "ContextBoundObject") break; 61 | #endif 62 | 63 | fi.AddRange(tp.GetDeclaredFields()); 64 | tp = tp.BaseType(); 65 | } 66 | while (tp != null); 67 | 68 | foreach (var fieldInfo in fi) 69 | { 70 | if (isDeepClone && !DeepClonerSafeTypes.CanReturnSameObject(fieldInfo.FieldType)) 71 | { 72 | var methodInfo = fieldInfo.FieldType.IsValueType() 73 | ? typeof(DeepClonerGenerator).GetPrivateStaticMethod("CloneStructInternal") 74 | .MakeGenericMethod(fieldInfo.FieldType) 75 | : typeof(DeepClonerGenerator).GetPrivateStaticMethod("CloneClassInternal"); 76 | 77 | var get = Expression.Field(fromLocal, fieldInfo); 78 | 79 | // toLocal.Field = Clone...Internal(fromLocal.Field) 80 | var call = (Expression) Expression.Call(methodInfo, get, state); 81 | if (!fieldInfo.FieldType.IsValueType()) 82 | call = Expression.Convert(call, fieldInfo.FieldType); 83 | 84 | // should handle specially 85 | // todo: think about optimization, but it rare case 86 | if (fieldInfo.IsInitOnly) 87 | { 88 | // var setMethod = fieldInfo.GetType().GetMethod("SetValue", new[] { typeof(object), typeof(object) }); 89 | // expressionList.Add(Expression.Call(Expression.Constant(fieldInfo), setMethod, toLocal, call)); 90 | var setMethod = typeof(DeepClonerExprGenerator).GetPrivateStaticMethod("ForceSetField"); 91 | expressionList.Add(Expression.Call(setMethod, Expression.Constant(fieldInfo), 92 | Expression.Convert(toLocal, typeof(object)), Expression.Convert(call, typeof(object)))); 93 | } 94 | else 95 | { 96 | expressionList.Add(Expression.Assign(Expression.Field(toLocal, fieldInfo), call)); 97 | } 98 | } 99 | else 100 | { 101 | expressionList.Add(Expression.Assign(Expression.Field(toLocal, fieldInfo), Expression.Field(fromLocal, fieldInfo))); 102 | } 103 | } 104 | 105 | expressionList.Add(Expression.Convert(toLocal, methodType)); 106 | 107 | var funcType = typeof(Func<,,,>).MakeGenericType(methodType, methodType, typeof(DeepCloneState), methodType); 108 | 109 | var blockParams = new List(); 110 | if (from != fromLocal) blockParams.Add(fromLocal); 111 | if (to != toLocal) blockParams.Add(toLocal); 112 | 113 | return Expression.Lambda(funcType, Expression.Block(blockParams, expressionList), from, to, state).Compile(); 114 | } 115 | 116 | private static object GenerateProcessArrayMethod(Type type, bool isDeep) 117 | { 118 | var elementType = type.GetElementType(); 119 | var rank = type.GetArrayRank(); 120 | 121 | ParameterExpression from = Expression.Parameter(typeof(object)); 122 | ParameterExpression to = Expression.Parameter(typeof(object)); 123 | var state = Expression.Parameter(typeof(DeepCloneState)); 124 | 125 | var funcType = typeof(Func<,,,>).MakeGenericType(typeof(object), typeof(object), typeof(DeepCloneState), typeof(object)); 126 | 127 | if (rank == 1 && type == elementType.MakeArrayType()) 128 | { 129 | if (!isDeep) 130 | { 131 | var callS = Expression.Call( 132 | typeof(ClonerToExprGenerator).GetPrivateStaticMethod("ShallowClone1DimArraySafeInternal") 133 | .MakeGenericMethod(elementType), Expression.Convert(from, type), Expression.Convert(to, type)); 134 | return Expression.Lambda(funcType, callS, from, to, state).Compile(); 135 | } 136 | else 137 | { 138 | var methodName = "Clone1DimArrayClassInternal"; 139 | if (DeepClonerSafeTypes.CanReturnSameObject(elementType)) methodName = "Clone1DimArraySafeInternal"; 140 | else if (elementType.IsValueType()) methodName = "Clone1DimArrayStructInternal"; 141 | var methodInfo = typeof(ClonerToExprGenerator).GetPrivateStaticMethod(methodName).MakeGenericMethod(elementType); 142 | var callS = Expression.Call(methodInfo, Expression.Convert(from, type), Expression.Convert(to, type), state); 143 | return Expression.Lambda(funcType, callS, from, to, state).Compile(); 144 | } 145 | } 146 | else 147 | { 148 | // multidim or not zero-based arrays 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 | 155 | var callS = Expression.Call(methodInfo, Expression.Convert(from, type), Expression.Convert(to, type), state, Expression.Constant(isDeep)); 156 | return Expression.Lambda(funcType, callS, from, to, state).Compile(); 157 | } 158 | } 159 | 160 | // when we can't use code generation, we can use these methods 161 | internal static T[] ShallowClone1DimArraySafeInternal(T[] objFrom, T[] objTo) 162 | { 163 | var l = Math.Min(objFrom.Length, objTo.Length); 164 | Array.Copy(objFrom, objTo, l); 165 | return objTo; 166 | } 167 | 168 | // when we can't use code generation, we can use these methods 169 | internal static T[] Clone1DimArraySafeInternal(T[] objFrom, T[] objTo, DeepCloneState state) 170 | { 171 | var l = Math.Min(objFrom.Length, objTo.Length); 172 | state.AddKnownRef(objFrom, objTo); 173 | Array.Copy(objFrom, objTo, l); 174 | return objTo; 175 | } 176 | 177 | internal static T[] Clone1DimArrayStructInternal(T[] objFrom, T[] objTo, DeepCloneState state) 178 | { 179 | // not null from called method, but will check it anyway 180 | if (objFrom == null || objTo == null) return null; 181 | var l = Math.Min(objFrom.Length, objTo.Length); 182 | state.AddKnownRef(objFrom, objTo); 183 | var cloner = DeepClonerGenerator.GetClonerForValueType(); 184 | for (var i = 0; i < l; i++) 185 | objTo[i] = cloner(objTo[i], state); 186 | 187 | return objTo; 188 | } 189 | 190 | internal static T[] Clone1DimArrayClassInternal(T[] objFrom, T[] objTo, DeepCloneState state) 191 | { 192 | // not null from called method, but will check it anyway 193 | if (objFrom == null || objTo == null) return null; 194 | var l = Math.Min(objFrom.Length, objTo.Length); 195 | state.AddKnownRef(objFrom, objTo); 196 | for (var i = 0; i < l; i++) 197 | objTo[i] = (T)DeepClonerGenerator.CloneClassInternal(objFrom[i], state); 198 | 199 | return objTo; 200 | } 201 | 202 | internal static T[,] Clone2DimArrayInternal(T[,] objFrom, T[,] objTo, DeepCloneState state, bool isDeep) 203 | { 204 | // not null from called method, but will check it anyway 205 | 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 | 210 | var l1 = Math.Min(objFrom.GetLength(0), objTo.GetLength(0)); 211 | var l2 = Math.Min(objFrom.GetLength(1), objTo.GetLength(1)); 212 | state.AddKnownRef(objFrom, objTo); 213 | if ((!isDeep || DeepClonerSafeTypes.CanReturnSameObject(typeof(T))) 214 | && objFrom.GetLength(0) == objTo.GetLength(0) 215 | && objFrom.GetLength(1) == objTo.GetLength(1)) 216 | { 217 | Array.Copy(objFrom, objTo, objFrom.Length); 218 | return objTo; 219 | } 220 | 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 | } 228 | 229 | if (typeof(T).IsValueType()) 230 | { 231 | var cloner = DeepClonerGenerator.GetClonerForValueType(); 232 | for (var i = 0; i < l1; i++) 233 | for (var k = 0; k < l2; k++) 234 | objTo[i, k] = cloner(objFrom[i, k], state); 235 | } 236 | else 237 | { 238 | for (var i = 0; i < l1; i++) 239 | for (var k = 0; k < l2; k++) 240 | objTo[i, k] = (T)DeepClonerGenerator.CloneClassInternal(objFrom[i, k], state); 241 | } 242 | 243 | return objTo; 244 | } 245 | 246 | // rare cases, very slow cloning. currently it's ok 247 | internal static Array CloneAbstractArrayInternal(Array objFrom, Array objTo, DeepCloneState state, bool isDeep) 248 | { 249 | // not null from called method, but will check it anyway 250 | if (objFrom == null || objTo == null) return null; 251 | var rank = objFrom.Rank; 252 | 253 | if (objTo.Rank != rank) 254 | throw new InvalidOperationException("Invalid rank of target array"); 255 | var lowerBoundsFrom = Enumerable.Range(0, rank).Select(objFrom.GetLowerBound).ToArray(); 256 | var lowerBoundsTo = Enumerable.Range(0, rank).Select(objTo.GetLowerBound).ToArray(); 257 | var lengths = Enumerable.Range(0, rank).Select(x => Math.Min(objFrom.GetLength(x), objTo.GetLength(x))).ToArray(); 258 | var idxesFrom = Enumerable.Range(0, rank).Select(objFrom.GetLowerBound).ToArray(); 259 | var idxesTo = Enumerable.Range(0, rank).Select(objTo.GetLowerBound).ToArray(); 260 | 261 | state.AddKnownRef(objFrom, objTo); 262 | 263 | // unable to copy any element 264 | if (lengths.Any(x => x == 0)) 265 | return objTo; 266 | 267 | while (true) 268 | { 269 | if (isDeep) 270 | objTo.SetValue(DeepClonerGenerator.CloneClassInternal(objFrom.GetValue(idxesFrom), state), idxesTo); 271 | else 272 | objTo.SetValue(objFrom.GetValue(idxesFrom), idxesTo); 273 | var ofs = rank - 1; 274 | while (true) 275 | { 276 | idxesFrom[ofs]++; 277 | idxesTo[ofs]++; 278 | if (idxesFrom[ofs] >= lowerBoundsFrom[ofs] + lengths[ofs]) 279 | { 280 | idxesFrom[ofs] = lowerBoundsFrom[ofs]; 281 | idxesTo[ofs] = lowerBoundsTo[ofs]; 282 | ofs--; 283 | if (ofs < 0) return objTo; 284 | } 285 | else 286 | break; 287 | } 288 | } 289 | } 290 | 291 | } 292 | } -------------------------------------------------------------------------------- /DeepCloner/Helpers/DeepCloneState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace Force.DeepCloner.Helpers 7 | { 8 | internal class DeepCloneState 9 | { 10 | private MiniDictionary _loops; 11 | 12 | private readonly object[] _baseFromTo = new object[6]; 13 | 14 | private int _idx; 15 | 16 | public object GetKnownRef(object from) 17 | { 18 | // this is faster than call Dictionary from begin 19 | // also, small poco objects does not have a lot of references 20 | var baseFromTo = _baseFromTo; 21 | if (ReferenceEquals(from, baseFromTo[0])) return baseFromTo[3]; 22 | if (ReferenceEquals(from, baseFromTo[1])) return baseFromTo[4]; 23 | if (ReferenceEquals(from, baseFromTo[2])) return baseFromTo[5]; 24 | if (_loops == null) 25 | return null; 26 | 27 | return _loops.FindEntry(from); 28 | } 29 | 30 | public void AddKnownRef(object from, object to) 31 | { 32 | if (_idx < 3) 33 | { 34 | _baseFromTo[_idx] = from; 35 | _baseFromTo[_idx + 3] = to; 36 | _idx++; 37 | return; 38 | } 39 | 40 | if (_loops == null) 41 | _loops = new MiniDictionary(); 42 | _loops.Insert(from, to); 43 | } 44 | 45 | private class MiniDictionary 46 | { 47 | private struct Entry 48 | { 49 | public int HashCode; 50 | public int Next; 51 | public object Key; 52 | public object Value; 53 | } 54 | 55 | private int[] _buckets; 56 | private Entry[] _entries; 57 | private int _count; 58 | 59 | 60 | public MiniDictionary() : this(5) 61 | { 62 | } 63 | 64 | public MiniDictionary(int capacity) 65 | { 66 | if (capacity > 0) 67 | Initialize(capacity); 68 | } 69 | 70 | public object FindEntry(object key) 71 | { 72 | if (_buckets != null) 73 | { 74 | var hashCode = RuntimeHelpers.GetHashCode(key) & 0x7FFFFFFF; 75 | var entries1 = _entries; 76 | for (var i = _buckets[hashCode % _buckets.Length]; i >= 0; i = entries1[i].Next) 77 | { 78 | if (entries1[i].HashCode == hashCode && ReferenceEquals(entries1[i].Key, key)) 79 | return entries1[i].Value; 80 | } 81 | } 82 | 83 | return null; 84 | } 85 | 86 | private static readonly int[] _primes = 87 | { 88 | 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, 89 | 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, 90 | 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, 91 | 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, 92 | 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369 93 | }; 94 | 95 | private static int GetPrime(int min) 96 | { 97 | for (var i = 0; i < _primes.Length; i++) 98 | { 99 | var prime = _primes[i]; 100 | if (prime >= min) return prime; 101 | } 102 | 103 | //outside of our predefined table. 104 | //compute the hard way. 105 | for (var i = min | 1; i < int.MaxValue; i += 2) 106 | { 107 | if (IsPrime(i) && (i - 1) % 101 != 0) 108 | return i; 109 | } 110 | 111 | return min; 112 | } 113 | 114 | private static bool IsPrime(int candidate) 115 | { 116 | if ((candidate & 1) != 0) 117 | { 118 | var limit = (int)Math.Sqrt(candidate); 119 | for (var divisor = 3; divisor <= limit; divisor += 2) 120 | { 121 | if ((candidate % divisor) == 0) 122 | return false; 123 | } 124 | 125 | return true; 126 | } 127 | 128 | return candidate == 2; 129 | } 130 | 131 | private static int ExpandPrime(int oldSize) 132 | { 133 | var newSize = 2 * oldSize; 134 | 135 | if ((uint)newSize > 0x7FEFFFFD && 0x7FEFFFFD > oldSize) 136 | { 137 | return 0x7FEFFFFD; 138 | } 139 | 140 | return GetPrime(newSize); 141 | } 142 | 143 | private void Initialize(int size) 144 | { 145 | _buckets = new int[size]; 146 | for (int i = 0; i < _buckets.Length; i++) 147 | _buckets[i] = -1; 148 | _entries = new Entry[size]; 149 | } 150 | 151 | public void Insert(object key, object value) 152 | { 153 | if (_buckets == null) Initialize(0); 154 | var hashCode = RuntimeHelpers.GetHashCode(key) & 0x7FFFFFFF; 155 | var targetBucket = hashCode % _buckets.Length; 156 | 157 | var entries1 = _entries; 158 | 159 | // we're always checking for entry before adding new 160 | // so this loop is useless 161 | /*for (var i = _buckets[targetBucket]; i >= 0; i = entries1[i].Next) 162 | { 163 | if (entries1[i].HashCode == hashCode && ReferenceEquals(entries1[i].Key, key)) 164 | { 165 | entries1[i].Value = value; 166 | return; 167 | } 168 | }*/ 169 | 170 | if (_count == entries1.Length) 171 | { 172 | Resize(); 173 | entries1 = _entries; 174 | targetBucket = hashCode % _buckets.Length; 175 | } 176 | 177 | var index = _count; 178 | _count++; 179 | 180 | entries1[index].HashCode = hashCode; 181 | entries1[index].Next = _buckets[targetBucket]; 182 | entries1[index].Key = key; 183 | entries1[index].Value = value; 184 | _buckets[targetBucket] = index; 185 | } 186 | 187 | private void Resize() 188 | { 189 | Resize(ExpandPrime(_count)); 190 | } 191 | 192 | private void Resize(int newSize) 193 | { 194 | var newBuckets = new int[newSize]; 195 | for (int i = 0; i < newBuckets.Length; i++) 196 | newBuckets[i] = -1; 197 | var newEntries = new Entry[newSize]; 198 | Array.Copy(_entries, 0, newEntries, 0, _count); 199 | 200 | for (var i = 0; i < _count; i++) 201 | { 202 | if (newEntries[i].HashCode >= 0) 203 | { 204 | var bucket = newEntries[i].HashCode % newSize; 205 | newEntries[i].Next = newBuckets[bucket]; 206 | newBuckets[bucket] = i; 207 | } 208 | } 209 | 210 | _buckets = newBuckets; 211 | _entries = newEntries; 212 | } 213 | } 214 | } 215 | } -------------------------------------------------------------------------------- /DeepCloner/Helpers/DeepClonerCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | 4 | namespace Force.DeepCloner.Helpers 5 | { 6 | internal static class DeepClonerCache 7 | { 8 | private static readonly ConcurrentDictionary _typeCache = new ConcurrentDictionary(); 9 | 10 | private static readonly ConcurrentDictionary _typeCacheDeepTo = new ConcurrentDictionary(); 11 | 12 | private static readonly ConcurrentDictionary _typeCacheShallowTo = new ConcurrentDictionary(); 13 | 14 | private static readonly ConcurrentDictionary _structAsObjectCache = new ConcurrentDictionary(); 15 | 16 | private static readonly ConcurrentDictionary, object> _typeConvertCache = new ConcurrentDictionary, object>(); 17 | 18 | public static object GetOrAddClass(Type type, Func adder) 19 | { 20 | // return _typeCache.GetOrAdd(type, x => adder(x)); 21 | 22 | // this implementation is slightly faster than getoradd 23 | object value; 24 | if (_typeCache.TryGetValue(type, out value)) return value; 25 | 26 | // will lock by type object to ensure only one type generator is generated simultaneously 27 | lock (type) 28 | { 29 | value = _typeCache.GetOrAdd(type, t => adder(t)); 30 | } 31 | 32 | return value; 33 | } 34 | 35 | public static object GetOrAddDeepClassTo(Type type, Func adder) 36 | { 37 | object value; 38 | if (_typeCacheDeepTo.TryGetValue(type, out value)) return value; 39 | 40 | // will lock by type object to ensure only one type generator is generated simultaneously 41 | lock (type) 42 | { 43 | value = _typeCacheDeepTo.GetOrAdd(type, t => adder(t)); 44 | } 45 | 46 | return value; 47 | } 48 | 49 | public static object GetOrAddShallowClassTo(Type type, Func adder) 50 | { 51 | object value; 52 | if (_typeCacheShallowTo.TryGetValue(type, out value)) return value; 53 | 54 | // will lock by type object to ensure only one type generator is generated simultaneously 55 | lock (type) 56 | { 57 | value = _typeCacheShallowTo.GetOrAdd(type, t => adder(t)); 58 | } 59 | 60 | return value; 61 | } 62 | 63 | public static object GetOrAddStructAsObject(Type type, Func adder) 64 | { 65 | // return _typeCache.GetOrAdd(type, x => adder(x)); 66 | 67 | // this implementation is slightly faster than getoradd 68 | object value; 69 | if (_structAsObjectCache.TryGetValue(type, out value)) return value; 70 | 71 | // will lock by type object to ensure only one type generator is generated simultaneously 72 | lock (type) 73 | { 74 | value = _structAsObjectCache.GetOrAdd(type, t => adder(t)); 75 | } 76 | 77 | return value; 78 | } 79 | 80 | public static T GetOrAddConvertor(Type from, Type to, Func adder) 81 | { 82 | return (T)_typeConvertCache.GetOrAdd(new Tuple(from, to), (tuple) => adder(tuple.Item1, tuple.Item2)); 83 | } 84 | 85 | /// 86 | /// This method can be used when we switch between safe / unsafe variants (for testing) 87 | /// 88 | public static void ClearCache() 89 | { 90 | _typeCache.Clear(); 91 | _typeCacheDeepTo.Clear(); 92 | _typeCacheShallowTo.Clear(); 93 | _structAsObjectCache.Clear(); 94 | _typeConvertCache.Clear(); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /DeepCloner/Helpers/DeepClonerExprGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Linq.Expressions; 6 | using System.Reflection; 7 | 8 | namespace Force.DeepCloner.Helpers 9 | { 10 | internal static class DeepClonerExprGenerator 11 | { 12 | private static readonly ConcurrentDictionary _readonlyFields = new ConcurrentDictionary(); 13 | 14 | private static readonly bool _canFastCopyReadonlyFields = false; 15 | 16 | private static readonly MethodInfo _fieldSetMethod; 17 | static DeepClonerExprGenerator() 18 | { 19 | try 20 | { 21 | typeof(DeepClonerExprGenerator).GetPrivateStaticField(nameof(_canFastCopyReadonlyFields)).SetValue(null, true); 22 | #if NETCORE13 23 | _fieldSetMethod = typeof(FieldInfo).GetRuntimeMethod("SetValue", new[] { typeof(object), typeof(object) }); 24 | #else 25 | _fieldSetMethod = typeof(FieldInfo).GetMethod("SetValue", new[] {typeof(object), typeof(object)}); 26 | #endif 27 | 28 | if (_fieldSetMethod == null) 29 | throw new ArgumentNullException(); 30 | } 31 | catch (Exception) 32 | { 33 | // cannot 34 | } 35 | } 36 | 37 | internal static object GenerateClonerInternal(Type realType, bool asObject) 38 | { 39 | return GenerateProcessMethod(realType, asObject && realType.IsValueType()); 40 | } 41 | 42 | private static FieldInfo _attributesFieldInfo = typeof(FieldInfo).GetPrivateField("m_fieldAttributes"); 43 | 44 | // today, I found that it not required to do such complex things. Just SetValue is enough 45 | // is it new runtime changes, or I made incorrect assumptions eariler 46 | // slow, but hardcore method to set readonly field 47 | internal static void ForceSetField(FieldInfo field, object obj, object value) 48 | { 49 | var fieldInfo = field.GetType().GetPrivateField("m_fieldAttributes"); 50 | 51 | // TODO: think about it 52 | // nothing to do :( we should a throw an exception, but it is no good for user 53 | if (fieldInfo == null) 54 | return; 55 | var ov = fieldInfo.GetValue(field); 56 | if (!(ov is FieldAttributes)) 57 | return; 58 | var v = (FieldAttributes)ov; 59 | 60 | // protect from parallel execution, when first thread set field readonly back, and second set it to write value 61 | lock (fieldInfo) 62 | { 63 | fieldInfo.SetValue(field, v & ~FieldAttributes.InitOnly); 64 | field.SetValue(obj, value); 65 | fieldInfo.SetValue(field, v | FieldAttributes.InitOnly); 66 | } 67 | } 68 | 69 | private static object GenerateProcessMethod(Type type, bool unboxStruct) 70 | { 71 | if (type.IsArray) 72 | { 73 | return GenerateProcessArrayMethod(type); 74 | } 75 | 76 | if (type.FullName != null && type.FullName.StartsWith("System.Tuple`")) 77 | { 78 | // if not safe type it is no guarantee that some type will contain reference to 79 | // this tuple. In usual way, we're creating new object, setting reference for it 80 | // and filling data. For tuple, we will fill data before creating object 81 | // (in constructor arguments) 82 | var genericArguments = type.GenericArguments(); 83 | // current tuples contain only 8 arguments, but may be in future... 84 | // we'll write code that works with it 85 | if (genericArguments.Length < 10 && genericArguments.All(DeepClonerSafeTypes.CanReturnSameObject)) 86 | { 87 | return GenerateProcessTupleMethod(type); 88 | } 89 | } 90 | 91 | var methodType = unboxStruct || type.IsClass() ? typeof(object) : type; 92 | 93 | var expressionList = new List(); 94 | 95 | ParameterExpression from = Expression.Parameter(methodType); 96 | var fromLocal = from; 97 | var toLocal = Expression.Variable(type); 98 | var state = Expression.Parameter(typeof(DeepCloneState)); 99 | 100 | if (!type.IsValueType()) 101 | { 102 | var methodInfo = typeof(object).GetPrivateMethod("MemberwiseClone"); 103 | 104 | // to = (T)from.MemberwiseClone() 105 | expressionList.Add(Expression.Assign(toLocal, Expression.Convert(Expression.Call(from, methodInfo), type))); 106 | 107 | fromLocal = Expression.Variable(type); 108 | // fromLocal = (T)from 109 | expressionList.Add(Expression.Assign(fromLocal, Expression.Convert(from, type))); 110 | 111 | // added from -> to binding to ensure reference loop handling 112 | // structs cannot loop here 113 | // state.AddKnownRef(from, to) 114 | expressionList.Add(Expression.Call(state, typeof(DeepCloneState).GetMethod("AddKnownRef"), from, toLocal)); 115 | } 116 | else 117 | { 118 | if (unboxStruct) 119 | { 120 | // toLocal = (T)from; 121 | expressionList.Add(Expression.Assign(toLocal, Expression.Unbox(from, type))); 122 | fromLocal = Expression.Variable(type); 123 | // fromLocal = toLocal; // structs, it is ok to copy 124 | expressionList.Add(Expression.Assign(fromLocal, toLocal)); 125 | } 126 | else 127 | { 128 | // toLocal = from 129 | expressionList.Add(Expression.Assign(toLocal, from)); 130 | } 131 | } 132 | 133 | List fi = new List(); 134 | var tp = type; 135 | do 136 | { 137 | #if !NETCORE 138 | // don't do anything with this dark magic! 139 | if (tp == typeof(ContextBoundObject)) break; 140 | #else 141 | if (tp.Name == "ContextBoundObject") break; 142 | #endif 143 | 144 | fi.AddRange(tp.GetDeclaredFields()); 145 | tp = tp.BaseType(); 146 | } 147 | while (tp != null); 148 | 149 | foreach (var fieldInfo in fi) 150 | { 151 | if (!DeepClonerSafeTypes.CanReturnSameObject(fieldInfo.FieldType)) 152 | { 153 | var methodInfo = fieldInfo.FieldType.IsValueType() 154 | ? typeof(DeepClonerGenerator).GetPrivateStaticMethod("CloneStructInternal") 155 | .MakeGenericMethod(fieldInfo.FieldType) 156 | : typeof(DeepClonerGenerator).GetPrivateStaticMethod("CloneClassInternal"); 157 | 158 | var get = Expression.Field(fromLocal, fieldInfo); 159 | 160 | // toLocal.Field = Clone...Internal(fromLocal.Field) 161 | var call = (Expression)Expression.Call(methodInfo, get, state); 162 | if (!fieldInfo.FieldType.IsValueType()) 163 | call = Expression.Convert(call, fieldInfo.FieldType); 164 | 165 | // should handle specially 166 | // todo: think about optimization, but it rare case 167 | var isReadonly = _readonlyFields.GetOrAdd(fieldInfo, f => f.IsInitOnly); 168 | if (isReadonly) 169 | { 170 | if (_canFastCopyReadonlyFields) 171 | { 172 | expressionList.Add(Expression.Call( 173 | Expression.Constant(fieldInfo), 174 | _fieldSetMethod, 175 | Expression.Convert(toLocal, typeof(object)), 176 | Expression.Convert(call, typeof(object)))); 177 | } 178 | else 179 | { 180 | var setMethod = typeof(DeepClonerExprGenerator).GetPrivateStaticMethod("ForceSetField"); 181 | expressionList.Add(Expression.Call(setMethod, Expression.Constant(fieldInfo), Expression.Convert(toLocal, typeof(object)), Expression.Convert(call, typeof(object)))); 182 | } 183 | } 184 | else 185 | { 186 | expressionList.Add(Expression.Assign(Expression.Field(toLocal, fieldInfo), call)); 187 | } 188 | } 189 | } 190 | 191 | expressionList.Add(Expression.Convert(toLocal, methodType)); 192 | 193 | var funcType = typeof(Func<,,>).MakeGenericType(methodType, typeof(DeepCloneState), methodType); 194 | 195 | var blockParams = new List(); 196 | if (from != fromLocal) blockParams.Add(fromLocal); 197 | blockParams.Add(toLocal); 198 | 199 | return Expression.Lambda(funcType, Expression.Block(blockParams, expressionList), from, state).Compile(); 200 | } 201 | 202 | private static object GenerateProcessArrayMethod(Type type) 203 | { 204 | var elementType = type.GetElementType(); 205 | var rank = type.GetArrayRank(); 206 | 207 | MethodInfo methodInfo; 208 | 209 | // multidim or not zero-based arrays 210 | if (rank != 1 || type != elementType.MakeArrayType()) 211 | { 212 | if (rank == 2 && type == elementType.MakeArrayType(2)) 213 | { 214 | // small optimization for 2 dim arrays 215 | methodInfo = typeof(DeepClonerGenerator).GetPrivateStaticMethod("Clone2DimArrayInternal").MakeGenericMethod(elementType); 216 | } 217 | else 218 | { 219 | methodInfo = typeof(DeepClonerGenerator).GetPrivateStaticMethod("CloneAbstractArrayInternal"); 220 | } 221 | } 222 | else 223 | { 224 | var methodName = "Clone1DimArrayClassInternal"; 225 | if (DeepClonerSafeTypes.CanReturnSameObject(elementType)) methodName = "Clone1DimArraySafeInternal"; 226 | else if (elementType.IsValueType()) methodName = "Clone1DimArrayStructInternal"; 227 | methodInfo = typeof(DeepClonerGenerator).GetPrivateStaticMethod(methodName).MakeGenericMethod(elementType); 228 | } 229 | 230 | ParameterExpression from = Expression.Parameter(typeof(object)); 231 | var state = Expression.Parameter(typeof(DeepCloneState)); 232 | var call = Expression.Call(methodInfo, Expression.Convert(from, type), state); 233 | 234 | var funcType = typeof(Func<,,>).MakeGenericType(typeof(object), typeof(DeepCloneState), typeof(object)); 235 | 236 | return Expression.Lambda(funcType, call, from, state).Compile(); 237 | } 238 | 239 | private static object GenerateProcessTupleMethod(Type type) 240 | { 241 | ParameterExpression from = Expression.Parameter(typeof(object)); 242 | var state = Expression.Parameter(typeof(DeepCloneState)); 243 | 244 | var local = Expression.Variable(type); 245 | var assign = Expression.Assign(local, Expression.Convert(from, type)); 246 | 247 | var funcType = typeof(Func); 248 | 249 | var tupleLength = type.GenericArguments().Length; 250 | 251 | var constructor = Expression.Assign(local, Expression.New(type.GetPublicConstructors().First(x => x.GetParameters().Length == tupleLength), 252 | type.GetPublicProperties().OrderBy(x => x.Name) 253 | .Where(x => x.CanRead && x.Name.StartsWith("Item") && char.IsDigit(x.Name[4])) 254 | .Select(x => Expression.Property(local, x.Name)))); 255 | 256 | return Expression.Lambda(funcType, Expression.Block(new[] { local }, 257 | assign, constructor, Expression.Call(state, typeof(DeepCloneState).GetMethod("AddKnownRef"), from, local), 258 | from), 259 | from, state).Compile(); 260 | } 261 | 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /DeepCloner/Helpers/DeepClonerGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace Force.DeepCloner.Helpers 5 | { 6 | internal static class DeepClonerGenerator 7 | { 8 | public static T CloneObject(T obj) 9 | { 10 | if (obj is ValueType) 11 | { 12 | var type = obj.GetType(); 13 | if (typeof(T) == type) 14 | { 15 | if (DeepClonerSafeTypes.CanReturnSameObject(type)) 16 | return obj; 17 | 18 | return CloneStructInternal(obj, new DeepCloneState()); 19 | } 20 | } 21 | 22 | return (T)CloneClassRoot(obj); 23 | } 24 | 25 | private static object CloneClassRoot(object obj) 26 | { 27 | if (obj == null) 28 | return null; 29 | 30 | var cloner = (Func)DeepClonerCache.GetOrAddClass(obj.GetType(), t => GenerateCloner(t, true)); 31 | 32 | // null -> should return same type 33 | if (cloner == null) 34 | return obj; 35 | 36 | return cloner(obj, new DeepCloneState()); 37 | } 38 | 39 | internal static object CloneClassInternal(object obj, DeepCloneState state) 40 | { 41 | if (obj == null) 42 | return null; 43 | 44 | var cloner = (Func)DeepClonerCache.GetOrAddClass(obj.GetType(), t => GenerateCloner(t, true)); 45 | 46 | // safe object 47 | if (cloner == null) 48 | return obj; 49 | 50 | // loop 51 | var knownRef = state.GetKnownRef(obj); 52 | if (knownRef != null) 53 | return knownRef; 54 | 55 | return cloner(obj, state); 56 | } 57 | 58 | private static T CloneStructInternal(T obj, DeepCloneState state) // where T : struct 59 | { 60 | // no loops, no nulls, no inheritance 61 | var cloner = GetClonerForValueType(); 62 | 63 | // safe ojbect 64 | if (cloner == null) 65 | return obj; 66 | 67 | return cloner(obj, state); 68 | } 69 | 70 | // when we can't use code generation, we can use these methods 71 | internal static T[] Clone1DimArraySafeInternal(T[] obj, DeepCloneState state) 72 | { 73 | var l = obj.Length; 74 | var outArray = new T[l]; 75 | state.AddKnownRef(obj, outArray); 76 | Array.Copy(obj, outArray, obj.Length); 77 | return outArray; 78 | } 79 | 80 | internal static T[] Clone1DimArrayStructInternal(T[] obj, DeepCloneState state) 81 | { 82 | // not null from called method, but will check it anyway 83 | if (obj == null) return null; 84 | var l = obj.Length; 85 | var outArray = new T[l]; 86 | state.AddKnownRef(obj, outArray); 87 | var cloner = GetClonerForValueType(); 88 | for (var i = 0; i < l; i++) 89 | outArray[i] = cloner(obj[i], state); 90 | 91 | return outArray; 92 | } 93 | 94 | internal static T[] Clone1DimArrayClassInternal(T[] obj, DeepCloneState state) 95 | { 96 | // not null from called method, but will check it anyway 97 | if (obj == null) return null; 98 | var l = obj.Length; 99 | var outArray = new T[l]; 100 | state.AddKnownRef(obj, outArray); 101 | for (var i = 0; i < l; i++) 102 | outArray[i] = (T)CloneClassInternal(obj[i], state); 103 | 104 | return outArray; 105 | } 106 | 107 | // relatively frequent case. specially handled 108 | internal static T[,] Clone2DimArrayInternal(T[,] obj, DeepCloneState state) 109 | { 110 | // not null from called method, but will check it anyway 111 | 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 | 120 | var l1 = obj.GetLength(0); 121 | var l2 = obj.GetLength(1); 122 | var outArray = new T[l1, l2]; 123 | state.AddKnownRef(obj, outArray); 124 | if (DeepClonerSafeTypes.CanReturnSameObject(typeof(T))) 125 | { 126 | Array.Copy(obj, outArray, obj.Length); 127 | return outArray; 128 | } 129 | 130 | if (typeof(T).IsValueType()) 131 | { 132 | var cloner = GetClonerForValueType(); 133 | for (var i = 0; i < l1; i++) 134 | for (var k = 0; k < l2; k++) 135 | outArray[i, k] = cloner(obj[i, k], state); 136 | } 137 | else 138 | { 139 | for (var i = 0; i < l1; i++) 140 | for (var k = 0; k < l2; k++) 141 | outArray[i, k] = (T)CloneClassInternal(obj[i, k], state); 142 | } 143 | 144 | return outArray; 145 | } 146 | 147 | // rare cases, very slow cloning. currently it's ok 148 | internal static Array CloneAbstractArrayInternal(Array obj, DeepCloneState state) 149 | { 150 | // not null from called method, but will check it anyway 151 | if (obj == null) return null; 152 | var rank = obj.Rank; 153 | 154 | var lengths = Enumerable.Range(0, rank).Select(obj.GetLength).ToArray(); 155 | 156 | var lowerBounds = Enumerable.Range(0, rank).Select(obj.GetLowerBound).ToArray(); 157 | var idxes = Enumerable.Range(0, rank).Select(obj.GetLowerBound).ToArray(); 158 | 159 | var elementType = obj.GetType().GetElementType(); 160 | var outArray = Array.CreateInstance(elementType, lengths, lowerBounds); 161 | 162 | state.AddKnownRef(obj, outArray); 163 | 164 | // we're unable to set any value to this array, so, just return it 165 | if (lengths.Any(x => x == 0)) 166 | return outArray; 167 | 168 | if (DeepClonerSafeTypes.CanReturnSameObject(elementType)) 169 | { 170 | Array.Copy(obj, outArray, obj.Length); 171 | return outArray; 172 | } 173 | 174 | var ofs = rank - 1; 175 | while (true) 176 | { 177 | outArray.SetValue(CloneClassInternal(obj.GetValue(idxes), state), idxes); 178 | idxes[ofs]++; 179 | 180 | if (idxes[ofs] >= lowerBounds[ofs] + lengths[ofs]) 181 | { 182 | do 183 | { 184 | if (ofs == 0) return outArray; 185 | idxes[ofs] = lowerBounds[ofs]; 186 | ofs--; 187 | idxes[ofs]++; 188 | } while (idxes[ofs] >= lowerBounds[ofs] + lengths[ofs]); 189 | 190 | ofs = rank - 1; 191 | } 192 | } 193 | } 194 | 195 | internal static Func GetClonerForValueType() 196 | { 197 | return (Func)DeepClonerCache.GetOrAddStructAsObject(typeof(T), t => GenerateCloner(t, false)); 198 | } 199 | 200 | private static object GenerateCloner(Type t, bool asObject) 201 | { 202 | if (DeepClonerSafeTypes.CanReturnSameObject(t) && (asObject && !t.IsValueType())) 203 | return null; 204 | 205 | #if !NETCORE 206 | if (ShallowObjectCloner.IsSafeVariant()) return DeepClonerExprGenerator.GenerateClonerInternal(t, asObject); 207 | else return DeepClonerMsilGenerator.GenerateClonerInternal(t, asObject); 208 | #else 209 | return DeepClonerExprGenerator.GenerateClonerInternal(t, asObject); 210 | #endif 211 | } 212 | 213 | public static object CloneObjectTo(object objFrom, object objTo, bool isDeep) 214 | { 215 | if (objTo == null) return null; 216 | 217 | if (objFrom == null) 218 | throw new ArgumentNullException("objFrom", "Cannot copy null object to another"); 219 | var type = objFrom.GetType(); 220 | if (!type.IsInstanceOfType(objTo)) 221 | throw new InvalidOperationException("From object should be derived from From object, but From object has type " + objFrom.GetType().FullName + " and to " + objTo.GetType().FullName); 222 | if (objFrom is string) 223 | throw new InvalidOperationException("It is forbidden to clone strings"); 224 | var cloner = (Func)(isDeep 225 | ? DeepClonerCache.GetOrAddDeepClassTo(type, t => ClonerToExprGenerator.GenerateClonerInternal(t, true)) 226 | : DeepClonerCache.GetOrAddShallowClassTo(type, t => ClonerToExprGenerator.GenerateClonerInternal(t, false))); 227 | if (cloner == null) return objTo; 228 | return cloner(objFrom, objTo, new DeepCloneState()); 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /DeepCloner/Helpers/DeepClonerMsilGenerator.cs: -------------------------------------------------------------------------------- 1 | #if !NETCORE 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Reflection.Emit; 7 | using System.Threading; 8 | 9 | namespace Force.DeepCloner.Helpers 10 | { 11 | internal static class DeepClonerMsilGenerator 12 | { 13 | private static int _methodCounter; 14 | 15 | internal static object GenerateClonerInternal(Type realType, bool asObject) 16 | { 17 | // there is no performance penalties to cast objects to concrete type, but we can win in removing other conversions 18 | var methodType = asObject ? typeof(object) : realType; 19 | 20 | var mb = TypeCreationHelper.GetModuleBuilder(); 21 | var dt = new DynamicMethod( 22 | "DeepObjectCloner_" + realType.Name + "_" + Interlocked.Increment(ref _methodCounter), methodType, new[] { methodType, typeof(DeepCloneState) }, mb, true); 23 | 24 | dt.InitLocals = false; 25 | 26 | var il = dt.GetILGenerator(); 27 | /*il.Emit(OpCodes.Ldarg_0); 28 | il.Emit(OpCodes.Ret);*/ 29 | 30 | GenerateProcessMethod(il, realType, asObject && realType.IsValueType); 31 | 32 | var funcType = typeof(Func<,,>).MakeGenericType(methodType, typeof(DeepCloneState), methodType); 33 | 34 | return dt.CreateDelegate(funcType); 35 | } 36 | 37 | private static void GenerateProcessMethod(ILGenerator il, Type type, bool unboxStruct) 38 | { 39 | if (type.IsArray) 40 | { 41 | GenerateProcessArrayMethod(il, type); 42 | return; 43 | } 44 | 45 | if (type.FullName != null && type.FullName.StartsWith("System.Tuple`")) 46 | { 47 | // if not safe type it is no guarantee that some type will contain reference to 48 | // this tuple. In usual way, we're creating new object, setting reference for it 49 | // and filling data. For tuple, we will fill data before creating object 50 | // (in constructor arguments) 51 | var genericArguments = type.GenericArguments(); 52 | // current tuples contain only 8 arguments, but may be in future... 53 | // we'll write code that works with it 54 | if (genericArguments.Length < 10 && genericArguments.All(DeepClonerSafeTypes.CanReturnSameObject)) 55 | { 56 | GenerateProcessTupleMethod(il, type); 57 | return; 58 | } 59 | } 60 | 61 | var typeLocal = il.DeclareLocal(type); 62 | LocalBuilder structLoc = null; 63 | 64 | var isGoodConstructor = false; 65 | 66 | if (!type.IsValueType) 67 | { 68 | var constructor = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null); 69 | 70 | isGoodConstructor = DeepClonerMsilHelper.IsConstructorDoNothing(type, constructor); 71 | 72 | // isGoodConstructor = false; 73 | 74 | // objects are used for locking and they are safe for constructor call 75 | if (isGoodConstructor) 76 | { 77 | il.Emit(OpCodes.Newobj, constructor); 78 | } 79 | else 80 | { 81 | // if (type.IsContextful) 82 | // { 83 | il.Emit(OpCodes.Ldarg_0); 84 | il.Emit(OpCodes.Call, typeof(object).GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic)); 85 | // } 86 | /*else <-- just for tests 87 | { 88 | isGoodConstructor = true; 89 | il.Emit(OpCodes.Ldarg_0); 90 | il.Emit(OpCodes.Call, typeof(object).GetMethod("GetType")); 91 | il.Emit(OpCodes.Call, typeof(System.Runtime.Serialization.FormatterServices).GetMethod("GetUninitializedObject")); 92 | }*/ 93 | 94 | } 95 | 96 | il.Emit(OpCodes.Stloc, typeLocal); 97 | } 98 | else 99 | { 100 | if (unboxStruct) 101 | { 102 | il.Emit(OpCodes.Ldarg_0); 103 | il.Emit(OpCodes.Unbox_Any, type); 104 | structLoc = il.DeclareLocal(type); 105 | il.Emit(OpCodes.Dup); 106 | il.Emit(OpCodes.Stloc, structLoc); 107 | il.Emit(OpCodes.Stloc, typeLocal); 108 | } 109 | else 110 | { 111 | il.Emit(OpCodes.Ldarg_0); 112 | il.Emit(OpCodes.Stloc, typeLocal); 113 | } 114 | } 115 | 116 | // added from -> to binding to ensure reference loop handling 117 | // structs cannot loop here 118 | if (type.IsClass) 119 | { 120 | il.Emit(OpCodes.Ldarg_1); 121 | il.Emit(OpCodes.Ldarg_0); 122 | il.Emit(OpCodes.Ldloc, typeLocal); 123 | il.Emit(OpCodes.Call, typeof(DeepCloneState).GetMethod("AddKnownRef")); 124 | } 125 | 126 | List fi = new List(); 127 | var tp = type; 128 | do 129 | { 130 | // don't do anything with this dark magic! 131 | if (tp == typeof(ContextBoundObject)) break; 132 | fi.AddRange(tp.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly)); 133 | tp = tp.BaseType; 134 | } 135 | while (tp != null); 136 | 137 | foreach (var fieldInfo in fi) 138 | { 139 | if (DeepClonerSafeTypes.CanReturnSameObject(fieldInfo.FieldType)) 140 | { 141 | // for good consturctor we use direct field copy anyway 142 | if (isGoodConstructor) 143 | { 144 | il.Emit(type.IsClass ? OpCodes.Ldloc : OpCodes.Ldloca_S, typeLocal); 145 | il.Emit(OpCodes.Ldarg_0); 146 | il.Emit(OpCodes.Ldfld, fieldInfo); 147 | il.Emit(OpCodes.Stfld, fieldInfo); 148 | } 149 | } 150 | else 151 | { 152 | il.Emit(type.IsClass ? OpCodes.Ldloc : OpCodes.Ldloca_S, typeLocal); 153 | if (structLoc == null) il.Emit(OpCodes.Ldarg_0); 154 | else il.Emit(OpCodes.Ldloc, structLoc); 155 | il.Emit(OpCodes.Ldfld, fieldInfo); 156 | il.Emit(OpCodes.Ldarg_1); 157 | 158 | var methodInfo = fieldInfo.FieldType.IsValueType 159 | ? typeof(DeepClonerGenerator).GetMethod("CloneStructInternal", BindingFlags.NonPublic | BindingFlags.Static) 160 | .MakeGenericMethod(fieldInfo.FieldType) 161 | : typeof(DeepClonerGenerator).GetMethod("CloneClassInternal", BindingFlags.NonPublic | BindingFlags.Static); 162 | il.Emit(OpCodes.Call, methodInfo); 163 | il.Emit(OpCodes.Stfld, fieldInfo); 164 | } 165 | } 166 | 167 | il.Emit(OpCodes.Ldloc, typeLocal); 168 | if (unboxStruct) 169 | il.Emit(OpCodes.Box, type); 170 | il.Emit(OpCodes.Ret); 171 | } 172 | 173 | private static void GenerateProcessArrayMethod(ILGenerator il, Type type) 174 | { 175 | var elementType = type.GetElementType(); 176 | var rank = type.GetArrayRank(); 177 | // multidim or not zero-based arrays 178 | if (rank != 1 || type != elementType.MakeArrayType()) 179 | { 180 | MethodInfo methodInfo; 181 | if (rank == 2 && type == elementType.MakeArrayType(2)) 182 | { 183 | // small optimization for 2 dim arrays 184 | methodInfo = typeof(DeepClonerGenerator).GetMethod("Clone2DimArrayInternal", BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod(elementType); 185 | } 186 | else 187 | { 188 | methodInfo = typeof(DeepClonerGenerator).GetMethod("CloneAbstractArrayInternal", BindingFlags.NonPublic | BindingFlags.Static); 189 | } 190 | 191 | il.Emit(OpCodes.Ldarg_0); 192 | il.Emit(OpCodes.Ldarg_1); 193 | il.Emit(OpCodes.Call, methodInfo); 194 | il.Emit(OpCodes.Ret); 195 | return; 196 | } 197 | 198 | // TODO: processing array of structs can be simplified 199 | var typeLocal = il.DeclareLocal(type); 200 | var lenLocal = il.DeclareLocal(typeof(int)); 201 | 202 | il.Emit(OpCodes.Ldarg_0); 203 | // msil uses native unsigned ints for array length, but C# does not have such types, so, we use usual int length 204 | // il.Emit(OpCodes.Ldlen); 205 | il.Emit(OpCodes.Call, type.GetProperty("Length").GetGetMethod()); 206 | il.Emit(OpCodes.Dup); 207 | il.Emit(OpCodes.Stloc, lenLocal); 208 | il.Emit(OpCodes.Newarr, elementType); 209 | il.Emit(OpCodes.Stloc, typeLocal); 210 | 211 | // add ref 212 | il.Emit(OpCodes.Ldarg_1); 213 | il.Emit(OpCodes.Ldarg_0); 214 | il.Emit(OpCodes.Ldloc, typeLocal); 215 | il.Emit(OpCodes.Call, typeof(DeepCloneState).GetMethod("AddKnownRef")); 216 | 217 | if (DeepClonerSafeTypes.CanReturnSameObject(elementType)) 218 | { 219 | // Array.Copy(from, to, from.Length); 220 | il.Emit(OpCodes.Ldarg_0); 221 | il.Emit(OpCodes.Ldloc, typeLocal); 222 | il.Emit(OpCodes.Ldloc, lenLocal); 223 | il.Emit( 224 | OpCodes.Call, 225 | typeof(Array).GetMethod("Copy", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(Array), typeof(Array), typeof(int) }, null)); 226 | } 227 | else 228 | { 229 | var methodInfo = elementType.IsValueType 230 | ? typeof(DeepClonerGenerator).GetMethod("CloneStructInternal", BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod(elementType) 231 | : typeof(DeepClonerGenerator).GetMethod("CloneClassInternal", BindingFlags.NonPublic | BindingFlags.Static); 232 | LocalBuilder clonerLocal = null; 233 | 234 | if (type.IsValueType) 235 | { 236 | // unsafe struct, no inheritance, so, we can use fixed cloner 237 | var funcType = typeof(Func<,,>).MakeGenericType(elementType, typeof(DeepCloneState), elementType); 238 | methodInfo = funcType.GetMethod("Invoke"); 239 | clonerLocal = il.DeclareLocal(funcType); 240 | il.Emit(OpCodes.Call, typeof(DeepClonerGenerator).GetMethod("GetClonerForValueType", BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod(elementType)); 241 | il.Emit(OpCodes.Stloc, clonerLocal); 242 | } 243 | 244 | var endLoopLabel = il.DefineLabel(); 245 | var startLoopLabel = il.DefineLabel(); 246 | // using for-loop 247 | var iLocal = il.DeclareLocal(typeof(int)); 248 | il.Emit(OpCodes.Ldc_I4_0); 249 | il.Emit(OpCodes.Stloc, iLocal); 250 | 251 | il.MarkLabel(startLoopLabel); 252 | 253 | il.Emit(OpCodes.Ldloc, iLocal); 254 | il.Emit(OpCodes.Ldloc, lenLocal); 255 | il.Emit(OpCodes.Bge_S, endLoopLabel); 256 | 257 | // to[i] = Clone(from[i]) 258 | il.Emit(OpCodes.Ldloc, typeLocal); // for save 259 | il.Emit(OpCodes.Ldloc, iLocal); 260 | 261 | if (clonerLocal != null) 262 | il.Emit(OpCodes.Ldloc, clonerLocal); 263 | 264 | il.Emit(OpCodes.Ldarg_0); 265 | il.Emit(OpCodes.Ldloc, iLocal); 266 | il.Emit(OpCodes.Ldelem, elementType); // get elem 267 | 268 | il.Emit(OpCodes.Ldarg_1); 269 | 270 | il.Emit(OpCodes.Call, methodInfo); 271 | il.Emit(OpCodes.Stelem, elementType); 272 | 273 | il.Emit(OpCodes.Ldloc, iLocal); 274 | il.Emit(OpCodes.Ldc_I4_1); 275 | il.Emit(OpCodes.Add); 276 | il.Emit(OpCodes.Stloc, iLocal); 277 | il.Emit(OpCodes.Br_S, startLoopLabel); 278 | 279 | il.MarkLabel(endLoopLabel); 280 | } 281 | 282 | il.Emit(OpCodes.Ldloc, typeLocal); 283 | il.Emit(OpCodes.Ret); 284 | } 285 | 286 | /*internal static object GenerateConvertor(Type from, Type to) 287 | { 288 | var mb = TypeCreationHelper.GetModuleBuilder(); 289 | 290 | var dt = new DynamicMethod( 291 | "DeepObjectConvertor_" + from.Name + "_" + to.Name + "_" + Interlocked.Increment(ref _methodCounter), to, new[] { to, typeof(DeepCloneState) }, mb, true); 292 | var il = dt.GetILGenerator(); 293 | il.Emit(OpCodes.Ldarg_0); // to 294 | var isStruct = from.IsValueType; 295 | if (isStruct) 296 | il.Emit(OpCodes.Unbox_Any, from); 297 | il.Emit(OpCodes.Ldarg_1); // state 298 | var realMethod = 299 | typeof(DeepClonerGenerator).GetMethod(isStruct ? "CloneStructInternal" : "CloneClassInternal", BindingFlags.NonPublic | BindingFlags.Static) 300 | .MakeGenericMethod(from); 301 | 302 | il.Emit(OpCodes.Call, realMethod); 303 | if (isStruct) 304 | il.Emit(OpCodes.Box, from); 305 | il.Emit(OpCodes.Ret); 306 | var funcType = typeof(Func<,,>).MakeGenericType(to, typeof(DeepCloneState), to); 307 | 308 | return dt.CreateDelegate(funcType); 309 | }*/ 310 | 311 | /*internal static Func GenerateMemberwiseCloner() 312 | { 313 | // only non-null classes. it is ok such simple implementation 314 | var dt = new DynamicMethod( 315 | "ShallowObjectCloner_" + Interlocked.Increment(ref _methodCounter), typeof(object), new[] { typeof(object) }, TypeCreationHelper.GetModuleBuilder(), true); 316 | 317 | var il = dt.GetILGenerator(); 318 | il.Emit(OpCodes.Ldarg_0); 319 | il.Emit(OpCodes.Call, typeof(object).GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic)); 320 | il.Emit(OpCodes.Ret); 321 | 322 | return (Func)dt.CreateDelegate(typeof(Func)); 323 | }*/ 324 | 325 | private static void GenerateProcessTupleMethod(ILGenerator il, Type type) 326 | { 327 | var tupleLength = type.GenericArguments().Length; 328 | var constructor = type.GetPublicConstructors().First(x => x.GetParameters().Length == tupleLength); 329 | var properties = type.GetPublicProperties().OrderBy(x => x.Name) 330 | .Where(x => x.CanRead && x.Name.StartsWith("Item") && char.IsDigit(x.Name[4])); 331 | 332 | foreach (var propertyInfo in properties) 333 | { 334 | il.Emit(OpCodes.Ldarg_0); 335 | il.Emit(OpCodes.Callvirt, propertyInfo.GetGetMethod()); 336 | } 337 | 338 | il.Emit(OpCodes.Newobj, constructor); 339 | var typeLocal = il.DeclareLocal(type); 340 | il.Emit(OpCodes.Stloc, typeLocal); 341 | 342 | // add ref 343 | il.Emit(OpCodes.Ldarg_1); 344 | il.Emit(OpCodes.Ldarg_0); 345 | il.Emit(OpCodes.Ldloc, typeLocal); 346 | il.Emit(OpCodes.Call, typeof(DeepCloneState).GetMethod("AddKnownRef")); 347 | il.Emit(OpCodes.Ldloc, typeLocal); 348 | il.Emit(OpCodes.Ret); 349 | } 350 | } 351 | } 352 | #endif -------------------------------------------------------------------------------- /DeepCloner/Helpers/DeepClonerMsilHelper.cs: -------------------------------------------------------------------------------- 1 | #if !NETCORE 2 | using System; 3 | using System.Reflection; 4 | 5 | namespace Force.DeepCloner.Helpers 6 | { 7 | internal static class DeepClonerMsilHelper 8 | { 9 | public static bool IsConstructorDoNothing(Type type, ConstructorInfo constructor) 10 | { 11 | if (constructor == null) return false; 12 | try 13 | { 14 | // will not try to determine body for this types 15 | if (type.IsGenericType || type.IsContextful || type.IsCOMObject || type.Assembly.IsDynamic) return false; 16 | 17 | var methodBody = constructor.GetMethodBody(); 18 | 19 | // this situation can be for com 20 | if (methodBody == null) return false; 21 | 22 | var ilAsByteArray = methodBody.GetILAsByteArray(); 23 | if (ilAsByteArray.Length == 7 24 | && ilAsByteArray[0] == 0x02 // Ldarg_0 25 | && ilAsByteArray[1] == 0x28 // newobj 26 | && ilAsByteArray[6] == 0x2a // ret 27 | && type.Module.ResolveMethod(BitConverter.ToInt32(ilAsByteArray, 2)) == typeof(object).GetConstructor(Type.EmptyTypes)) // call object 28 | { 29 | return true; 30 | } 31 | else if (ilAsByteArray.Length == 1 && ilAsByteArray[0] == 0x2a) // ret 32 | { 33 | return true; 34 | } 35 | 36 | return false; 37 | } 38 | catch (Exception) 39 | { 40 | // no permissions or something similar 41 | return false; 42 | } 43 | } 44 | } 45 | } 46 | #endif -------------------------------------------------------------------------------- /DeepCloner/Helpers/DeepClonerSafeTypes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | 7 | namespace Force.DeepCloner.Helpers 8 | { 9 | /// 10 | /// Safe types are types, which can be copied without real cloning. e.g. simple structs or strings (it is immutable) 11 | /// 12 | internal static class DeepClonerSafeTypes 13 | { 14 | internal static readonly ConcurrentDictionary KnownTypes = new ConcurrentDictionary(); 15 | 16 | static DeepClonerSafeTypes() 17 | { 18 | foreach ( 19 | var x in 20 | new[] 21 | { 22 | typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), 23 | typeof(float), typeof(double), typeof(decimal), typeof(char), typeof(string), typeof(bool), typeof(DateTime), 24 | typeof(IntPtr), typeof(UIntPtr), typeof(Guid), 25 | // do not clone such native type 26 | Type.GetType("System.RuntimeType"), 27 | Type.GetType("System.RuntimeTypeHandle"), 28 | StringComparer.Ordinal.GetType(), 29 | StringComparer.CurrentCulture.GetType(), // CultureAwareComparer - can be same 30 | #if !NETCORE 31 | typeof(DBNull) 32 | #endif 33 | }) KnownTypes.TryAdd(x, true); 34 | } 35 | 36 | private static bool CanReturnSameType(Type type, HashSet processingTypes) 37 | { 38 | bool isSafe; 39 | if (KnownTypes.TryGetValue(type, out isSafe)) 40 | return isSafe; 41 | 42 | // enums are safe 43 | // pointers (e.g. int*) are unsafe, but we cannot do anything with it except blind copy 44 | if (type.IsEnum() || type.IsPointer) 45 | { 46 | KnownTypes.TryAdd(type, true); 47 | return true; 48 | } 49 | 50 | #if !NETCORE 51 | // do not do anything with remoting. it is very dangerous to clone, bcs it relate to deep core of framework 52 | if (type.FullName.StartsWith("System.Runtime.Remoting.") 53 | && type.Assembly == typeof(System.Runtime.Remoting.CustomErrorsModes).Assembly) 54 | { 55 | KnownTypes.TryAdd(type, true); 56 | return true; 57 | } 58 | 59 | if (type.FullName.StartsWith("System.Reflection.") && type.Assembly == typeof(PropertyInfo).Assembly) 60 | { 61 | KnownTypes.TryAdd(type, true); 62 | return true; 63 | } 64 | 65 | // catched by previous condition 66 | /*if (type.FullName.StartsWith("System.Reflection.Emit") && type.Assembly == typeof(System.Reflection.Emit.OpCode).Assembly) 67 | { 68 | KnownTypes.TryAdd(type, true); 69 | return true; 70 | }*/ 71 | 72 | // this types are serious native resources, it is better not to clone it 73 | if (type.IsSubclassOf(typeof(System.Runtime.ConstrainedExecution.CriticalFinalizerObject))) 74 | { 75 | KnownTypes.TryAdd(type, true); 76 | return true; 77 | } 78 | 79 | // Better not to do anything with COM 80 | if (type.IsCOMObject) 81 | { 82 | KnownTypes.TryAdd(type, true); 83 | return true; 84 | } 85 | #else 86 | // do not copy db null 87 | if (type.FullName.StartsWith("System.DBNull")) 88 | { 89 | KnownTypes.TryAdd(type, true); 90 | return true; 91 | } 92 | 93 | if (type.FullName.StartsWith("System.RuntimeType")) 94 | { 95 | KnownTypes.TryAdd(type, true); 96 | return true; 97 | } 98 | 99 | if (type.FullName.StartsWith("System.Reflection.") && Equals(type.GetTypeInfo().Assembly, typeof(PropertyInfo).GetTypeInfo().Assembly)) 100 | { 101 | KnownTypes.TryAdd(type, true); 102 | return true; 103 | } 104 | 105 | if (type.IsSubclassOfTypeByName("CriticalFinalizerObject")) 106 | { 107 | KnownTypes.TryAdd(type, true); 108 | return true; 109 | } 110 | 111 | // better not to touch ms dependency injection 112 | if (type.FullName.StartsWith("Microsoft.Extensions.DependencyInjection.")) 113 | { 114 | KnownTypes.TryAdd(type, true); 115 | return true; 116 | } 117 | 118 | if (type.FullName == "Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector") 119 | { 120 | KnownTypes.TryAdd(type, true); 121 | return true; 122 | } 123 | #endif 124 | // default comparers should not be cloned due possible comparison EqualityComparer.Default == comparer 125 | if (type.FullName.Contains("EqualityComparer")) 126 | { 127 | if (type.FullName.StartsWith("System.Collections.Generic.GenericEqualityComparer`") 128 | || type.FullName.StartsWith("System.Collections.Generic.ObjectEqualityComparer`") 129 | || type.FullName.StartsWith("System.Collections.Generic.EnumEqualityComparer`") 130 | || type.FullName.StartsWith("System.Collections.Generic.NullableEqualityComparer`") 131 | || type.FullName == "System.Collections.Generic.ByteEqualityComparer") 132 | { 133 | KnownTypes.TryAdd(type, true); 134 | return true; 135 | } 136 | } 137 | 138 | // classes are always unsafe (we should copy it fully to count references) 139 | if (!type.IsValueType()) 140 | { 141 | KnownTypes.TryAdd(type, false); 142 | return false; 143 | } 144 | 145 | if (processingTypes == null) 146 | processingTypes = new HashSet(); 147 | 148 | // structs cannot have a loops, but check it anyway 149 | processingTypes.Add(type); 150 | 151 | List fi = new List(); 152 | var tp = type; 153 | do 154 | { 155 | fi.AddRange(tp.GetAllFields()); 156 | tp = tp.BaseType(); 157 | } 158 | while (tp != null); 159 | 160 | foreach (var fieldInfo in fi) 161 | { 162 | // type loop 163 | var fieldType = fieldInfo.FieldType; 164 | if (processingTypes.Contains(fieldType)) 165 | continue; 166 | 167 | // not safe and not not safe. we need to go deeper 168 | if (!CanReturnSameType(fieldType, processingTypes)) 169 | { 170 | KnownTypes.TryAdd(type, false); 171 | return false; 172 | } 173 | } 174 | 175 | KnownTypes.TryAdd(type, true); 176 | return true; 177 | } 178 | 179 | // not used anymore 180 | /*/// 181 | /// Classes with only safe fields are safe for ShallowClone (if they root objects for copying) 182 | /// 183 | private static bool CanCopyClassInShallow(Type type) 184 | { 185 | // do not do this anything for struct and arrays 186 | if (!type.IsClass() || type.IsArray) 187 | { 188 | return false; 189 | } 190 | 191 | List fi = new List(); 192 | var tp = type; 193 | do 194 | { 195 | fi.AddRange(tp.GetAllFields()); 196 | tp = tp.BaseType(); 197 | } 198 | while (tp != null); 199 | 200 | if (fi.Any(fieldInfo => !CanReturnSameType(fieldInfo.FieldType, null))) 201 | { 202 | return false; 203 | } 204 | 205 | return true; 206 | }*/ 207 | 208 | public static bool CanReturnSameObject(Type type) 209 | { 210 | return CanReturnSameType(type, null); 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /DeepCloner/Helpers/ReflectionHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | 5 | namespace Force.DeepCloner.Helpers 6 | { 7 | internal static class ReflectionHelper 8 | { 9 | public static bool IsEnum(this Type t) 10 | { 11 | #if NETCORE 12 | return t.GetTypeInfo().IsEnum; 13 | #else 14 | return t.IsEnum; 15 | #endif 16 | } 17 | 18 | public static bool IsValueType(this Type t) 19 | { 20 | #if NETCORE 21 | return t.GetTypeInfo().IsValueType; 22 | #else 23 | return t.IsValueType; 24 | #endif 25 | } 26 | 27 | public static bool IsClass(this Type t) 28 | { 29 | #if NETCORE 30 | return t.GetTypeInfo().IsClass; 31 | #else 32 | return t.IsClass; 33 | #endif 34 | } 35 | 36 | public static Type BaseType(this Type t) 37 | { 38 | #if NETCORE 39 | return t.GetTypeInfo().BaseType; 40 | #else 41 | return t.BaseType; 42 | #endif 43 | } 44 | 45 | public static FieldInfo[] GetAllFields(this Type t) 46 | { 47 | #if NETCORE 48 | return t.GetTypeInfo().DeclaredFields.Where(x => !x.IsStatic).ToArray(); 49 | #else 50 | return t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); 51 | #endif 52 | } 53 | 54 | public static PropertyInfo[] GetPublicProperties(this Type t) 55 | { 56 | #if NETCORE 57 | return t.GetTypeInfo().DeclaredProperties.ToArray(); 58 | #else 59 | return t.GetProperties(BindingFlags.Instance | BindingFlags.Public); 60 | #endif 61 | } 62 | 63 | public static FieldInfo[] GetDeclaredFields(this Type t) 64 | { 65 | #if NETCORE 66 | return t.GetTypeInfo().DeclaredFields.Where(x => !x.IsStatic).ToArray(); 67 | #else 68 | return t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly); 69 | #endif 70 | } 71 | 72 | public static ConstructorInfo[] GetPrivateConstructors(this Type t) 73 | { 74 | #if NETCORE 75 | return t.GetTypeInfo().DeclaredConstructors.ToArray(); 76 | #else 77 | return t.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance); 78 | #endif 79 | } 80 | 81 | public static ConstructorInfo[] GetPublicConstructors(this Type t) 82 | { 83 | #if NETCORE 84 | return t.GetTypeInfo().DeclaredConstructors.ToArray(); 85 | #else 86 | return t.GetConstructors(BindingFlags.Public | BindingFlags.Instance); 87 | #endif 88 | } 89 | 90 | public static MethodInfo GetPrivateMethod(this Type t, string methodName) 91 | { 92 | #if NETCORE 93 | return t.GetTypeInfo().GetDeclaredMethod(methodName); 94 | #else 95 | return t.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance); 96 | #endif 97 | } 98 | 99 | public static MethodInfo GetMethod(this Type t, string methodName) 100 | { 101 | #if NETCORE 102 | return t.GetTypeInfo().GetDeclaredMethod(methodName); 103 | #else 104 | return t.GetMethod(methodName); 105 | #endif 106 | } 107 | 108 | public static MethodInfo GetPrivateStaticMethod(this Type t, string methodName) 109 | { 110 | #if NETCORE 111 | return t.GetTypeInfo().GetDeclaredMethod(methodName); 112 | #else 113 | return t.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static); 114 | #endif 115 | } 116 | 117 | public static FieldInfo GetPrivateField(this Type t, string fieldName) 118 | { 119 | #if NETCORE 120 | return t.GetTypeInfo().GetDeclaredField(fieldName); 121 | #else 122 | return t.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); 123 | #endif 124 | } 125 | 126 | public static FieldInfo GetPrivateStaticField(this Type t, string fieldName) 127 | { 128 | #if NETCORE 129 | return t.GetTypeInfo().GetDeclaredField(fieldName); 130 | #else 131 | return t.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Static); 132 | #endif 133 | } 134 | 135 | #if NETCORE 136 | public static bool IsSubclassOfTypeByName(this Type t, string typeName) 137 | { 138 | while (t != null) 139 | { 140 | if (t.Name == typeName) 141 | return true; 142 | t = t.BaseType(); 143 | } 144 | 145 | return false; 146 | } 147 | #endif 148 | 149 | #if NETCORE 150 | public static bool IsAssignableFrom(this Type from, Type to) 151 | { 152 | return from.GetTypeInfo().IsAssignableFrom(to.GetTypeInfo()); 153 | } 154 | 155 | public static bool IsInstanceOfType(this Type from, object to) 156 | { 157 | return from.IsAssignableFrom(to.GetType()); 158 | } 159 | #endif 160 | 161 | public static Type[] GenericArguments(this Type t) 162 | { 163 | #if NETCORE 164 | return t.GetTypeInfo().GenericTypeArguments; 165 | #else 166 | return t.GetGenericArguments(); 167 | #endif 168 | } 169 | } 170 | } -------------------------------------------------------------------------------- /DeepCloner/Helpers/ShallowClonerGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Force.DeepCloner.Helpers 4 | { 5 | internal static class ShallowClonerGenerator 6 | { 7 | public static T CloneObject(T obj) 8 | { 9 | // this is faster than typeof(T).IsValueType 10 | if (obj is ValueType) 11 | { 12 | if (typeof(T) == obj.GetType()) 13 | return obj; 14 | 15 | // we're here so, we clone value type obj as object type T 16 | // so, we need to copy it, bcs we have a reference, not real object. 17 | return (T)ShallowObjectCloner.CloneObject(obj); 18 | } 19 | 20 | if (ReferenceEquals(obj, null)) 21 | return (T)(object)null; 22 | 23 | if (DeepClonerSafeTypes.CanReturnSameObject(obj.GetType())) 24 | return obj; 25 | 26 | return (T)ShallowObjectCloner.CloneObject(obj); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /DeepCloner/Helpers/ShallowObjectCloner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | using System.Reflection.Emit; 5 | 6 | namespace Force.DeepCloner.Helpers 7 | { 8 | /// 9 | /// Internal class but due implementation restriction should be public 10 | /// 11 | public abstract class ShallowObjectCloner 12 | { 13 | /// 14 | /// Abstract method for real object cloning 15 | /// 16 | protected abstract object DoCloneObject(object obj); 17 | 18 | private static readonly ShallowObjectCloner _unsafeInstance; 19 | 20 | private static ShallowObjectCloner _instance; 21 | 22 | /// 23 | /// Performs real shallow object clone 24 | /// 25 | public static object CloneObject(object obj) 26 | { 27 | return _instance.DoCloneObject(obj); 28 | } 29 | 30 | internal static bool IsSafeVariant() 31 | { 32 | return _instance is ShallowSafeObjectCloner; 33 | } 34 | 35 | static ShallowObjectCloner() 36 | { 37 | #if !NETCORE 38 | _unsafeInstance = GenerateUnsafeCloner(); 39 | _instance = _unsafeInstance; 40 | try 41 | { 42 | _instance.DoCloneObject(new object()); 43 | } 44 | catch (Exception) 45 | { 46 | // switching to safe 47 | _instance = new ShallowSafeObjectCloner(); 48 | } 49 | #else 50 | _instance = new ShallowSafeObjectCloner(); 51 | // no unsafe variant for core 52 | _unsafeInstance = _instance; 53 | #endif 54 | } 55 | 56 | /// 57 | /// Purpose of this method is testing variants 58 | /// 59 | internal static void SwitchTo(bool isSafe) 60 | { 61 | DeepClonerCache.ClearCache(); 62 | if (isSafe) _instance = new ShallowSafeObjectCloner(); 63 | else _instance = _unsafeInstance; 64 | } 65 | 66 | #if !NETCORE 67 | private static ShallowObjectCloner GenerateUnsafeCloner() 68 | { 69 | var mb = TypeCreationHelper.GetModuleBuilder(); 70 | 71 | var builder = mb.DefineType("ShallowSafeObjectClonerImpl", TypeAttributes.Public, typeof(ShallowObjectCloner)); 72 | var ctorBuilder = builder.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis | CallingConventions.HasThis, Type.EmptyTypes); 73 | 74 | var cil = ctorBuilder.GetILGenerator(); 75 | cil.Emit(OpCodes.Ldarg_0); 76 | // ReSharper disable AssignNullToNotNullAttribute 77 | cil.Emit(OpCodes.Call, typeof(ShallowObjectCloner).GetPrivateConstructors()[0]); 78 | // ReSharper restore AssignNullToNotNullAttribute 79 | cil.Emit(OpCodes.Ret); 80 | 81 | var methodBuilder = builder.DefineMethod( 82 | "DoCloneObject", 83 | MethodAttributes.Public | MethodAttributes.Virtual, 84 | CallingConventions.HasThis, 85 | typeof(object), 86 | new[] { typeof(object) }); 87 | 88 | var il = methodBuilder.GetILGenerator(); 89 | il.Emit(OpCodes.Ldarg_1); 90 | il.Emit(OpCodes.Call, typeof(object).GetPrivateMethod("MemberwiseClone")); 91 | il.Emit(OpCodes.Ret); 92 | var type = builder.CreateType(); 93 | return (ShallowObjectCloner)Activator.CreateInstance(type); 94 | } 95 | #endif 96 | 97 | private class ShallowSafeObjectCloner : ShallowObjectCloner 98 | { 99 | private static readonly Func _cloneFunc; 100 | 101 | static ShallowSafeObjectCloner() 102 | { 103 | var methodInfo = typeof(object).GetPrivateMethod("MemberwiseClone"); 104 | var p = Expression.Parameter(typeof(object)); 105 | var mce = Expression.Call(p, methodInfo); 106 | _cloneFunc = Expression.Lambda>(mce, p).Compile(); 107 | } 108 | 109 | protected override object DoCloneObject(object obj) 110 | { 111 | return _cloneFunc(obj); 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /DeepCloner/Helpers/TypeCreationHelper.cs: -------------------------------------------------------------------------------- 1 | #if !NETCORE 2 | using System; 3 | using System.Reflection; 4 | using System.Reflection.Emit; 5 | 6 | namespace Force.DeepCloner.Helpers 7 | { 8 | internal static class TypeCreationHelper 9 | { 10 | private static ModuleBuilder _moduleBuilder; 11 | 12 | internal static ModuleBuilder GetModuleBuilder() 13 | { 14 | // todo: think about multithread 15 | if (_moduleBuilder == null) 16 | { 17 | AssemblyName aName = new AssemblyName("DeepClonerCode"); 18 | var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(aName, AssemblyBuilderAccess.Run); 19 | var mb = ab.DefineDynamicModule(aName.Name); 20 | _moduleBuilder = mb; 21 | } 22 | 23 | return _moduleBuilder; 24 | } 25 | } 26 | } 27 | #endif -------------------------------------------------------------------------------- /DeepCloner/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("DeepCloner")] 8 | [assembly: AssemblyDescription("Library for cloning objects")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("Force")] 11 | [assembly: AssemblyProduct("DeepCloner")] 12 | [assembly: AssemblyCopyright("Copyright © Force 2016-2022")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("2e4d0d08-2858-4797-bb4c-9b700601a8c0")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | 34 | [assembly: AssemblyVersion("0.10.0.0")] // change this value only when api is changing 35 | [assembly: AssemblyFileVersion("0.10.4.0")] 36 | [assembly: AssemblyInformationalVersion("0.10.4.0")] 37 | 38 | #if BUILDCORE 39 | // [assembly: AssemblyKeyFileAttribute("..\\public.snk")] 40 | // [assembly: AssemblyDelaySign(true)] 41 | #endif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 force 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DeepCloner 2 | 3 | Library with extenstion to clone objects for .NET. It can deep or shallow copy objects. In deep cloning all object graph is maintained. Library actively uses code-generation in runtime as result object cloning is blazingly fast. 4 | Also, there are some performance tricks to increase cloning speed (see tests below). 5 | Objects are copied by its' internal structure, **no** methods or constructuctors are called for cloning objects. As result, you can copy **any** object, but we don't recommend to copy objects which are binded to native resources or pointers. It can cause unpredictable results (but object will be cloned). 6 | 7 | You don't need to mark objects somehow, like Serializable-attribute, or restrict to specific interface. Absolutely any object can be cloned by this library. And this object doesn't have any ability to determine that he is clone (except with very specific methods). 8 | 9 | Also, there is no requirement to specify object type for cloning. Object can be casted to inteface or as an abstract object, you can clone array of ints as abstract Array or IEnumerable, even null can be cloned without any errors. 10 | 11 | Installation through Nuget: 12 | 13 | ``` 14 | Install-Package DeepCloner 15 | ``` 16 | 17 | 18 | ## Supported Frameworks 19 | 20 | DeepCloner works for .NET 4.0 or higher or for .NET Standard 1.3 (.NET Core). .NET Standard version implements only Safe copying variant (slightly slower than standard, see Benchmarks). 21 | 22 | ## Limitation 23 | 24 | Library requires Full Trust permission set or Reflection permission (MemberAccess). It prefers Full Trust, but if code lacks of this variant, library seamlessly switchs to slighlty slower but safer variant. 25 | 26 | If your code is on very limited permission set, you can try to use another library, e.g. [CloneExtensions](https://github.com/MarcinJuraszek/CloneExtensions). It clones only public properties of objects, so, result can differ, but should work better (it requires only RestrictedMemberAccess permission). 27 | 28 | ## Usage 29 | 30 | Deep cloning any object: 31 | ``` 32 | var clone = new { Id = 1, Name = "222" }.DeepClone(); 33 | ``` 34 | 35 | With a reference to same object: 36 | ``` 37 | // public class Tree { public Tree ParentTree; } 38 | var t = new Tree(); 39 | t.ParentTree = t; 40 | var cloned = t.DeepClone(); 41 | Console.WriteLine(cloned.ParentTree == cloned); // True 42 | ``` 43 | 44 | Or as object: 45 | ``` 46 | var date = DateTime.Now; 47 | var obj = (object)date; 48 | obj.DeepClone().GetType(); // DateTime 49 | ``` 50 | 51 | Shallow cloning (clone only same object, not objects that object relate to) 52 | ``` 53 | var clone = new { Id = 1, Name = "222" }.ShallowClone(); 54 | ``` 55 | 56 | Cloning to existing object (can be useful for _copying_ constructors, creating wrappers or for keeping references to same object) 57 | ``` 58 | public class Derived : BaseClass 59 | { 60 | public Derived(BaseClass parent) 61 | { 62 | parent.DeepCloneTo(this); // now this has every field from parent 63 | } 64 | } 65 | ``` 66 | Please, note, that _DeepCloneTo_ and _ShallowCloneTo_ requre that object should be class (it is useless for structures) and derived class must be real descendant of parent class (or same type). In another words, this code will not work: 67 | ``` 68 | public class Base {} 69 | public class Derived1 : Base {} 70 | public class Derived2 : Base {} 71 | 72 | var b = (Base)new Derived1(); // casting derived to parent 73 | var derived2 = new Derived2(); 74 | // will compile, but will throw an exception in runtime, Derived1 is not parent for Derived2 75 | b.DeepCloneTo(derived2); 76 | 77 | ``` 78 | 79 | ## Installation 80 | 81 | Through nuget: 82 | ``` 83 | Install-Package DeepCloner 84 | ``` 85 | 86 | ## Details 87 | 88 | You can use deep clone of objects for a lot of situations, e.g.: 89 | * Emulation of external service or _deserialization elimination_ (e.g. in Unit Testing). When code has received object from external source, code can change it (because object for code is *own*). 90 | * ReadOnly object replace. Instead of wrapping your object to readonly object, you can clone object and target code can do anything with it without any restriction. 91 | * Caching. You can cache data locally and want to ensurce that cached object hadn't been changed by other code 92 | 93 | You can use shallow clone as fast, light version of deep clone (if your situation allows that). Main difference between deep and shallow clone in code below: 94 | ``` 95 | // public class A { public B B; } 96 | // public class B { public int X; } 97 | var b = new B { X = 1 }; 98 | var a = new A { B = b }; 99 | var deepClone = a.DeepClone(); 100 | deepClone.B.X = 2; 101 | Console.WriteLine(a.B.X); // 1 102 | var shallowClone = a.ShallowClone(); 103 | shallowClone.B.X = 2; 104 | Console.WriteLine(a.B.X); // 2 105 | ``` 106 | So, deep cloning is guarantee that all changes of cloned object does not affect original. Shallow clone does not guarantee this. But it faster, because deep clone of object can copy big graph of related objects and related objects of related objects and related related related objects, and... so on... 107 | 108 | This library does not call any method of cloning object: constructors, Equals, GetHashCode, propertes - nothing is called. So, it is impossible for cloning object to receive information about cloning, throw an exception or return invalid data. 109 | If you need to call some methods after cloning, you can wrap cloning call to another method which will perform required actions. 110 | 111 | Extension methods in library are generic, but it is not require to specifify type for cloning. You can cast your objects to System.Object, or to an interface, add fields will be carefully copied to new object. 112 | 113 | ### Performance 114 | Cloning Speed can vary on many factors. This library contains some optimizations, e.g. structs are just copied, arrays also can be copied through Array.Copy if possible. So, real performance will depend on structure of your object. 115 | 116 | Tables below, just for information. Simple object with some fields is cloned multiple times. Preparation time (only affect first execution) excluded from tests. 117 | 118 | Example of object 119 | ``` 120 | var c = new C1 { V1 = 1, O = new object(), V2 = "xxx" }; 121 | var c1 = new C1Complex { C1 = c, Guid = Guid.NewGuid(), O = new object(), V1 = 42, V2 = "some test string", Array = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 } }; 122 | ``` 123 | 124 | 125 | **Deep cloning** 126 | 127 | Method | Time per object (ns) | Comments 128 | ---|---|--- 129 | Manual | 50 | You should manually realize cloning. It requires a lot of work and can cause copy-paste errors, but it is fastest variant 130 | DeepClone / Unsafe | 570 | This variant is really slower than manual, but clones any object without preparation 131 | DeepClone / Safe | 760 | Safe variant based on on expressions 132 | [CloneExtensions](https://github.com/MarcinJuraszek/CloneExtensions) | 1800 | Implementation of cloning objects on expression trees. 133 | [NClone](https://github.com/mijay/NClone) | 2890 | Not analyzed carefully, but author says that lib has a problem with a cyclic dependencies 134 | [Clone.Behave!](https://github.com/kalisohn/CloneBehave) | 41890 | Very slow, also has a dependency to fasterflect 135 | [GeorgeCloney](https://github.com/laazyj/GeorgeCloney) | 6420 | Has a lot limitations and prefers to clone through BinaryFormatter 136 | [Nuclex.Cloning](https://github.com/junweilee/Nuclex.Cloning/) | n/a | Crashed with a null reference exception 137 | [.Net Object FastDeepCloner](https://github.com/Alenah091/FastDeepCloner/) | 15030 | Not analyzed carefully, only for .NET 4.5.1 or higher 138 | [DesertOctopus](https://github.com/nowol/DesertOctopus) | 1700 | Not analyzed. Only for .NET 4.5.2 or higher 139 | BinaryFormatter | 49100 | 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 140 | 141 | **Shallow cloning** 142 | Shallow cloning is usually faster, because we no need to calculate references and clone additional objects. 143 | 144 | Method | Time per object (ns) | Comments 145 | ---|---|--- 146 | Manual | 16 | You should manually realize clone, property by property, field by field. Fastest variant 147 | Manual / MemberwiseClone | 46 | Fast variant to clone: call MemberwiseClone inside your class. Should be done manually, but does not require a lot of work. 148 | ShallowClone / Unsafe | 64 | Slightly slower than MemberwiseClone due checks for nulls and object types 149 | ShallowClone / Safe | 64 | Safe variant based on expressions 150 | [CloneExtensions](https://github.com/MarcinJuraszek/CloneExtensions) | 125 | Implementation of cloning objects on expression trees. 151 | [Nuclex.Cloning](https://github.com/junweilee/Nuclex.Cloning/) | 2498 | Looks like interesting expression-based implementation with a some caching, but unexpectedly very slow 152 | 153 | ## Performance tricks 154 | 155 | We perform a lot of performance tricks to ensure cloning is really fast. Here is some of them: 156 | 157 | * Using a shallow cloning instead of deep cloning if object is safe for this operation 158 | * Copying an whole object and updating only required fields 159 | * Special handling for structs (can be copied without any cloning code, if possible) 160 | * Cloners caching 161 | * Optimizations for copying simple objects (reduced number of checks to ensure good performance) 162 | * Special handling of reference count for simple objects, that is faster than default dictionary 163 | * Constructors analyzing to select best variant of object construction 164 | * Direct copying of arrays if possible 165 | * Custom handling of one-dimensional and two-dimensional zero-based arrays (most of arrays in usual code) 166 | 167 | 168 | ## License 169 | 170 | [MIT](https://github.com/force-net/DeepCloner/blob/develop/LICENSE) license 171 | -------------------------------------------------------------------------------- /deepcloner-nuget-ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/force-net/DeepCloner/da61ac691905bd4bea302f548f42520f670ea667/deepcloner-nuget-ico.png -------------------------------------------------------------------------------- /pack.cmd: -------------------------------------------------------------------------------- 1 | dotnet build -c BuildCore DeepCloner\DeepCloner.Core.csproj 2 | "C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\sn.exe" -R DeepCloner\bin\BuildCore\net40\DeepCloner.dll private.snk 3 | "C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\sn.exe" -R DeepCloner\bin\BuildCore\netstandard1.3\DeepCloner.dll private.snk 4 | .tools\nuget.exe pack 5 | xcopy *.nupkg .tools 6 | del *.nupkg 7 | 8 | -------------------------------------------------------------------------------- /public.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/force-net/DeepCloner/da61ac691905bd4bea302f548f42520f670ea667/public.snk --------------------------------------------------------------------------------