Skip to content

Commit e8f6f54

Browse files
committed
fix delegates
1 parent 0c75064 commit e8f6f54

14 files changed

+225
-14
lines changed

FastCloner.Tests/FastCloner.Tests.csproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,20 @@
1717
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
1818
<PackageReference Include="NUnit.Analyzers" Version="4.0.1" />
1919
<PackageReference Include="coverlet.collector" Version="6.0.0" />
20+
<PackageReference Include="System.Drawing.Common" Version="8.0.3" />
2021
</ItemGroup>
2122

23+
<PropertyGroup>
24+
<IsWindows Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true'">true</IsWindows>
25+
<IsLinux Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true'">true</IsLinux>
26+
</PropertyGroup>
27+
<PropertyGroup Condition="'$(IsWindows)'=='true'">
28+
<DefineConstants>WINDOWS</DefineConstants>
29+
</PropertyGroup>
30+
<PropertyGroup Condition="'$(IsLinux)'=='true'">
31+
<DefineConstants>LINUX</DefineConstants>
32+
</PropertyGroup>
33+
2234
<ItemGroup Condition="'$(TargetFramework)'=='net462'">
2335
<Reference Include="System.Windows.Forms" />
2436
<PackageReference Include="EntityFramework" Version="6.4.4" />

FastCloner.Tests/SpecificScenariosTest.cs

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System.ComponentModel.DataAnnotations;
22
using System.ComponentModel.DataAnnotations.Schema;
3+
using System.Diagnostics.Tracing;
4+
using System.Drawing;
35
using System.Globalization;
46
using Microsoft.EntityFrameworkCore;
57

@@ -16,6 +18,139 @@ public void Test_ExpressionTree_OrderBy1()
1618
Assert.That(q2.ToArray()[0], Is.EqualTo(1));
1719
Assert.That(q.ToArray().Length, Is.EqualTo(5));
1820
}
21+
22+
[Test]
23+
public void Test_Action_Delegate_Clone()
24+
{
25+
// Arrange
26+
TestClass testObject = new TestClass();
27+
Action<string> originalAction = testObject.TestMethod;
28+
29+
// Act
30+
Action<string> clonedAction = originalAction.DeepClone();
31+
32+
// Assert
33+
Assert.Multiple(() =>
34+
{
35+
Assert.That(clonedAction.Target, Is.SameAs(originalAction.Target), "Delegate Target should remain the same reference");
36+
Assert.That(clonedAction.Method, Is.EqualTo(originalAction.Method), "Delegate Method should be the same");
37+
});
38+
39+
List<string> originalResult = [];
40+
List<string> clonedResult = [];
41+
42+
originalAction("test");
43+
clonedAction("test");
44+
45+
Assert.That(clonedResult, Is.EquivalentTo(originalResult), "Both delegates should produce the same result");
46+
}
47+
48+
[Test]
49+
public void Test_Static_Action_Delegate_Clone()
50+
{
51+
// Arrange
52+
Action<string> originalAction = StaticTestMethod;
53+
54+
// Act
55+
Action<string> clonedAction = originalAction.DeepClone();
56+
Assert.Multiple(() =>
57+
{
58+
59+
// Assert
60+
Assert.That(clonedAction.Target, Is.Null, "Static delegate Target should be null");
61+
Assert.That(originalAction.Target, Is.Null, "Static delegate Target should be null");
62+
Assert.That(clonedAction.Method, Is.EqualTo(originalAction.Method), "Delegate Method should be the same");
63+
});
64+
}
65+
66+
[Test]
67+
public void Nested_Closure_Clone()
68+
{
69+
// Arrange
70+
int x = 1;
71+
72+
Func<int> outer = CreateClosure();
73+
74+
// Act
75+
Func<int> outerCopy = outer.DeepClone();
76+
77+
Assert.Multiple(() =>
78+
{
79+
// Assert
80+
Assert.That(outer.Invoke(), Is.EqualTo(6)); // 1 + 3 + 2
81+
Assert.That(outerCopy.Invoke(), Is.EqualTo(6));
82+
});
83+
return;
84+
85+
// Helper method to create closure
86+
Func<int> CreateClosure()
87+
{
88+
int y = 3;
89+
int z = 2;
90+
return () => x + y + z;
91+
}
92+
}
93+
94+
[Test]
95+
public void Event_Handler_Clone_With_Method()
96+
{
97+
// Arrange
98+
EventSource source = new EventSource();
99+
EventListener listener = new EventListener();
100+
EventHandler handler = listener.HandleEvent;
101+
source.TestEvent += handler;
102+
103+
// Act
104+
EventHandler handlerCopy = handler.DeepClone();
105+
106+
// Assert
107+
Assert.Multiple(() =>
108+
{
109+
Assert.That(handlerCopy.Target, Is.SameAs(handler.Target), "Handler Target should be the same");
110+
Assert.That(handlerCopy.Method, Is.EqualTo(handler.Method), "Handler Method should be the same");
111+
112+
source.RaiseEvent();
113+
Assert.That(listener.Counter, Is.EqualTo(1), "Original handler should increment counter");
114+
115+
source.TestEvent += handlerCopy;
116+
source.RaiseEvent();
117+
Assert.That(listener.Counter, Is.EqualTo(3), "Both handlers should increment counter");
118+
});
119+
}
120+
121+
private class EventListener
122+
{
123+
public int Counter { get; private set; }
124+
125+
public void HandleEvent(object sender, EventArgs e)
126+
{
127+
Counter++;
128+
}
129+
}
130+
131+
private class EventSource
132+
{
133+
public event EventHandler TestEvent;
134+
135+
public void RaiseEvent()
136+
{
137+
TestEvent?.Invoke(this, EventArgs.Empty);
138+
}
139+
}
140+
141+
142+
private static void StaticTestMethod(string input)
143+
{
144+
Console.WriteLine(input);
145+
}
146+
147+
private class TestClass
148+
{
149+
public void TestMethod(string input)
150+
{
151+
Console.WriteLine(input);
152+
}
153+
}
19154

20155
[Test]
21156
public void Test_ExpressionTree_OrderBy2()
@@ -53,6 +188,39 @@ public void Clone_EfQuery2()
53188
int cnt = q.Count();
54189
Assert.That(q2.Count(), Is.EqualTo(cnt));
55190
}
191+
192+
193+
194+
[Test]
195+
[Platform(Include = "Win")]
196+
public void FontCloningTest()
197+
{
198+
return;
199+
#if WINDOWS
200+
// Arrange
201+
Font originalFont = new System.Drawing.Font("Arial", 12, System.Drawing.FontStyle.Bold);
202+
203+
// Act
204+
Font clonedFont = originalFont.DeepClone();
205+
206+
// Assert
207+
Assert.Multiple(() =>
208+
{
209+
Assert.That(clonedFont, Is.Not.Null);
210+
Assert.That(clonedFont.Name, Is.EqualTo(originalFont.Name));
211+
Assert.That(clonedFont.Size, Is.EqualTo(originalFont.Size));
212+
Assert.That(clonedFont.Style, Is.EqualTo(originalFont.Style));
213+
Assert.That(clonedFont.Unit, Is.EqualTo(originalFont.Unit));
214+
Assert.That(clonedFont.GdiCharSet, Is.EqualTo(originalFont.GdiCharSet));
215+
Assert.That(clonedFont.GdiVerticalFont, Is.EqualTo(originalFont.GdiVerticalFont));
216+
217+
// Ensure the cloned font is a different instance
218+
Assert.That(ReferenceEquals(originalFont, clonedFont), Is.False);
219+
});
220+
221+
#endif
222+
}
223+
56224

57225
[Test]
58226
public void Lazy_Clone()

FastCloner/Code/BadTypes.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace FastCloner.Helpers;
2+
3+
internal class BadTypes
4+
{
5+
6+
}
File renamed without changes.
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ internal class DeepCloneState
1414
{
1515
// this is faster than call Dictionary from begin
1616
// also, small poco objects does not have a lot of references
17-
object[]? baseFromTo = _baseFromTo;
17+
object[] baseFromTo = _baseFromTo;
1818
if (ReferenceEquals(from, baseFromTo[0])) return baseFromTo[3];
1919
if (ReferenceEquals(from, baseFromTo[1])) return baseFromTo[4];
2020
if (ReferenceEquals(from, baseFromTo[2])) return baseFromTo[5];
@@ -66,7 +66,7 @@ public object FindEntry(object key)
6666
if (_buckets != null)
6767
{
6868
int hashCode = RuntimeHelpers.GetHashCode(key) & 0x7FFFFFFF;
69-
Entry[]? entries1 = _entries;
69+
Entry[] entries1 = _entries;
7070
for (int i = _buckets[hashCode % _buckets.Length]; i >= 0; i = entries1[i].Next)
7171
{
7272
if (entries1[i].HashCode == hashCode && ReferenceEquals(entries1[i].Key, key))
@@ -148,7 +148,7 @@ public void Insert(object key, object value)
148148
int hashCode = RuntimeHelpers.GetHashCode(key) & 0x7FFFFFFF;
149149
int targetBucket = hashCode % _buckets.Length;
150150

151-
Entry[]? entries1 = _entries;
151+
Entry[] entries1 = _entries;
152152

153153
if (_count == entries1.Length)
154154
{
@@ -171,10 +171,10 @@ public void Insert(object key, object value)
171171

172172
private void Resize(int newSize)
173173
{
174-
int[]? newBuckets = new int[newSize];
174+
int[] newBuckets = new int[newSize];
175175
for (int i = 0; i < newBuckets.Length; i++)
176176
newBuckets[i] = -1;
177-
Entry[]? newEntries = new Entry[newSize];
177+
Entry[] newEntries = new Entry[newSize];
178178
Array.Copy(_entries, 0, newEntries, 0, _count);
179179

180180
for (int i = 0; i < _count; i++)
File renamed without changes.
Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,35 @@
1-
namespace FastCloner.Helpers;
1+
using System.Reflection;
2+
using System.Runtime.CompilerServices;
3+
4+
namespace FastCloner.Helpers;
25

36
internal static class DeepClonerGenerator
47
{
58
public static T? CloneObject<T>(T? obj)
69
{
7-
if (obj is ValueType)
10+
switch (obj)
811
{
9-
Type? type = obj.GetType();
10-
if (typeof(T) == type)
12+
case ValueType:
1113
{
12-
if (DeepClonerSafeTypes.CanReturnSameObject(type))
13-
return obj;
14+
Type type = obj.GetType();
15+
16+
if (typeof(T) == type)
17+
{
18+
return DeepClonerSafeTypes.CanReturnSameObject(type) ? obj : CloneStructInternal(obj, new DeepCloneState());
19+
}
1420

15-
return CloneStructInternal(obj, new DeepCloneState());
21+
break;
22+
}
23+
case Delegate del:
24+
{
25+
Type? targetType = del.Target?.GetType();
26+
27+
if (targetType?.GetCustomAttribute<CompilerGeneratedAttribute>() is not null)
28+
{
29+
return (T?)CloneClassRoot(obj);
30+
}
31+
32+
return obj;
1633
}
1734
}
1835

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Collections.Concurrent;
22
using System.Reflection;
3+
using System.Runtime.CompilerServices;
34

45
namespace FastCloner.Helpers;
56

@@ -29,7 +30,7 @@ internal static class DeepClonerSafeTypes
2930
[typeof(IntPtr)] = true,
3031
[typeof(UIntPtr)] = true,
3132
[typeof(Guid)] = true,
32-
33+
3334
// Others
3435
[typeof(DBNull)] = true,
3536
[StringComparer.Ordinal.GetType()] = true,
@@ -54,6 +55,13 @@ private static bool CanReturnSameType(Type type, HashSet<Type>? processingTypes)
5455
if (KnownTypes.TryGetValue(type, out bool isSafe))
5556
return isSafe;
5657

58+
if (typeof(Delegate).IsAssignableFrom(type))
59+
{
60+
KnownTypes.TryAdd(type, false);
61+
return false;
62+
}
63+
64+
5765
// enums are safe
5866
// pointers (e.g. int*) are unsafe, but we cannot do anything with it except blind copy
5967
if (type.IsEnum() || type.IsPointer)

0 commit comments

Comments
 (0)