Skip to content

Commit

Permalink
fix delegates
Browse files Browse the repository at this point in the history
  • Loading branch information
lofcz committed Jan 7, 2025
1 parent 0c75064 commit e8f6f54
Show file tree
Hide file tree
Showing 14 changed files with 225 additions and 14 deletions.
12 changes: 12 additions & 0 deletions FastCloner.Tests/FastCloner.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,20 @@
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.0.1" />
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="System.Drawing.Common" Version="8.0.3" />
</ItemGroup>

<PropertyGroup>
<IsWindows Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))' == 'true'">true</IsWindows>
<IsLinux Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))' == 'true'">true</IsLinux>
</PropertyGroup>
<PropertyGroup Condition="'$(IsWindows)'=='true'">
<DefineConstants>WINDOWS</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(IsLinux)'=='true'">
<DefineConstants>LINUX</DefineConstants>
</PropertyGroup>

<ItemGroup Condition="'$(TargetFramework)'=='net462'">
<Reference Include="System.Windows.Forms" />
<PackageReference Include="EntityFramework" Version="6.4.4" />
Expand Down
168 changes: 168 additions & 0 deletions FastCloner.Tests/SpecificScenariosTest.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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<string> originalAction = testObject.TestMethod;

// Act
Action<string> 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<string> originalResult = [];
List<string> 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<string> originalAction = StaticTestMethod;

// Act
Action<string> 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<int> outer = CreateClosure();

// Act
Func<int> 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<int> 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()
Expand Down Expand Up @@ -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()
Expand Down
6 changes: 6 additions & 0 deletions FastCloner/Code/BadTypes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace FastCloner.Helpers;

internal class BadTypes
{

}
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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)
{
Expand All @@ -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++)
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -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>(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<CompilerGeneratedAttribute>() is not null)
{
return (T?)CloneClassRoot(obj);
}

return obj;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Collections.Concurrent;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace FastCloner.Helpers;

Expand Down Expand Up @@ -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,
Expand All @@ -54,6 +55,13 @@ private static bool CanReturnSameType(Type type, HashSet<Type>? 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)
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion FastCloner/FastCloner.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<Version>1.1.4</Version>

<Title>FastCloner</Title>
<Description>Fast deep cloning library for .NET 8+</Description>
<Description>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.</Description>
<PackageReadmeFile>README.md</PackageReadmeFile>

<Authors>Matěj Štágl, Adrian Mos, DeepCloner Collaborators</Authors>
Expand Down

0 comments on commit e8f6f54

Please sign in to comment.