diff --git a/csharp-intermediate-topics/DeepCopyInCSharp/DeepCopyInCSharp/Address.cs b/csharp-intermediate-topics/DeepCopyInCSharp/DeepCopyInCSharp/Address.cs
index b624685963..91bb86a42f 100644
--- a/csharp-intermediate-topics/DeepCopyInCSharp/DeepCopyInCSharp/Address.cs
+++ b/csharp-intermediate-topics/DeepCopyInCSharp/DeepCopyInCSharp/Address.cs
@@ -1,4 +1,6 @@
-namespace DeepCopyInCSharp
+using System.Diagnostics.CodeAnalysis;
+
+namespace DeepCopyInCSharp
{
[Serializable]
public class Address : ICloneable
@@ -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
diff --git a/csharp-intermediate-topics/DeepCopyInCSharp/DeepCopyInCSharp/DeepCopierBenchmark.cs b/csharp-intermediate-topics/DeepCopyInCSharp/DeepCopyInCSharp/DeepCopierBenchmark.cs
index 988e87561f..6379d8bf93 100644
--- a/csharp-intermediate-topics/DeepCopyInCSharp/DeepCopyInCSharp/DeepCopierBenchmark.cs
+++ b/csharp-intermediate-topics/DeepCopyInCSharp/DeepCopyInCSharp/DeepCopierBenchmark.cs
@@ -4,7 +4,7 @@ namespace DeepCopyInCSharp
{
public class DeepCopierBenchmark
{
- private Person? _person;
+ private Person _person = null!;
[GlobalSetup]
public void Setup()
@@ -22,6 +22,12 @@ public void Setup()
};
}
+ [Benchmark]
+ public Person CopyConstructorBenchmark()
+ {
+ return new Person(_person);
+ }
+
[Benchmark]
public Person ICloneableBenchmark()
{
@@ -58,12 +64,6 @@ public Person ExpressionTreesBenchmark()
return DeepCopyMaker.DeepCopyExpressionTrees(_person);
}
- [Benchmark]
- public Person AutoMapperBenchmark()
- {
- return new DeepCopyMaker().DeepCopyAutoMapper(_person);
- }
-
[Benchmark]
public Person FastDeepClonerBenchmark()
{
diff --git a/csharp-intermediate-topics/DeepCopyInCSharp/DeepCopyInCSharp/DeepCopyInCSharp.csproj b/csharp-intermediate-topics/DeepCopyInCSharp/DeepCopyInCSharp/DeepCopyInCSharp.csproj
index 0a3c72bc58..d072e4d083 100644
--- a/csharp-intermediate-topics/DeepCopyInCSharp/DeepCopyInCSharp/DeepCopyInCSharp.csproj
+++ b/csharp-intermediate-topics/DeepCopyInCSharp/DeepCopyInCSharp/DeepCopyInCSharp.csproj
@@ -2,17 +2,16 @@
Exe
- net7.0
+ net10.0
enable
enable
-
-
+
-
+
diff --git a/csharp-intermediate-topics/DeepCopyInCSharp/DeepCopyInCSharp/DeepCopyMaker.cs b/csharp-intermediate-topics/DeepCopyInCSharp/DeepCopyInCSharp/DeepCopyMaker.cs
index b6e8326b9a..657f972e91 100644
--- a/csharp-intermediate-topics/DeepCopyInCSharp/DeepCopyInCSharp/DeepCopyMaker.cs
+++ b/csharp-intermediate-topics/DeepCopyInCSharp/DeepCopyInCSharp/DeepCopyMaker.cs
@@ -1,5 +1,4 @@
-using AutoMapper;
-using DeepCopy;
+using DeepCopy;
using FastDeepCloner;
using System.Linq.Expressions;
using System.Runtime.Serialization;
@@ -10,62 +9,56 @@ namespace DeepCopyInCSharp
{
public class DeepCopyMaker
{
- private readonly IMapper _mapper;
-
- public DeepCopyMaker()
- {
- var config = new MapperConfiguration(cfg =>
- {
- cfg.CreateMap
();
- cfg.CreateMap()
- .ForMember(dest => dest.Address, opt => opt.MapFrom(src => _mapper.Map(src.Address)));
- });
-
- _mapper = config.CreateMapper();
- }
-
public static T DeepCopyXML(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 input)
{
+ ArgumentNullException.ThrowIfNull(input);
+
var jsonString = JsonSerializer.Serialize(input);
- return JsonSerializer.Deserialize(jsonString);
+ return JsonSerializer.Deserialize(jsonString)!;
}
public static T DeepCopyDataContract(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 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));
}
@@ -81,7 +74,15 @@ public static T DeepCopyReflection(T input)
public static T DeepCopyExpressionTrees(T input)
{
- return GenerateDeepCopy()(input);
+ return Cache.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
+ {
+ public static readonly Func Copy = GenerateDeepCopy();
}
private static Func GenerateDeepCopy()
@@ -96,7 +97,7 @@ private static Func GenerateDeepCopy()
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);
@@ -114,11 +115,6 @@ private static Func GenerateDeepCopy()
return Expression.Lambda>(memberInitExpression, inputParameter).Compile();
}
- public Person DeepCopyAutoMapper(Person input)
- {
- return _mapper.Map(input);
- }
-
public static T DeepCopyFastDeepCloner(T input)
{
return (T)DeepCloner.Clone(input);
@@ -131,9 +127,11 @@ public static T DeepCopyLibraryDeepCopy(T input)
public static T DeepCopyJsonDotNet(T input)
{
+ ArgumentNullException.ThrowIfNull(input);
+
var serialized = Newtonsoft.Json.JsonConvert.SerializeObject(input);
- return Newtonsoft.Json.JsonConvert.DeserializeObject(serialized);
+ return Newtonsoft.Json.JsonConvert.DeserializeObject(serialized)!;
}
}
}
diff --git a/csharp-intermediate-topics/DeepCopyInCSharp/DeepCopyInCSharp/Person.cs b/csharp-intermediate-topics/DeepCopyInCSharp/DeepCopyInCSharp/Person.cs
index 36022b0ca1..6a5caae585 100644
--- a/csharp-intermediate-topics/DeepCopyInCSharp/DeepCopyInCSharp/Person.cs
+++ b/csharp-intermediate-topics/DeepCopyInCSharp/DeepCopyInCSharp/Person.cs
@@ -1,4 +1,5 @@
-using System.Runtime.Serialization;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.Serialization;
namespace DeepCopyInCSharp
{
@@ -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()
diff --git a/csharp-intermediate-topics/DeepCopyInCSharp/DeepCopyInCSharp/Program.cs b/csharp-intermediate-topics/DeepCopyInCSharp/DeepCopyInCSharp/Program.cs
index c609059c85..0d626321eb 100644
--- a/csharp-intermediate-topics/DeepCopyInCSharp/DeepCopyInCSharp/Program.cs
+++ b/csharp-intermediate-topics/DeepCopyInCSharp/DeepCopyInCSharp/Program.cs
@@ -8,6 +8,9 @@ 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);
@@ -15,59 +18,67 @@ static void Main(string[] args)
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 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();
}
}
}
\ No newline at end of file
diff --git a/csharp-intermediate-topics/DeepCopyInCSharp/Tests/Test.cs b/csharp-intermediate-topics/DeepCopyInCSharp/Tests/Test.cs
index d6b3f8287a..99be3d81f3 100644
--- a/csharp-intermediate-topics/DeepCopyInCSharp/Tests/Test.cs
+++ b/csharp-intermediate-topics/DeepCopyInCSharp/Tests/Test.cs
@@ -29,21 +29,22 @@ public void WhenUsingICloneable_ThenAllPropertiesCopied()
}
[Fact]
- public void WhenUsingXMLSerialization_ThenAllPropertiesCopied()
+ public void WhenUsingCopyConstructor_ThenAllPropertiesCopiedAndAddressNotShared()
{
- var copy = DeepCopyMaker.DeepCopyXML(_person);
+ var copy = new Person(_person);
Assert.Equal(copy.Name, _person.Name);
Assert.Equal(copy.Age, _person.Age);
Assert.Equal(copy.Address.Street, _person.Address.Street);
Assert.Equal(copy.Address.City, _person.Address.City);
Assert.Equal(copy.Address.State, _person.Address.State);
+ Assert.NotSame(_person.Address, copy.Address);
}
[Fact]
- public void WhenUsingJsonSerialization_ThenAllPropertiesCopied()
+ public void WhenUsingXMLSerialization_ThenAllPropertiesCopied()
{
- var copy = DeepCopyMaker.DeepCopyJSON(_person);
+ var copy = DeepCopyMaker.DeepCopyXML(_person);
Assert.Equal(copy.Name, _person.Name);
Assert.Equal(copy.Age, _person.Age);
@@ -53,9 +54,9 @@ public void WhenUsingJsonSerialization_ThenAllPropertiesCopied()
}
[Fact]
- public void WhenUsingDataContractSerialization_ThenAllPropertiesCopied()
+ public void WhenUsingJsonSerialization_ThenAllPropertiesCopied()
{
- var copy = DeepCopyMaker.DeepCopyDataContract(_person);
+ var copy = DeepCopyMaker.DeepCopyJSON(_person);
Assert.Equal(copy.Name, _person.Name);
Assert.Equal(copy.Age, _person.Age);
@@ -65,9 +66,9 @@ public void WhenUsingDataContractSerialization_ThenAllPropertiesCopied()
}
[Fact]
- public void WhenUsingReflection_ThenAllPropertiesCopied()
+ public void WhenUsingDataContractSerialization_ThenAllPropertiesCopied()
{
- var copy = DeepCopyMaker.DeepCopyReflection(_person);
+ var copy = DeepCopyMaker.DeepCopyDataContract(_person);
Assert.Equal(copy.Name, _person.Name);
Assert.Equal(copy.Age, _person.Age);
@@ -77,9 +78,9 @@ public void WhenUsingReflection_ThenAllPropertiesCopied()
}
[Fact]
- public void WhenUsingExpressionTrees_ThenAllPropertiesCopied()
+ public void WhenUsingReflection_ThenAllPropertiesCopied()
{
- var copy = DeepCopyMaker.DeepCopyExpressionTrees(_person);
+ var copy = DeepCopyMaker.DeepCopyReflection(_person);
Assert.Equal(copy.Name, _person.Name);
Assert.Equal(copy.Age, _person.Age);
@@ -89,10 +90,9 @@ public void WhenUsingExpressionTrees_ThenAllPropertiesCopied()
}
[Fact]
- public void WhenUsingAutoMApper_ThenAllPropertiesCopied()
+ public void WhenUsingExpressionTrees_ThenAllPropertiesCopied()
{
- var copier = new DeepCopyMaker();
- var copy = copier.DeepCopyAutoMapper(_person);
+ var copy = DeepCopyMaker.DeepCopyExpressionTrees(_person);
Assert.Equal(copy.Name, _person.Name);
Assert.Equal(copy.Age, _person.Age);
diff --git a/csharp-intermediate-topics/DeepCopyInCSharp/Tests/Tests.csproj b/csharp-intermediate-topics/DeepCopyInCSharp/Tests/Tests.csproj
index 0eb4dbf7fb..3c46d1c456 100644
--- a/csharp-intermediate-topics/DeepCopyInCSharp/Tests/Tests.csproj
+++ b/csharp-intermediate-topics/DeepCopyInCSharp/Tests/Tests.csproj
@@ -1,7 +1,7 @@
- net7.0
+ net10.0
enable
enable
@@ -9,13 +9,13 @@
-
-
-
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all