Automatic implementation of the IDeepCloneable<T> interface via source generators. Suitable for both library authors and users.
Provides automatic generation of the DeepClone() method and IDeepCloneable<T> implementation for partial types marked with [DeepCloneable].
Install the NuGet package IDeepCloneable to your project.
dotnet add package IDeepCloneableThen mark a partial type with the [DeepCloneable] attribute.
[DeepCloneable] // <- add this attribute
public partial class Person // <- make it partial
{
public string Name { get; set; }
public int Age { get; set; }
}That's it! The DeepClone() method will be automatically generated and the generated partial type will implement IDeepCloneable<Person>.
// generated code (sample)
partial class Person : IDeepCloneable<Person>
{
public Person DeepClone()
{
return new Person
{
Name = this.Name,
Age = this.Age,
};
}
}And you can use it like this:
var person1 = new Person { Name = "Alice", Age = 30 };
var person2 = person1.DeepClone();Library authors can use the IDeepCloneable<T> interface to perform DeepClone() without reflection.
First, install the NuGet package IDeepCloneable to your project.
dotnet add package IDeepCloneableThen, you can check if a type implements IDeepCloneable<T> and call the DeepClone() method accordingly.
public void RegisterCloneMethod<T>()
{
Func<T, T> cloneFunc = null;
bool isDeepCloneable = typeof(IDeepCloneable<T>).IsAssignableFrom(typeof(T));
if(isDeepCloneable) {
cloneFunc = value => ((IDeepCloneable<T>)value).DeepClone();
}
else {
// fallback implementation
}
}
// or using generic constraints
public void RegisterCloneMethod<T>() where T : IDeepCloneable<T>
{
Func<T, T> cloneFunc = value => value.DeepClone();
}This completes the setup. Library users do not need to introduce IDeepCloneable; they only need to apply [DeepCloneable].
// user side
[DeepCloneable]
public partial class MyModel { /* ... */ }
// call library method
library.RegisterCloneMethod<MyModel>();While there are many similar libraries available, this library's key feature is that it generates the DeepClone() method as an implementation of the IDeepCloneable<T> interface.
By doing this:
- Library authors can use
DeepClone()without reflection (NativeAOT friendly) - Users are relieved of the burden of manual implementation
Performance is a concern, right? In benchmarks for medium-sized models, it shows comparable results to major libraries.
| Method | Mean | Ratio | Gen0 | Gen1 | Allocated |
|---|---|---|---|---|---|
| IDeepCloneable | 912.7 ns | 1.00 | 0.2890 | 0.0048 | 4.73 KB |
| Mapperly | 996.6 ns | 1.09 | 0.2880 | 0.0038 | 4.73 KB |
| FastCloner_SourceGen | 1,147.7 ns | 1.26 | 0.2880 | 0.0038 | 4.73 KB |
| AutoMapper | 3,139.2 ns | 3.44 | 0.3433 | 0.0038 | 5.65 KB |
| FastCloner_Reflection | 8,772.5 ns | 9.61 | 0.8392 | 0.0153 | 13.79 KB |
| SystemTextJson_Reflection | 31,857.2 ns | 34.90 | 1.2207 | - | 20.59 KB |
Detailed results can be found in benchmark/results and Benchmark source code.
As you can see from the generated code, you can simply implement the IDeepCloneable<T>.DeepClone() method yourself.
public class Person : IDeepCloneable<Person>
{
public string Name { get; set; }
public int Age { get; set; }
public Person DeepClone()
{
// your custom implementation
}
}This consists of two libraries.
This is the library that defines the IDeepCloneable<T> interface and the [DeepCloneable] marker attribute.
To allow users of third-party libraries to use it without worrying about IDeepCloneable, it is defined directly under the global namespace.
public sealed class DeepCloneableAttribute : Attribute;
public interface IDeepCloneable<T>
{
T DeepClone();
}Additionally, it will automatically reference the IDeepCloneable.Generator.
This is the source generator library that automatically generates the IDeepCloneable<T>.DeepClone() method.
There is no need to directly reference this library.
DeepClone (also commonly referred to as DeepCopy) refers to the operation of creating a complete copy of an object.
For example, if you simply assign an object, reference-type properties are not copied, and both variables will point to the same instance.
var person1 = new Person
{
Name = "Alice",
Address = new Address { City = "Wonderland" }
};
var person2 = person1; // shallow copy
person2.Address.City = "New City";
// person1.Address.City is now "New City"Additionally, in the following example, the Address property is a shallow copy, which is insufficient.
var person3 = new Person
{
Name = person1.Name,
Address = person1.Address // shallow copy
};
person3.Address.City = "Another City";
// person1.Address.City is now "Another City"To avoid this, you would need to manually copy everything.
var person4 = new Person
{
Name = person1.Name,
Address = new Address { City = person1.Address.City }
};
person4.Address.City = "Different City";
// person1.Address.City remains unchangedWriting this every time is tedious. Instead, you can use the DeepClone() method.
public class Person
{
public string Name { get; set; }
public Address Address { get; set; }
public Person DeepClone()
{
return new Person
{
Name = this.Name,
Address = new Address
{
City = this.Address.City
}
};
}
}
var person5 = person1.DeepClone();
person5.Address.City = "Cloned City";
// person1.Address.City remains unchangedWhile this works, it is still a bit cumbersome. With this library, you can automatically generate the implementation of the DeepClone() method.
[DeepCloneable] // <- add this attribute
public partial class Person // <- make it partial
{
public string Name { get; set; }
public Address Address { get; set; }
}
var person6 = person1.DeepClone();
person6.Address.City = "Auto Cloned City";
// person1.Address.City remains unchangedThis library implements its own IDeepCloneable<T> interface instead of the standard System.ICloneable for the following reasons:
- The behavior of
ICloneable.Clone()is ambiguous—it is unclear whether it performs a shallow or deep copy. ICloneable.Clone()is non-generic, so you must cast the return value.
For these reasons, even as early as 2004, the use of ICloneable was not recommended. Reference