Skip to content

Commit

Permalink
Issue #17 hotfix for comparers
Browse files Browse the repository at this point in the history
  • Loading branch information
force-net committed Apr 29, 2022
1 parent 2c1ff62 commit e2e19be
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 19 deletions.
48 changes: 48 additions & 0 deletions DeepCloner.Tests/ArraysSpec.cs
Original file line number Diff line number Diff line change
@@ -1,5 1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

using NUnit.Framework;
Expand Down Expand Up @@ -358,5 360,51 @@ public void MultiDimensional_Array_Should_Be_Cloned()
Array.CreateInstance(typeof(int), new[] { 0, 0, 1 }).DeepClone();
Array.CreateInstance(typeof(int), new[] { 1, 1, 1 }).DeepClone();
}

[Test]
public void Issue_17_Spec()
{
var set = new HashSet<string> { "value" };
Assert.That(set.Contains("value"), Is.True);

var cloned = set.DeepClone();
Assert.That(cloned.Contains("value"), Is.True);

var copyOfSet = new HashSet<string>(set, set.Comparer);
Assert.That(copyOfSet.Contains("value"), Is.True);

var copyOfCloned = new HashSet<string>(cloned, cloned.Comparer);
Assert.That(copyOfCloned.ToArray()[0] == "value", Is.True);

Assert.That(copyOfCloned.Contains("value"), Is.True);
}

[Test]
public void Check_Comparer_does_not_Clone()
{
Check_Comparer_does_not_Clone_Internal<string>();
Check_Comparer_does_not_Clone_Internal<int>();
Check_Comparer_does_not_Clone_Internal<object>();
Check_Comparer_does_not_Clone_Internal<FileShare>();
Check_Comparer_does_not_Clone_Internal<byte[]>();
Check_Comparer_does_not_Clone_Internal<byte>();
Check_Comparer_does_not_Clone_Internal<int?>();
Check_Comparer_does_not_Clone_Internal<HashSet<int>>();
Assert.That(StringComparer.Ordinal == StringComparer.Ordinal.DeepClone(), Is.True);
Assert.That(StringComparer.OrdinalIgnoreCase == StringComparer.OrdinalIgnoreCase.DeepClone(), Is.True);
Assert.That(StringComparer.InvariantCulture == StringComparer.InvariantCulture.DeepClone(), Is.True);
Assert.That(StringComparer.InvariantCultureIgnoreCase == StringComparer.InvariantCultureIgnoreCase.DeepClone(), Is.True);
Assert.That(StringComparer.CurrentCulture == StringComparer.CurrentCulture.DeepClone(), Is.True);
Assert.That(StringComparer.CurrentCultureIgnoreCase == StringComparer.CurrentCultureIgnoreCase.DeepClone(), Is.True);
}

private void Check_Comparer_does_not_Clone_Internal<T>()
{
var comparer = EqualityComparer<T>.Default;
var cloned = comparer.DeepClone();

// checking by reference
Assert.That(comparer == cloned, Is.True);
}
}
}
16 changes: 14 additions & 2 deletions DeepCloner.Tests/DeepCloner.Tests.Core.csproj
Original file line number Diff line number Diff line change
@@ -1,6 1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp1.0;netcoreapp2.0;net461</TargetFrameworks>
<TargetFrameworks>netcoreapp2.0;netcoreapp3.1;netcoreapp6.0;net461</TargetFrameworks>
<DebugType>portable</DebugType>
<AssemblyName>DeepCloner.NET.Tests</AssemblyName>
<OutputType>Library</OutputType>
Expand All @@ -26,6 26,12 @@
<PackageReference Include="EntityFramework" Version="6.1.3" />
<PackageReference Include="ServiceStack" Version="5.9.2" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp6.0'">
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.2" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1'">
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.2" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.0'">
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.2" />
</ItemGroup>
Expand All @@ -36,11 42,17 @@
<Reference Include="System.Windows.Forms" />
</ItemGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp1.0' ">
<DefineConstants>$(DefineConstants);NETCORE;;NETCORE13</DefineConstants>
<DefineConstants>$(DefineConstants);NETCORE;NETCORE13</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.0' ">
<DefineConstants>$(DefineConstants);NETCORE;NETCORE20</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp3.1' ">
<DefineConstants>$(DefineConstants);NETCORE;NETCORE31</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netcoreapp6.0' ">
<DefineConstants>$(DefineConstants);NETCORE;NETCORE60</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<DefineConstants>$(DefineConstants);COREVERSION</DefineConstants>
</PropertyGroup>
Expand Down
10 changes: 8 additions & 2 deletions DeepCloner.Tests/SystemTypesSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 44,11 @@ public void Type_With_Native_Resource_Should_Be_Cloned()
try
{
var writer = File.CreateText(fileName);
writer.AutoFlush = true;
writer.AutoFlush = true;
writer.Write("1");
var cloned = writer.DeepClone();
writer.Write("2");
cloned.Write(3);
cloned.Write("3");
#if !NETCORE
var f = typeof(FileStream).GetField("_handle", BindingFlags.NonPublic | BindingFlags.Instance);
var f2 = typeof(SafeHandle).GetField("_state", BindingFlags.NonPublic | BindingFlags.Instance);
Expand All @@ -63,7 63,13 @@ public void Type_With_Native_Resource_Should_Be_Cloned()

Assert.Throws<ObjectDisposedException>(cloned.Flush);
var res = File.ReadAllText(fileName);
#if NETCORE60
// it uses RandomAccess.WriteAtOffset(this._fileHandle, buffer, this._filePosition); - and offset of cloned file
// is preserved, so, 2 will disappear
Assert.That(res, Is.EqualTo("13"));
#else
Assert.That(res, Is.EqualTo("123"));
#endif
}
finally
{
Expand Down
14 changes: 3 additions & 11 deletions DeepCloner.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 3,7 @@
<metadata>
<id>DeepCloner</id>
<title>DeepCloner</title>
<version>0.10.3</version>
<version>0.10.4</version>
<authors>force</authors>
<owners>force</owners>
<license type="expression">MIT</license>
Expand All @@ -15,17 15,9 @@
<description>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.</description>
<summary>Small Library for fast deep or shallow cloning .NET objects.</summary>
<releaseNotes>
Custom dictionary implementation for faster reference counting (and faster cloning)
Excluded DBNull from cloning
Rewrited code for more correct cloning of objects forbidden for cloning (mainly for Shallow cloning)
Better handling for stucts casted to object
Increased speed for cloning simple tuples
Added some protection for parallel cloning objects with readonly fields
Optimized copying readonly fields for .net core
Fixed copying multi-dim arrays with 0-length of some dimension
2-dim arrays were processed by generic (non-optimized) way
Excluded default comparers from cloning. Main fix for .NET6 and HashSet&lt;string&gt; but some other cases can be fixed too.
</releaseNotes>
<copyright>Copyright by Force 2016-2021</copyright>
<copyright>Copyright by Force 2016-2022</copyright>
<tags>.NET shallow deep clone DeepClone fast</tags>
<dependencies>
<group targetFramework="net40">
Expand Down
2 changes: 1 addition & 1 deletion DeepCloner/DeepCloner.Core.csproj
Original file line number Diff line number Diff line change
@@ -1,6 1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<VersionPrefix>0.10.3</VersionPrefix>
<VersionPrefix>0.10.4</VersionPrefix>
<TargetFrameworks>netstandard1.3;net40</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AssemblyName>DeepCloner</AssemblyName>
Expand Down
15 changes: 15 additions & 0 deletions DeepCloner/Helpers/DeepClonerSafeTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 25,8 @@ var x in
// do not clone such native type
Type.GetType("System.RuntimeType"),
Type.GetType("System.RuntimeTypeHandle"),
StringComparer.Ordinal.GetType(),
StringComparer.CurrentCulture.GetType(), // CultureAwareComparer - can be same
#if !NETCORE
typeof(DBNull)
#endif
Expand Down Expand Up @@ -119,6 121,19 @@ private static bool CanReturnSameType(Type type, HashSet<Type> processingTypes)
return true;
}
#endif
// default comparers should not be cloned due possible comparison EqualityComparer<T>.Default == comparer
if (type.FullName.Contains("EqualityComparer"))
{
if (type.FullName.StartsWith("System.Collections.Generic.GenericEqualityComparer`")
|| type.FullName.StartsWith("System.Collections.Generic.ObjectEqualityComparer`")
|| type.FullName.StartsWith("System.Collections.Generic.EnumEqualityComparer`")
|| type.FullName.StartsWith("System.Collections.Generic.NullableEqualityComparer`")
|| type.FullName == "System.Collections.Generic.ByteEqualityComparer")
{
KnownTypes.TryAdd(type, true);
return true;
}
}

// classes are always unsafe (we should copy it fully to count references)
if (!type.IsValueType())
Expand Down
6 changes: 3 additions & 3 deletions DeepCloner/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 9,7 @@
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Force")]
[assembly: AssemblyProduct("DeepCloner")]
[assembly: AssemblyCopyright("Copyright © Force 2016-2021")]
[assembly: AssemblyCopyright("Copyright © Force 2016-2022")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

Expand All @@ -32,8 32,8 @@
// by using the '*' as shown below:

[assembly: AssemblyVersion("0.10.0.0")] // change this value only when api is changing
[assembly: AssemblyFileVersion("0.10.3.0")]
[assembly: AssemblyInformationalVersion("0.10.3.0")]
[assembly: AssemblyFileVersion("0.10.4.0")]
[assembly: AssemblyInformationalVersion("0.10.4.0")]

#if BUILDCORE
// [assembly: AssemblyKeyFileAttribute("..\\public.snk")]
Expand Down

0 comments on commit e2e19be

Please sign in to comment.