Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace DeepCopyInCSharp
using System.Diagnostics.CodeAnalysis;

namespace DeepCopyInCSharp
{
[Serializable]
public class Address : ICloneable
Expand All @@ -7,6 +9,16 @@ public class Address : ICloneable
public required string City { get; set; }
public required string State { get; set; }

public Address() { }

[SetsRequiredMembers]
public Address(Address other)
{
Street = other.Street;
City = other.City;
State = other.State;
}

public object Clone()
{
return new Address
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace DeepCopyInCSharp
{
public class DeepCopierBenchmark
{
private Person? _person;
private Person _person = null!;

[GlobalSetup]
public void Setup()
Expand All @@ -22,6 +22,12 @@ public void Setup()
};
}

[Benchmark]
public Person CopyConstructorBenchmark()
{
return new Person(_person);
}

[Benchmark]
public Person ICloneableBenchmark()
{
Expand Down Expand Up @@ -58,12 +64,6 @@ public Person ExpressionTreesBenchmark()
return DeepCopyMaker.DeepCopyExpressionTrees(_person);
}

[Benchmark]
public Person AutoMapperBenchmark()
{
return new DeepCopyMaker().DeepCopyAutoMapper(_person);
}

[Benchmark]
public Person FastDeepClonerBenchmark()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AutoMapper" Version="12.0.1" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.5" />
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
<PackageReference Include="DeepCopy" Version="1.0.3" />
<PackageReference Include="FastDeepCloner" Version="1.3.6" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using AutoMapper;
using DeepCopy;
using DeepCopy;
using FastDeepCloner;
using System.Linq.Expressions;
using System.Runtime.Serialization;
Expand All @@ -10,62 +9,56 @@ namespace DeepCopyInCSharp
{
public class DeepCopyMaker
{
private readonly IMapper _mapper;

public DeepCopyMaker()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Address, Address>();
cfg.CreateMap<Person, Person>()
.ForMember(dest => dest.Address, opt => opt.MapFrom(src => _mapper.Map<Address>(src.Address)));
});

_mapper = config.CreateMapper();
}

public static T DeepCopyXML<T>(T input)
{
ArgumentNullException.ThrowIfNull(input);

using var stream = new MemoryStream();

var serializer = new XmlSerializer(typeof(T));
serializer.Serialize(stream, input);
stream.Position = 0;

return (T)serializer.Deserialize(stream);
return (T)serializer.Deserialize(stream)!;
}

public static T DeepCopyJSON<T>(T input)
{
ArgumentNullException.ThrowIfNull(input);

var jsonString = JsonSerializer.Serialize(input);

return JsonSerializer.Deserialize<T>(jsonString);
return JsonSerializer.Deserialize<T>(jsonString)!;
}

public static T DeepCopyDataContract<T>(T input)
{
ArgumentNullException.ThrowIfNull(input);

using var stream = new MemoryStream();

var serializer = new DataContractSerializer(typeof(T));
serializer.WriteObject(stream, input);
stream.Position = 0;

return (T)serializer.ReadObject(stream);
return (T)serializer.ReadObject(stream)!;
}

public static T DeepCopyReflection<T>(T input)
{
ArgumentNullException.ThrowIfNull(input);

var type = input.GetType();
var properties = type.GetProperties();

T clonedObj = (T)Activator.CreateInstance(type);
T clonedObj = (T)Activator.CreateInstance(type)!;

foreach (var property in properties)
{
if (property.CanWrite)
{
object value = property.GetValue(input);
if (value != null && value.GetType().IsClass && !value.GetType().FullName.StartsWith("System."))
object? value = property.GetValue(input);
if (value != null && value.GetType().IsClass && !value.GetType().FullName!.StartsWith("System."))
{
property.SetValue(clonedObj, DeepCopyReflection(value));
}
Expand All @@ -81,7 +74,15 @@ public static T DeepCopyReflection<T>(T input)

public static T DeepCopyExpressionTrees<T>(T input)
{
return GenerateDeepCopy<T>()(input);
return Cache<T>.Copy(input);
}

// Compiling an expression tree is expensive, so each type's delegate is built once
// and reused. The generated code copies a nested object by calling
// DeepCopyExpressionTrees, so nested types go through this cache as well.
private static class Cache<T>
{
public static readonly Func<T, T> Copy = GenerateDeepCopy<T>();
}

private static Func<T, T> GenerateDeepCopy<T>()
Expand All @@ -96,7 +97,7 @@ private static Func<T, T> GenerateDeepCopy<T>()
if (propertyInfo.PropertyType.IsClass && propertyInfo.PropertyType != typeof(string))
{
var copyMethod = typeof(DeepCopyMaker)
.GetMethod(nameof(DeepCopyMaker.DeepCopyExpressionTrees))
.GetMethod(nameof(DeepCopyMaker.DeepCopyExpressionTrees))!
.MakeGenericMethod(propertyInfo.PropertyType);

var propertyCopyExpression = Expression.Call(copyMethod, propertyExpression);
Expand All @@ -114,11 +115,6 @@ private static Func<T, T> GenerateDeepCopy<T>()
return Expression.Lambda<Func<T, T>>(memberInitExpression, inputParameter).Compile();
}

public Person DeepCopyAutoMapper(Person input)
{
return _mapper.Map<Person>(input);
}

public static T DeepCopyFastDeepCloner<T>(T input)
{
return (T)DeepCloner.Clone(input);
Expand All @@ -131,9 +127,11 @@ public static T DeepCopyLibraryDeepCopy<T>(T input)

public static T DeepCopyJsonDotNet<T>(T input)
{
ArgumentNullException.ThrowIfNull(input);

var serialized = Newtonsoft.Json.JsonConvert.SerializeObject(input);

return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(serialized);
return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(serialized)!;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Runtime.Serialization;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.Serialization;

namespace DeepCopyInCSharp
{
Expand All @@ -15,6 +16,16 @@ public class Person : ICloneable
[DataMember]
public required Address Address { get; set; }

public Person() { }

[SetsRequiredMembers]
public Person(Person other)
{
Name = other.Name;
Age = other.Age;
Address = new Address(other.Address);
}

public Person ShallowCopy() => (Person)this.MemberwiseClone();

public object Clone()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,66 +8,77 @@ public class Program
static void Main(string[] args)
{
//Benchmark - start
// FastDeepCloner 1.3.6 ships an assembly built without optimizations, and BenchmarkDotNet
// refuses to run while any referenced assembly is non-optimized. This switch turns that
// check off for every assembly, so always run this project with -c Release.
var config = ManualConfig.Create(DefaultConfig.Instance)
.WithOptions(ConfigOptions.DisableOptimizationsValidator);

var summary = BenchmarkRunner.Run<DeepCopierBenchmark>(config);
Console.WriteLine(summary);
//Benchmark - end

var originalPerson = new Person
{
Name = "Steve Doe",
Age = 21,
Address = new Address
{
Street = "123 Main St.",
City = "Anytown",
State = "AB"
}
};

//Shallow Copy
var copiedPerson = originalPerson.ShallowCopy();
CopyModifyAndPrint("Shallow copy (MemberwiseClone)", original => original.ShallowCopy());

//Deep Copy - ICloneable
copiedPerson = (Person)originalPerson.Clone();
CopyModifyAndPrint("ICloneable", original => (Person)original.Clone());

//Deep Copy - Copy Constructor
CopyModifyAndPrint("Copy constructor", original => new Person(original));

//Deep Copy - XML Serializer
copiedPerson = DeepCopyMaker.DeepCopyXML(originalPerson);
CopyModifyAndPrint("XML serialization", DeepCopyMaker.DeepCopyXML);

//Deep Copy - JSON Serialzer
copiedPerson = DeepCopyMaker.DeepCopyJSON(originalPerson);
//Deep Copy - JSON Serializer
CopyModifyAndPrint("JSON serialization", DeepCopyMaker.DeepCopyJSON);

//Deep Copy - Data Contract Serialization
copiedPerson = DeepCopyMaker.DeepCopyDataContract(originalPerson);
CopyModifyAndPrint("Data contract serialization", DeepCopyMaker.DeepCopyDataContract);

//Deep Copy - Reflection
copiedPerson = DeepCopyMaker.DeepCopyReflection(originalPerson);
CopyModifyAndPrint("Reflection", DeepCopyMaker.DeepCopyReflection);

//Deep Copy - Expression Trees
copiedPerson = DeepCopyMaker.DeepCopyExpressionTrees(originalPerson);

//Deep Copy - AutoMapper
var copier = new DeepCopyMaker();
copiedPerson = copier.DeepCopyAutoMapper(originalPerson);
CopyModifyAndPrint("Expression trees", DeepCopyMaker.DeepCopyExpressionTrees);

//Deep Copy - FastDeepCloner
copiedPerson = DeepCopyMaker.DeepCopyFastDeepCloner(originalPerson);
CopyModifyAndPrint("FastDeepCloner", DeepCopyMaker.DeepCopyFastDeepCloner);

//Deep Copy - DeepCopy
copiedPerson = DeepCopyMaker.DeepCopyLibraryDeepCopy(originalPerson);
CopyModifyAndPrint("DeepCopy", DeepCopyMaker.DeepCopyLibraryDeepCopy);

//Deep Copy - JSON.Net
copiedPerson = DeepCopyMaker.DeepCopyJsonDotNet(originalPerson);
CopyModifyAndPrint("Json.NET", DeepCopyMaker.DeepCopyJsonDotNet);
}

// Each technique gets a fresh original, so one technique's result
// can never be hidden behind the next one's.
private static void CopyModifyAndPrint(string technique, Func<Person, Person> copy)
{
var originalPerson = new Person
{
Name = "Steve Doe",
Age = 22,
Address = new Address
{
Street = "123 Main St.",
City = "Anytown",
State = "AB"
}
};

var copiedPerson = copy(originalPerson);

//Modifying the copied object
copiedPerson.Name = "Jack Swallow";
copiedPerson.Address.Street = "456 Elmo St.";

//Result
Console.WriteLine(technique);
Console.WriteLine($"Original Name: {originalPerson.Name}");
Console.WriteLine($"Original Street: {originalPerson.Address.Street}");
Console.WriteLine();
}
}
}
Loading
Loading