CloneBox 1.0.0
dotnet add package CloneBox --version 1.0.0
NuGet\Install-Package CloneBox -Version 1.0.0
<PackageReference Include="CloneBox" Version="1.0.0" />
<PackageVersion Include="CloneBox" Version="1.0.0" />
<PackageReference Include="CloneBox" />
paket add CloneBox --version 1.0.0
#r "nuget: CloneBox, 1.0.0"
#:package CloneBox@1.0.0
#addin nuget:?package=CloneBox&version=1.0.0
#tool nuget:?package=CloneBox&version=1.0.0
CloneBox
Cloning just works.
CloneX() deep-clones any .NET object graph. CloneXTo() copies it into another type — or into an instance you already have.
Most “cloners” handle a flat POCO and then fall apart: cycles overflow the stack, ExpandoObject keeps sharing nested instances, dictionaries lose their key types. Mappers and JSON round-trips are not cloners at all.
CloneBox is built for exactly those cases, backed by 280+ tests on .NET 10 and .NET Framework 4.8 / 4.7 — and it is the only library in the benchmark that gets every graph right, including cloning into an object you already have.
Free and open source — from the makers of ETLBox
CloneBox is built and maintained by ETLBoxperts GmbH, the company behind ETLBox, the code-first ETL and data integration library for .NET. We needed a cloner our own data flows could rely on, found none — so we built one and released it under the MIT license for the community. Discover ETLBox →
Install
dotnet add package CloneBox
<PackageReference Include="CloneBox" Version="*" />
Targets: netstandard2.0, net47, net48, net10.0.
Usage
Deep clone — CloneX()
using CloneBox;
var copy = original.CloneX();
That is a real deep clone: nested objects, collections, arrays, dictionaries, and cycles. The copy does not share identity with the source.
Clone into another object — CloneXTo()
CloneXTo() copies matching members onto a different type or onto an existing instance. Members that exist only on the target are left unchanged. [DoNotClone] is honored on the destination.
public class Address {
public string City { get; set; }
}
public class Customer {
public int Id { get; set; }
public string Name { get; set; }
public string Password { get; set; }
public Address Address { get; set; }
}
public class CustomerDto {
public int Id { get; set; }
public string Name { get; set; }
[DoNotClone] public string Password { get; set; }
public Address Address { get; set; }
public string ExtraOnTarget { get; set; }
}
Start with a filled source and a target that already has values you want to keep:
var source = new Customer {
Id = 1,
Name = "Ada",
Password = "secret",
Address = new Address { City = "Berlin" }
};
var target = new CustomerDto {
Password = "already-set",
ExtraOnTarget = "keep-me"
};
source.CloneXTo(target);
What happened, in order:
- Matching members —
IdandNameare copied ontotarget.Addressis a deep copy, not a shared reference. - Extra field on the destination —
ExtraOnTargetis still"keep-me". CloneBox does not clear members the source does not have. [DoNotClone]on the destination —Passwordis still"already-set". The source value"secret"is not written.
The same call works when source and target do not have the same shape at all.
ExpandoObject → existing DTO, matched by member name:
dynamic expando = new ExpandoObject();
expando.Id = 1;
expando.Name = "Ada";
expando.Password = "secret";
var dto = new CustomerDto { Password = "already-set", ExtraOnTarget = "keep-me" };
((ExpandoObject)expando).CloneXTo(dto);
// dto.Id == 1, dto.Name == "Ada"
// dto.Password == "already-set", dto.ExtraOnTarget == "keep-me"
POCO → ExpandoObject:
var customer = new Customer { Id = 1, Name = "Ada", Address = new Address { City = "Berlin" } };
var expando = new ExpandoObject();
customer.CloneXTo(expando);
dynamic result = expando;
// result.Id == 1, result.Name == "Ada"
// result.Address is a deep copy, not the same instance as customer.Address
List → Array, overlapping items only:
var sourceList = new List<int> { 1, 2, 3 };
var targetArray = new int[2];
sourceList.CloneXTo(targetArray);
// targetArray is { 1, 2 }
C# cannot dispatch extension methods on a
dynamicvariable. Keep the expando in anExpandoObjectvariable (or cast it) as shown above, or callCloneXExtensions.CloneXTo(source, target)directly.
Skip members
By default CloneBox copies every property and field it is allowed to see. Mark a member or a whole class with [DoNotClone] to leave it out — on CloneX() it stays at its default, on CloneXTo() the destination keeps its current value.
public class User {
public string Name { get; set; }
[DoNotClone] public string Password { get; set; }
}
var clone = user.CloneX();
// clone.Name == user.Name, clone.Password == null
When you cannot change the type — a third-party class, or a rule that spans many members — use predicates instead:
var clone = source.CloneX(new CloneSettings {
DoNotCloneProperty = p => p.Name == "Password" || p.Name == "Token",
DoNotCloneField = f => f.Name.StartsWith("_cache"),
DoNotCloneClass = t => t == typeof(Logger)
});
Settings
Defaults copy public and non-public properties and fields, and will use non-public constructors when needed. ICloneable is ignored unless you opt in.
var clone = source.CloneX(new CloneSettings {
IncludeNonPublicFields = false,
IncludeNonPublicProperties = false,
UseICloneableClone = true,
Logger = logger // Microsoft.Extensions.Logging
});
What gets cloned
- Object graphs with self-references and cycles — no stack overflow
- Lists, arrays (multi-dimensional and non-zero-based included) and dictionaries with their runtime key types
ExpandoObject/DynamicObject, including graphs that mix expandos and real classes- Inheritance, structs, built-in types, nested collections
This is covered by 280+ xUnit tests, run on net10.0, net48, and net47.
Benchmarks
CloneBox.Benchmark compares CloneBox with the widely used clone libraries (DeepCloner, FastDeepCloner, CloneExtensions, AnyClone) and, as contrast, Mapster, AutoMapper 14, and a Newtonsoft.Json round-trip. Each (library × scenario) runs in an isolated process so a stack overflow cannot take down the suite.
Four graphs:
| # | Scenario | What it checks |
|---|---|---|
| 1 | simple | Typed POCO, primitives, list, nested child · 1,000,000 clones |
| 2 | cyclic | 301 children in List/Array/Dictionary (int keys), byte[], SelfReference · 400 clones |
| 3 | dynamic | ExpandoObject as root: typed class, nested Expando, Parent/Self cycles · 300,000 clones |
| 4 | into | Expando → existing DTO: matching members, keep extra target fields, skip [DoNotClone] · 200,000 runs |
Representative Release / net10.0 run:
| Library | simple | cyclic | dynamic | into | |
|---|---|---|---|---|---|
| CloneBox | 0.35 µs | 0.33 ms | 1.92 µs | 0.91 µs | 4/4 |
| DeepCloner | 0.20 µs | 0.37 ms | 1.18 µs | — | 3/4 |
| FastDeepCloner | 2.76 µs | — | — | — | 1/4 |
| CloneExtensions | 0.17 µs | — | — | — | 1/4 |
| AnyClone | 3.40 µs | — | — | — | 1/4 |
| Mapster | 0.36 µs | — | — | — | 1/4 |
| AutoMapper | 0.16 µs | — | — | — | 1/4 |
| Newtonsoft.Json | 2.31 µs | — | — | — | 1/4 |
Times vary by machine — reproduce with dotnet run --project CloneBox.Benchmark -c Release.
How the libraries compare
The table measures two different jobs. simple / cyclic / dynamic are same-type deep clones; into is a clone onto an existing object of another type. A time is only listed when the result passed the checks — — means the copy was wrong or the process crashed, so speed there is meaningless.
Same-type clone (columns 1–3). CloneBox and DeepCloner both finish all three graphs, and DeepCloner is the faster one on the flat POCO and on Expando; on the cyclic graph they are in the same range. Everyone else breaks once the graph stops being a tree of POCOs:
| Library | What goes wrong |
|---|---|
| FastDeepCloner | no cycle tracking, so cyclic ends in a stack overflow |
| CloneExtensions | dictionary keys change type (Int32 → Object); throws on Expando |
| AnyClone | Parent and the class inside the Expando are still the original instances |
| Mapster | SelfReference and nested Expando members still point at the source |
| AutoMapper | same as Mapster, plus it cannot bind the Expando graph at all |
| Newtonsoft.Json | dictionary keys become string; the nested class on the Expando is gone |
Clone into another type (column 4). Only CloneBox produces a correct CustomerDto. DeepCloner’s DeepCloneTo requires the destination to inherit from the source, so ExpandoObject → CustomerDto does not even compile; FastDeepCloner and CloneExtensions clone the Expando into another Expando. Mapster, AutoMapper, and Newtonsoft.Json do return a DTO-shaped object, but they build a new instance instead of filling the one you passed in — which is why Password gets overwritten and ExtraOnTarget is lost.
In short: DeepCloner is a solid same-type cloner and wins on simple graphs, the mappers and the serializer were never meant to be cloners, and CloneBox is the only one that is correct on cycles, on dynamic objects, and on clone-into.
Who builds CloneBox
CloneBox is created and maintained by ETLBoxperts GmbH, the company behind ETLBox — a complete ETL and data integration library for .NET.
It started as our own problem. ETLBox moves records through data flows that work with strongly typed objects and ExpandoObject, and several components have to pass on a copy of a row rather than the row itself. We went looking for a cloning library, and none of them held up against real data: cycles ended in stack overflows, dynamic objects came back sharing their nested instances, dictionaries lost their key types. So we wrote our own and hardened it with the test suite in this repository.
We are releasing it under the MIT license because a dependable deep clone is something almost every .NET project runs into sooner or later, and the community has given us plenty over the years. This is real open source, not a trial version: use it, fork it, and issues and pull requests are welcome.
If you work with data in .NET, have a look at what we build for a living: www.etlbox.net. ETLBox is a code-first ETL toolbox — extract, transform and load across databases, files, APIs and streaming, with a parallel data-flow engine that handles datasets larger than memory. No GUI required, though DirectSync exists if you want one.
License
MIT — see LICENSE. Copyright ETLBoxperts GmbH.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 is compatible. net471 was computed. net472 was computed. net48 is compatible. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETFramework 4.7
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.1)
-
.NETFramework 4.8
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.1)
-
.NETStandard 2.0
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.1)
-
net10.0
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 45 | 9/27/2026 |
Initial release. Deep-clone any .NET object graph with CloneX(), or copy into another type or an existing instance with CloneXTo().