Skip to content

Commit

Permalink
net9 support and speed up deepcopy of other System.Numerics types
Browse files Browse the repository at this point in the history
  • Loading branch information
jpmikkers committed Nov 19, 2024
1 parent c509f4f commit 0dc4048
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 20 deletions.
8 changes: 4 additions & 4 deletions deepcopy/Baksteen.Extensions.DeepCopy.csproj
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
<TargetFrameworks>net6.0;net8.0;net9.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>1.0.3</Version>
<Version>1.0.4</Version>
<RootNamespace>Baksteen.Extensions.DeepCopy</RootNamespace>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageTags>clone;deepclone;copy;deepcopy;reflection;extension</PackageTags>
Expand All @@ -15,8 +15,8 @@ It has various optimizations so it remains surprisingly fast, about 7 times fast
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/jpmikkers/Baksteen.Extensions.DeepCopy.git</RepositoryUrl>
<PackageProjectUrl>https://github.com/jpmikkers/Baksteen.Extensions.DeepCopy</PackageProjectUrl>
<PackageReleaseNotes>Added System.Uri as immutable, added support for dotnet 8.</PackageReleaseNotes>
<Copyright>Copyright (c) JPMikkers 2023</Copyright>
<PackageReleaseNotes>Added net9 support, removed net7. Speed up copy of other System.Numerics types</PackageReleaseNotes>
<Copyright>Copyright (c) JPMikkers 2024</Copyright>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

Expand Down
41 changes: 28 additions & 13 deletions deepcopy/DeepCopyObjectExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,26 @@ private class DeepCopyContext
{
private static readonly Func<object, object> _shallowClone;

// Set of deeply immutable types. This includes all primitives, some known immutable
// Set of deeply immutable types**. This includes all primitives, some known immutable
// valuetypes, and a few sealed immutable reference types such as 'string', 'DBNull' and
// 'Version'. Nullable<T> of an immutable valuetype T is itself immutable as well but rather
// than duplicating all those entries here, they are added programmatically in the static
// constructor below.
//
// When the DeepCopy encounters an object of one of these types it can simply return the same
// object without further (slower) deepcopy of its member fields.
//
// ** or mutable value types (struct) that do not contain any reference fields (e.g. Quaternion,
// Vector2 etc, see below). This is still safe even for boxed versions of the struct because:
//
// - when boxing such a struct, a copy is created => the boxed struct doesn't see
// mutations of the original struct
//
// - when unboxing such a struct another copy is created => mutations of the unboxed
// struct doesn't affect the boxed struct
//
// - c# doesn't let you directly mutate fields of a boxed object (though with reflection
// anything is possible)
private static readonly HashSet<Type> _immutableTypes = new()
{
typeof(nint),
Expand All @@ -42,14 +57,14 @@ private class DeepCopyContext
typeof(Half),
typeof(decimal),
typeof(BigInteger),
typeof(Vector2),
typeof(Vector3),
typeof(Vector4),
typeof(Matrix3x2),
typeof(Matrix4x4),
typeof(Complex),
typeof(Quaternion),
typeof(Plane),
typeof(Quaternion), // ref free mutable value type
typeof(Vector2), // ref free mutable value type
typeof(Vector3), // ref free mutable value type
typeof(Vector4), // ref free mutable value type
typeof(Plane), // ref free mutable value type
typeof(Matrix3x2), // ref free mutable value type
typeof(Matrix4x4), // ref free mutable value type
typeof(Guid),
typeof(DateTime),
typeof(DateOnly),
Expand Down Expand Up @@ -150,12 +165,12 @@ private static bool IsDeeplyImmutable(Type type)

private static void ReplaceArrayElements(Array array, Func<object?, object?> func, int dimension, int[] counts, int[] indices)
{
int len = counts[dimension];
var len = counts[dimension];

if(dimension < (counts.Length - 1))
{
// not the final dimension, loop the range, and recursively handle one dimension higher
for(int t = 0; t < len; t++)
for(var t = 0; t < len; t++)
{
indices[dimension] = t;
ReplaceArrayElements(array, func, dimension + 1, counts, indices);
Expand All @@ -164,7 +179,7 @@ private static void ReplaceArrayElements(Array array, Func<object?, object?> fun
else
{
// we've reached the final dimension where the elements are closest together in memory. Do a final loop.
for(int t = 0; t < len; t++)
for(var t = 0; t < len; t++)
{
indices[dimension] = t;
array.SetValue(func(array.GetValue(indices)), indices);
Expand All @@ -177,8 +192,8 @@ private static void ReplaceArrayElements(Array array, Func<object?, object?> fun
if(array.Rank == 1)
{
// do a fast loop for the common case, a one dimensional array
int len = array.GetLength(0);
for(int t = 0; t < len; t++)
var len = array.GetLength(0);
for(var t = 0; t < len; t++)
{
array.SetValue(func(array.GetValue(t)), t);
}
Expand Down
7 changes: 7 additions & 0 deletions unittests/ObjectExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,13 @@ public void Copy_ShouldNotDeepCopyImmutableTypes()
SubTest(new Half());
SubTest(new decimal());
SubTest(new Complex());
SubTest(new Quaternion());
SubTest(new Vector2());
SubTest(new Vector3());
SubTest(new Vector4());
SubTest(new Plane());
SubTest(new Matrix3x2());
SubTest(new Matrix4x4());
SubTest(new BigInteger());
SubTest(new Guid());
SubTest(new DateTime());
Expand Down
6 changes: 3 additions & 3 deletions unittests/unittests.csproj
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
<TargetFrameworks>net6.0;net8.0;net9.0</TargetFrameworks>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="MSTest.TestAdapter" Version="3.6.0" />
<PackageReference Include="MSTest.TestFramework" Version="3.6.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.6.3" />
<PackageReference Include="MSTest.TestFramework" Version="3.6.3" />
<PackageReference Include="coverlet.collector" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down

0 comments on commit 0dc4048

Please sign in to comment.