Skip to content

lofcz/FastCloner

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

FastCloner

FastCloner

The fastest and most reliable .NET deep cloning library.

FastCloner FastCloner License:MIT

The fastest deep cloning library, supporting anything from .NET 4.6 to modern .NET 10+ with no dependencies. FastCloner uses a unique source generator capable of analyzing object graphs and cloning objects without explicit annotations. For types that cannot be cloned, such as HttpClient, FastCloner uses a highly optimized reflection-based fallback. Zero dependencies, blazingly fast, built for developers who need cloning that just works.

✨ Features

  • The Fastest - Benchmarked to beat all other libraries with third-party independent benchmarks verifying the performance. 300x speed-up vs Newtonsoft.Json and 160x vs System.Text.Json
  • The Most Correct - Cloning objects is hard: <T>, abstract, immutables, read-only, pointers, circular dependencies, deeply nested graphs.. we have over 600 tests verifying correct behavior in these cases and we are transparent about the limitations
  • Novel Algorithm - FastCloner recognizes that certain cloning code cannot be generated in certain scenarios and uses highly optimized reflection-based approach instead for these types - this only happens for the members that need this, not entire objects
  • Zero-Overhead Abstractions - The generator uses call site analysis to eliminate indirection via inlining of generated methods. This ensures the generated code behaves like a single optimized block, just as if you hand-wrote it for maximum performance.
  • Embeddable - FastCloner has no dependencies outside the standard library. Source generator and reflection parts can be installed independently
  • Gentle & Caring - FastCloner detects standard attributes like [NonSerialized] making it easy to try without polluting codebase with custom attributes. Type usage graph for generics is built automatically producing performant cloning code without manual annotations
  • Easy Integration - FastDeepClone() for AOT cloning, DeepClone() for reflection cloning. That's it!
  • Production Ready - Used by projects like Jobbr, TarkovSP, and WinPaletter, with over 150K downloads on NuGet

Getting Started

Install the package via NuGet:

dotnet add package FastCloner # Reflection
dotnet add package FastCloner.SourceGenerator # AOT

Clone via Reflection

using FastCloner.Code;
var clone = FastCloner.FastCloner.DeepClone(new { Hello = "world", MyList = new List<int> { 1 } });

For convenience, add the following method to your project. We intentionally don't ship this extension to make switching from/to FastCloner easier:

[return: NotNullIfNotNull(nameof(obj))]
public static T? DeepClone<T>(this T? obj)
{
    return FastCloner.FastCloner.DeepClone(obj);
}

Clone via Source Generator

[FastClonerClonable]
public class GenericClass<T>
{
    public T Value { get; set; }
}

public class MyClass
{
    public string StrVal { get; set; }
}

// [FastClonerClonable] is only required on types where you call .FastDeepClone()
var original = new GenericClass<List<MyClass>> { Value = new List<MyClass> { new MyClass { StrVal = "hello world" } } };
var clone = original.FastDeepClone();

Advanced Usage

Sometimes, you might want to exclude certain fields/events/properties from cloning:

private class TestPropsWithIgnored
{
    [FastClonerIgnore] // <-- decorate with [FastClonerIgnore] or [NonSerialized]
    public string B { get; set; } = "My string";
    public int A { get; set; } = 10;
}

TestPropsWithIgnored original = new TestPropsWithIgnored { A = 42, B = "Test value" };
TestPropsWithIgnored clone = original.DeepClone(); // clone.B is null (default value of a given type)

You might also need to exclude certain types from ever being cloned. To do that, put offending types on a blacklist:

FastCloner.FastCloner.IgnoreType(typeof(PropertyChangedEventHandler)); // or FastCloner.FastCloner.IgnoreTypes([ .. ])

If needed, the types can be removed from the blacklist later:

// note: doing this invalidates precompiled expressions and clears the cache,
// performance will be negatively affected until the cache is repopulated
FastCloner.FastCloner.ClearIgnoredTypes();

Cache can be invalidated to reduce the memory footprint, if needed:

FastCloner.FastCloner.ClearCache();

Generic Classes and Abstract Types

The source generator automatically discovers which concrete types your generic classes and abstract hierarchies are used with:

Generic types - The generator scans your codebase for usages like MyClass<int> or MyClass<Customer> and generates specialized cloning code:

[FastClonerClonable]
public class Container<T>
{
    public T Value { get; set; }
}

// Source generator finds this usage and generates cloning code for Container<int>
var container = new Container<int> { Value = 42 };
var clone = container.FastDeepClone();

Abstract classes - The generator automatically finds all concrete derived types in your codebase:

[FastClonerClonable]
public abstract class Animal
{
    public string Name { get; set; }
}

public class Dog : Animal
{
    public string Breed { get; set; }
}

public class Cat : Animal
{
    public bool IsIndoor { get; set; }
}

// Cloning via the abstract type works - the generator discovered Dog and Cat
Animal pet = new Dog { Name = "Buddy", Breed = "Labrador" };
Animal clone = pet.FastDeepClone(); // Returns a cloned Dog

Explicitly Including Types

When a type is only used dynamically (not visible at compile time), use [FastClonerInclude] to ensure the generator creates cloning code for it:

[FastClonerClonable]
[FastClonerInclude(typeof(Customer), typeof(Order))] // Include types used dynamically
public class Wrapper<T>
{
    public T Value { get; set; }
}

For abstract classes, you can also use [FastClonerInclude] to add derived types that aren't in your codebase (e.g., from external assemblies):

[FastClonerClonable]
[FastClonerInclude(typeof(ExternalPlugin))] // Add external derived types
public abstract class Plugin
{
    public string Name { get; set; }
}

Custom Cloning Context

For advanced scenarios, create a custom cloning context to explicitly register types you want to clone. This is useful when you need a centralized cloning entry point or want to clone types from external assemblies:

public class Customer
{
    public string Name { get; set; }
    public Address Address { get; set; }
}

public class Address
{
    public string City { get; set; }
}

// Create a context and register types to clone
[FastClonerRegister(typeof(Customer), typeof(Address))]
public partial class MyCloningContext : FastClonerContext { }

Using the context:

MyCloningContext ctx = new MyCloningContext();

// Clone with compile-time type safety
Customer clone = ctx.Clone(original);

// Check if a type is handled by this context
bool handled = ctx.IsHandled(typeof(Customer)); // true

// Try to clone (returns false for unregistered types)
if (ctx.TryClone(obj, out var cloned))
{
    // Successfully cloned
}

Nullability Trust

The generator can be instructed to fully trust nullability annotations. When [FastClonerTrustNullability] attribute is applied, FastCloner will skip null checks for non-nullable reference types (e.g., string vs string?), assuming the contract is valid.

[FastClonerClonable]
[FastClonerTrustNullability] // Skip null checks for non-nullable members
public class HighPerformanceDto
{
    public string Id { get; set; } // No null check generated
    public string? Details { get; set; } // Null check still generated
}

This eliminates branching and improves performance slightly. If a non-nullable property is actually null at runtime, this may result in a NullReferenceException in the generated code.

Safe Handles

When you have a struct that acts as a handle to internal state or a singleton (where identity matters), use [FastClonerSafeHandle]. This tells FastCloner to shallow-copy the readonly fields instead of deep-cloning them, preserving the original internal references.

[FastClonerSafeHandle]
public struct MyHandle
{
    private readonly object _internalState; // Preserved (shared), not deep cloned
    public int Value; // Cloned normally
}

This is the default behavior for system types like System.Net.Http.Headers.HeaderDescriptor to prevent breaking internal framework logic. Use this attribute if your custom structs behave similarly.

Limitations

  • Cloning unmanaged resources, such as IntPtrs may result in side-effects, as there is no metadata for the length of buffers such pointers often point to.
  • ReadOnly and Immutable collections are tested to behave well if they follow basic conventions.
  • With reflection, cloning deeply nested objects switches from recursion to iterative approach on the fly. The threshold for this can be configured by changing FastCloner.MaxRecursionDepth, iterative approach is marginally slower.

Performance

FastCloner is the fastest deep cloning library. It was benchmarked against every library capable of cloning objects I've been able to find:

BenchmarkDotNet v0.15.8, Windows 11 (10.0.26220.7271)
Intel Core i7-8700 CPU 3.20GHz (Max: 3.19GHz) (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
.NET SDK 10.0.100

| Method             | Mean        | Error      | StdDev     | Median      | Ratio  | RatioSD | Rank | Gen0   | Gen1   | Allocated | Alloc Ratio |
|------------------- |------------:|-----------:|-----------:|------------:|-------:|--------:|-----:|-------:|-------:|----------:|------------:|
| FastCloner         |    10.25 ns |   0.219 ns |   0.183 ns |    10.24 ns |   1.00 |    0.02 |    1 | 0.0115 |      - |      72 B |        1.00 |
| DeepCopier         |    23.37 ns |   0.448 ns |   0.582 ns |    23.29 ns |   2.28 |    0.07 |    2 | 0.0115 |      - |      72 B |        1.00 |
| DeepCopy           |    40.56 ns |   3.589 ns |  10.583 ns |    43.56 ns |   3.96 |    1.03 |    3 | 0.0115 |      - |      72 B |        1.00 |
| DeepCopyExpression |   126.05 ns |   3.355 ns |   9.892 ns |   129.32 ns |  12.30 |    0.98 |    4 | 0.0356 |      - |     224 B |        3.11 |
| AutoMapper         |   135.07 ns |   6.097 ns |  17.976 ns |   143.16 ns |  13.18 |    1.76 |    5 | 0.0114 |      - |      72 B |        1.00 |
| DeepCloner         |   261.42 ns |  14.113 ns |  41.614 ns |   282.99 ns |  25.51 |    4.06 |    6 | 0.0367 |      - |     232 B |        3.22 |
| ObjectCloner       |   336.89 ns |  14.249 ns |  42.012 ns |   355.28 ns |  32.87 |    4.12 |    7 | 0.0534 |      - |     336 B |        4.67 |
| MessagePack        |   499.71 ns |  20.831 ns |  61.420 ns |   524.63 ns |  48.75 |    6.02 |    8 | 0.0315 |      - |     200 B |        2.78 |
| ProtobufNet        |   898.60 ns |  34.925 ns | 102.978 ns |   934.13 ns |  87.67 |   10.11 |    9 | 0.0782 |      - |     496 B |        6.89 |
| NClone             |   904.75 ns |  33.559 ns |  98.949 ns |   919.05 ns |  88.27 |    9.73 |    9 | 0.1488 |      - |     936 B |       13.00 |
| SystemTextJson     | 1,687.39 ns |  70.341 ns | 201.821 ns | 1,766.14 ns | 164.63 |   19.79 |   10 | 0.1755 |      - |    1120 B |       15.56 |
| NewtonsoftJson     | 3,147.66 ns | 109.097 ns | 321.676 ns | 3,269.96 ns | 307.10 |   31.67 |   11 | 0.7286 | 0.0038 |    4592 B |       63.78 |
| FastDeepCloner     | 3,970.90 ns | 155.503 ns | 458.505 ns | 4,128.09 ns | 387.41 |   45.01 |   12 | 0.2060 |      - |    1304 B |       18.11 |
| AnyCloneBenchmark  | 5,102.40 ns | 239.089 ns | 704.959 ns | 5,370.93 ns | 497.81 |   68.98 |   13 | 0.9003 |      - |    5656 B |       78.56 |

You can run the benchmark locally to verify the results. There are also third-party benchmarks in some of the competing libraries confirming these results.

Build Times & IDE Performance

FastCloner's source generator is carefully engineered for zero impact on IDE responsiveness and swift build times.

  • Tiered Caching: We use ForAttributeWithMetadataName for highly efficient filtering and strictly separate syntax analysis from code generation.
  • Smart Models: Roslyn symbols are immediately projected into lightweight, cache-friendly TypeModel records. The generator never holds onto compilation symbols, allowing the incremental pipeline to perfectly cache previous results.
  • No Compilation Trashing: We avoid expensive CompilationProvider combinations that break generator caching. Code generation only re-runs when your data models actually change, not on every keystroke or unrelated edit.
  • Allocation Free: EquatableArray collections ensure that change detection is instant and creates no garbage collection pressure.

Contributing

If you are looking to add new functionality, please open an issue first to verify your intent is aligned with the scope of the project. The library is covered by over 600 tests, please run them against your work before proposing changes. When reporting issues, providing a minimal reproduction we can plug in as a new test greatly reduces turnaround time.

License

This library is licensed under the MIT license. 💜