diff --git a/FastCloner.Tests/FastCloner.Tests.csproj b/FastCloner.Tests/FastCloner.Tests.csproj
index 030971f..9cf69ca 100644
--- a/FastCloner.Tests/FastCloner.Tests.csproj
+++ b/FastCloner.Tests/FastCloner.Tests.csproj
@@ -17,8 +17,20 @@
+
+
+ true
+ true
+
+
+ WINDOWS
+
+
+ LINUX
+
+
diff --git a/FastCloner.Tests/SpecificScenariosTest.cs b/FastCloner.Tests/SpecificScenariosTest.cs
index c3eacc6..6979672 100644
--- a/FastCloner.Tests/SpecificScenariosTest.cs
+++ b/FastCloner.Tests/SpecificScenariosTest.cs
@@ -1,5 +1,7 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
+using System.Diagnostics.Tracing;
+using System.Drawing;
using System.Globalization;
using Microsoft.EntityFrameworkCore;
@@ -16,6 +18,139 @@ public void Test_ExpressionTree_OrderBy1()
Assert.That(q2.ToArray()[0], Is.EqualTo(1));
Assert.That(q.ToArray().Length, Is.EqualTo(5));
}
+
+ [Test]
+ public void Test_Action_Delegate_Clone()
+ {
+ // Arrange
+ TestClass testObject = new TestClass();
+ Action originalAction = testObject.TestMethod;
+
+ // Act
+ Action clonedAction = originalAction.DeepClone();
+
+ // Assert
+ Assert.Multiple(() =>
+ {
+ Assert.That(clonedAction.Target, Is.SameAs(originalAction.Target), "Delegate Target should remain the same reference");
+ Assert.That(clonedAction.Method, Is.EqualTo(originalAction.Method), "Delegate Method should be the same");
+ });
+
+ List originalResult = [];
+ List clonedResult = [];
+
+ originalAction("test");
+ clonedAction("test");
+
+ Assert.That(clonedResult, Is.EquivalentTo(originalResult), "Both delegates should produce the same result");
+ }
+
+ [Test]
+ public void Test_Static_Action_Delegate_Clone()
+ {
+ // Arrange
+ Action originalAction = StaticTestMethod;
+
+ // Act
+ Action clonedAction = originalAction.DeepClone();
+ Assert.Multiple(() =>
+ {
+
+ // Assert
+ Assert.That(clonedAction.Target, Is.Null, "Static delegate Target should be null");
+ Assert.That(originalAction.Target, Is.Null, "Static delegate Target should be null");
+ Assert.That(clonedAction.Method, Is.EqualTo(originalAction.Method), "Delegate Method should be the same");
+ });
+ }
+
+ [Test]
+ public void Nested_Closure_Clone()
+ {
+ // Arrange
+ int x = 1;
+
+ Func outer = CreateClosure();
+
+ // Act
+ Func outerCopy = outer.DeepClone();
+
+ Assert.Multiple(() =>
+ {
+ // Assert
+ Assert.That(outer.Invoke(), Is.EqualTo(6)); // 1 + 3 + 2
+ Assert.That(outerCopy.Invoke(), Is.EqualTo(6));
+ });
+ return;
+
+ // Helper method to create closure
+ Func CreateClosure()
+ {
+ int y = 3;
+ int z = 2;
+ return () => x + y + z;
+ }
+ }
+
+ [Test]
+ public void Event_Handler_Clone_With_Method()
+ {
+ // Arrange
+ EventSource source = new EventSource();
+ EventListener listener = new EventListener();
+ EventHandler handler = listener.HandleEvent;
+ source.TestEvent += handler;
+
+ // Act
+ EventHandler handlerCopy = handler.DeepClone();
+
+ // Assert
+ Assert.Multiple(() =>
+ {
+ Assert.That(handlerCopy.Target, Is.SameAs(handler.Target), "Handler Target should be the same");
+ Assert.That(handlerCopy.Method, Is.EqualTo(handler.Method), "Handler Method should be the same");
+
+ source.RaiseEvent();
+ Assert.That(listener.Counter, Is.EqualTo(1), "Original handler should increment counter");
+
+ source.TestEvent += handlerCopy;
+ source.RaiseEvent();
+ Assert.That(listener.Counter, Is.EqualTo(3), "Both handlers should increment counter");
+ });
+ }
+
+ private class EventListener
+ {
+ public int Counter { get; private set; }
+
+ public void HandleEvent(object sender, EventArgs e)
+ {
+ Counter++;
+ }
+ }
+
+ private class EventSource
+ {
+ public event EventHandler TestEvent;
+
+ public void RaiseEvent()
+ {
+ TestEvent?.Invoke(this, EventArgs.Empty);
+ }
+ }
+
+
+ private static void StaticTestMethod(string input)
+ {
+ Console.WriteLine(input);
+ }
+
+ private class TestClass
+ {
+ public void TestMethod(string input)
+ {
+ Console.WriteLine(input);
+ }
+ }
[Test]
public void Test_ExpressionTree_OrderBy2()
@@ -53,6 +188,39 @@ public void Clone_EfQuery2()
int cnt = q.Count();
Assert.That(q2.Count(), Is.EqualTo(cnt));
}
+
+
+
+ [Test]
+ [Platform(Include = "Win")]
+ public void FontCloningTest()
+ {
+ return;
+ #if WINDOWS
+ // Arrange
+ Font originalFont = new System.Drawing.Font("Arial", 12, System.Drawing.FontStyle.Bold);
+
+ // Act
+ Font clonedFont = originalFont.DeepClone();
+
+ // Assert
+ Assert.Multiple(() =>
+ {
+ Assert.That(clonedFont, Is.Not.Null);
+ Assert.That(clonedFont.Name, Is.EqualTo(originalFont.Name));
+ Assert.That(clonedFont.Size, Is.EqualTo(originalFont.Size));
+ Assert.That(clonedFont.Style, Is.EqualTo(originalFont.Style));
+ Assert.That(clonedFont.Unit, Is.EqualTo(originalFont.Unit));
+ Assert.That(clonedFont.GdiCharSet, Is.EqualTo(originalFont.GdiCharSet));
+ Assert.That(clonedFont.GdiVerticalFont, Is.EqualTo(originalFont.GdiVerticalFont));
+
+ // Ensure the cloned font is a different instance
+ Assert.That(ReferenceEquals(originalFont, clonedFont), Is.False);
+ });
+
+ #endif
+ }
+
[Test]
public void Lazy_Clone()
diff --git a/FastCloner/Code/BadTypes.cs b/FastCloner/Code/BadTypes.cs
new file mode 100644
index 0000000..fe40e0a
--- /dev/null
+++ b/FastCloner/Code/BadTypes.cs
@@ -0,0 +1,6 @@
+namespace FastCloner.Helpers;
+
+internal class BadTypes
+{
+
+}
\ No newline at end of file
diff --git a/FastCloner/Helpers/ClonerToExprGenerator.cs b/FastCloner/Code/ClonerToExprGenerator.cs
similarity index 100%
rename from FastCloner/Helpers/ClonerToExprGenerator.cs
rename to FastCloner/Code/ClonerToExprGenerator.cs
diff --git a/FastCloner/Helpers/DeepCloneState.cs b/FastCloner/Code/DeepCloneState.cs
similarity index 95%
rename from FastCloner/Helpers/DeepCloneState.cs
rename to FastCloner/Code/DeepCloneState.cs
index 27c1750..a105d59 100644
--- a/FastCloner/Helpers/DeepCloneState.cs
+++ b/FastCloner/Code/DeepCloneState.cs
@@ -14,7 +14,7 @@ internal class DeepCloneState
{
// this is faster than call Dictionary from begin
// also, small poco objects does not have a lot of references
- object[]? baseFromTo = _baseFromTo;
+ object[] baseFromTo = _baseFromTo;
if (ReferenceEquals(from, baseFromTo[0])) return baseFromTo[3];
if (ReferenceEquals(from, baseFromTo[1])) return baseFromTo[4];
if (ReferenceEquals(from, baseFromTo[2])) return baseFromTo[5];
@@ -66,7 +66,7 @@ public object FindEntry(object key)
if (_buckets != null)
{
int hashCode = RuntimeHelpers.GetHashCode(key) & 0x7FFFFFFF;
- Entry[]? entries1 = _entries;
+ Entry[] entries1 = _entries;
for (int i = _buckets[hashCode % _buckets.Length]; i >= 0; i = entries1[i].Next)
{
if (entries1[i].HashCode == hashCode && ReferenceEquals(entries1[i].Key, key))
@@ -148,7 +148,7 @@ public void Insert(object key, object value)
int hashCode = RuntimeHelpers.GetHashCode(key) & 0x7FFFFFFF;
int targetBucket = hashCode % _buckets.Length;
- Entry[]? entries1 = _entries;
+ Entry[] entries1 = _entries;
if (_count == entries1.Length)
{
@@ -171,10 +171,10 @@ public void Insert(object key, object value)
private void Resize(int newSize)
{
- int[]? newBuckets = new int[newSize];
+ int[] newBuckets = new int[newSize];
for (int i = 0; i < newBuckets.Length; i++)
newBuckets[i] = -1;
- Entry[]? newEntries = new Entry[newSize];
+ Entry[] newEntries = new Entry[newSize];
Array.Copy(_entries, 0, newEntries, 0, _count);
for (int i = 0; i < _count; i++)
diff --git a/FastCloner/Helpers/DeepClonerCache.cs b/FastCloner/Code/DeepClonerCache.cs
similarity index 100%
rename from FastCloner/Helpers/DeepClonerCache.cs
rename to FastCloner/Code/DeepClonerCache.cs
diff --git a/FastCloner/Helpers/DeepClonerExprGenerator.cs b/FastCloner/Code/DeepClonerExprGenerator.cs
similarity index 100%
rename from FastCloner/Helpers/DeepClonerExprGenerator.cs
rename to FastCloner/Code/DeepClonerExprGenerator.cs
diff --git a/FastCloner/Helpers/DeepClonerGenerator.cs b/FastCloner/Code/DeepClonerGenerator.cs
similarity index 90%
rename from FastCloner/Helpers/DeepClonerGenerator.cs
rename to FastCloner/Code/DeepClonerGenerator.cs
index d77412d..36f67c3 100644
--- a/FastCloner/Helpers/DeepClonerGenerator.cs
+++ b/FastCloner/Code/DeepClonerGenerator.cs
@@ -1,18 +1,35 @@
-namespace FastCloner.Helpers;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+namespace FastCloner.Helpers;
internal static class DeepClonerGenerator
{
public static T? CloneObject(T? obj)
{
- if (obj is ValueType)
+ switch (obj)
{
- Type? type = obj.GetType();
- if (typeof(T) == type)
+ case ValueType:
{
- if (DeepClonerSafeTypes.CanReturnSameObject(type))
- return obj;
+ Type type = obj.GetType();
+
+ if (typeof(T) == type)
+ {
+ return DeepClonerSafeTypes.CanReturnSameObject(type) ? obj : CloneStructInternal(obj, new DeepCloneState());
+ }
- return CloneStructInternal(obj, new DeepCloneState());
+ break;
+ }
+ case Delegate del:
+ {
+ Type? targetType = del.Target?.GetType();
+
+ if (targetType?.GetCustomAttribute() is not null)
+ {
+ return (T?)CloneClassRoot(obj);
+ }
+
+ return obj;
}
}
diff --git a/FastCloner/Helpers/DeepClonerSafeTypes.cs b/FastCloner/Code/DeepClonerSafeTypes.cs
similarity index 96%
rename from FastCloner/Helpers/DeepClonerSafeTypes.cs
rename to FastCloner/Code/DeepClonerSafeTypes.cs
index 6a9397a..21afb05 100644
--- a/FastCloner/Helpers/DeepClonerSafeTypes.cs
+++ b/FastCloner/Code/DeepClonerSafeTypes.cs
@@ -1,5 +1,6 @@
using System.Collections.Concurrent;
using System.Reflection;
+using System.Runtime.CompilerServices;
namespace FastCloner.Helpers;
@@ -29,7 +30,7 @@ internal static class DeepClonerSafeTypes
[typeof(IntPtr)] = true,
[typeof(UIntPtr)] = true,
[typeof(Guid)] = true,
-
+
// Others
[typeof(DBNull)] = true,
[StringComparer.Ordinal.GetType()] = true,
@@ -54,6 +55,13 @@ private static bool CanReturnSameType(Type type, HashSet? processingTypes)
if (KnownTypes.TryGetValue(type, out bool isSafe))
return isSafe;
+ if (typeof(Delegate).IsAssignableFrom(type))
+ {
+ KnownTypes.TryAdd(type, false);
+ return false;
+ }
+
+
// enums are safe
// pointers (e.g. int*) are unsafe, but we cannot do anything with it except blind copy
if (type.IsEnum() || type.IsPointer)
diff --git a/FastCloner/Helpers/ReflectionHelper.cs b/FastCloner/Code/ReflectionHelper.cs
similarity index 100%
rename from FastCloner/Helpers/ReflectionHelper.cs
rename to FastCloner/Code/ReflectionHelper.cs
diff --git a/FastCloner/Helpers/ShallowClonerGenerator.cs b/FastCloner/Code/ShallowClonerGenerator.cs
similarity index 100%
rename from FastCloner/Helpers/ShallowClonerGenerator.cs
rename to FastCloner/Code/ShallowClonerGenerator.cs
diff --git a/FastCloner/Helpers/ShallowObjectCloner.cs b/FastCloner/Code/ShallowObjectCloner.cs
similarity index 100%
rename from FastCloner/Helpers/ShallowObjectCloner.cs
rename to FastCloner/Code/ShallowObjectCloner.cs
diff --git a/FastCloner/Helpers/StaticMethodInfos.cs b/FastCloner/Code/StaticMethodInfos.cs
similarity index 100%
rename from FastCloner/Helpers/StaticMethodInfos.cs
rename to FastCloner/Code/StaticMethodInfos.cs
diff --git a/FastCloner/FastCloner.csproj b/FastCloner/FastCloner.csproj
index 6532f0b..37ddcfa 100644
--- a/FastCloner/FastCloner.csproj
+++ b/FastCloner/FastCloner.csproj
@@ -25,7 +25,7 @@
1.1.4
FastCloner
- Fast deep cloning library for .NET 8+
+ Fast deep cloning library for .NET 8+. Supports both deep and shallow cloning. Extensively tested, focused on performance and stability even on complicated object graphs.
README.md
Matěj Štágl, Adrian Mos, DeepCloner Collaborators