ktsu.DeepClone 2.0.4

Prefix Reserved
dotnet add package ktsu.DeepClone --version 2.0.4
                    
NuGet\Install-Package ktsu.DeepClone -Version 2.0.4
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ktsu.DeepClone" Version="2.0.4" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ktsu.DeepClone" Version="2.0.4" />
                    
Directory.Packages.props
<PackageReference Include="ktsu.DeepClone" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ktsu.DeepClone --version 2.0.4
                    
#r "nuget: ktsu.DeepClone, 2.0.4"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ktsu.DeepClone@2.0.4
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ktsu.DeepClone&version=2.0.4
                    
Install as a Cake Addin
#tool nuget:?package=ktsu.DeepClone&version=2.0.4
                    
Install as a Cake Tool

ktsu.DeepClone

A lightweight .NET library providing a simple, generic interface for implementing deep cloning functionality in your classes.

License NuGet NuGet Downloads Build Status GitHub Stars

Introduction

The ktsu.DeepClone library defines the IDeepCloneable interface, which allows you to create deep copies of objects. The DeepCloneable<TDerived> base class implements this interface while providing type-safe cloning. This is particularly useful in scenarios where you need to duplicate an object while ensuring that its references to other objects are also fully cloned, not just copied.

Inspired by and based on the ppy/osu! project's cloning utilities, this library is licensed under the MIT License.

Features

  • Generic and Non-Generic Interfaces: Works with any reference type (class)
  • Deep Cloning: Ensures that the cloned object is a completely independent copy, including all nested references
  • Inheritance Support: Easily implement deep cloning in class hierarchies
  • Circular References: Handles circular object references correctly without stack overflows or infinite loops
  • Collection Extensions: Deep clone collections including:
    • Standard collections (List, Dictionary, Array)
    • Specialized collections (HashSet, SortedSet, Stack, Queue, LinkedList)
    • Concurrent collections (ConcurrentDictionary)
    • Immutable collections (ImmutableArray, ImmutableList, ImmutableDictionary)
  • Thread Safety: Safe to use in multi-threaded environments
  • Performance: Designed for efficiency with minimal overhead
  • Lightweight: Minimal dependencies, focused on doing one thing well
  • Simple Integration: Easy to implement in your own classes
  • Comprehensive Testing: Extensive test coverage for reliability

Installation

Package Manager Console

Install-Package ktsu.DeepClone

.NET CLI

dotnet add package ktsu.DeepClone

Package Reference

<PackageReference Include="ktsu.DeepClone" Version="x.y.z" />

Usage Examples

Basic Example

using ktsu.DeepClone;

public class MyClass : DeepCloneable<MyClass>
{
    public int Value { get; set; }
    public MyClass? NestedObject { get; set; }

    protected override MyClass CreateInstance() => new MyClass();

    protected override void DeepClone(MyClass clone)
    {
        ArgumentNullException.ThrowIfNull(clone);
        base.DeepClone(clone);
        
        clone.Value = this.Value;
        clone.NestedObject = this.NestedObject?.DeepClone();
    }
}

You can then create deep copies of your objects:

var original = new MyClass
{
    Value = 42,
    NestedObject = new MyClass { Value = 84 }
};

var copy = original.DeepClone();

// The copy is a completely independent object
copy.Value = 100;
copy.NestedObject.Value = 200;

// Original remains unchanged
Console.WriteLine(original.Value); // Outputs: 42
Console.WriteLine(original.NestedObject.Value); // Outputs: 84

Understanding the Interface Structure

The library provides a non-generic interface for deep cloning:

// Non-generic interface - Allows polymorphic cloning that returns object
public interface IDeepCloneable
{
    object DeepClone();
}

The DeepCloneable<TDerived> base class implements this interface while providing type-safe cloning through its generic implementation. This allows classes that inherit from it to have strongly-typed DeepClone() methods that return their specific type, while still supporting polymorphic cloning through the IDeepCloneable interface.

Using the DeepCloneable Base Class

For simpler implementation, you can use the provided DeepCloneable<TDerived> abstract class which implements the non-generic IDeepCloneable interface:

public class Person : DeepCloneable<Person>
{
    public string? Name { get; set; }
    public int Age { get; set; }
    public List<string> Interests { get; set; } = new();
    
    // Required: Create a new instance
    protected override Person CreateInstance() => new Person();
    
    // Required: Copy your properties
    protected override void DeepClone(Person clone)
    {
        ArgumentNullException.ThrowIfNull(clone);
        
        clone.Name = Name;
        clone.Age = Age;
        
        // For collections, use the DeepCloneFrom extension method
        // This handles cloning of all elements in the collection
        clone.Interests.DeepCloneFrom(Interests);
    }
}

Understanding the Type-Safe CRTP Pattern

The library uses a C# implementation of the Curiously Recurring Template Pattern (CRTP) to provide type safety in inheritance hierarchies. This pattern allows derived classes to receive their actual concrete type in the DeepClone method without any casting.

// The generic type parameter TDerived is the derived class itself
public abstract class DeepCloneable<TDerived> : IDeepCloneable
    where TDerived : DeepCloneable<TDerived>
{
    // Public method that creates and returns a strongly typed clone
    public TDerived DeepClone()
    {
        // Create a new instance of the derived type using an abstract factory method
        TDerived clone = CreateInstance();
        
        // Call protected method to copy properties
        DeepClone(clone);
        
        return clone;
    }
    
    // Implementation of the non-generic interface
    object IDeepCloneable.DeepClone() => DeepClone();

    // To be implemented by derived classes to create instances of themselves
    protected abstract TDerived CreateInstance();

    // To be implemented by derived classes to copy properties
    protected virtual void DeepClone(TDerived clone) { }
}

With this pattern:

  1. The DeepClone() method returns the exact type of the derived class
  2. The DeepClone(TDerived clone) method receives a parameter of the derived type, not the base type
  3. No casting is required in the derived class's implementation

This ensures complete type safety throughout the inheritance chain.

Inheritance and Deep Cloning

When implementing deep cloning in a class hierarchy, it's critical to call base.DeepClone() first in derived classes:

// Base class
public abstract class Shape<T> : DeepCloneable<T> where T : Shape<T>
{
    public int Id { get; set; }
    public string? Name { get; set; }
    public List<string> Tags { get; set; } = new();
    
    protected override void DeepClone(T clone)
    {
        ArgumentNullException.ThrowIfNull(clone);
        
        // Clone base class properties
        clone.Id = Id;
        clone.Name = Name;
        
        // Use DeepCloneFrom for collections
        clone.Tags.DeepCloneFrom(Tags);
    }
}

// Derived class
public class Circle : Shape<Circle>
{
    public double Radius { get; set; }
    public Dictionary<string, double> Measurements { get; set; } = new();
    
    protected override Circle CreateInstance() => new Circle();
    
    protected override void DeepClone(Circle clone)
    {
        // IMPORTANT: Always call base.DeepClone first to clone base properties
        base.DeepClone(clone);
        
        // Then clone properties specific to this type
        clone.Radius = Radius;
        
        // Use DeepCloneFrom for collections
        clone.Measurements.DeepCloneFrom(Measurements);
    }
}

// Further derived class
public class ColoredCircle : Circle
{
    public string? Color { get; set; }
    public HashSet<string> ColorGradients { get; set; } = new();
    
    protected override ColoredCircle CreateInstance() => new ColoredCircle();
    
    protected override void DeepClone(ColoredCircle clone)
    {
        // Call base first to handle Circle and Shape properties
        base.DeepClone(clone);
        
        // Then clone ColoredCircle specific properties
        clone.Color = Color;
        
        // Use DeepCloneFrom for collections
        clone.ColorGradients.DeepCloneFrom(ColorGradients);
    }
}

Note that each derived class's DeepClone() method receives a parameter of its own type:

  • Shape<T>.DeepClone(T clone) receives a T
  • Circle.DeepClone(Circle clone) receives a Circle
  • ColoredCircle.DeepClone(ColoredCircle clone) receives a ColoredCircle

This is achieved through the CRTP pattern and means that no casting is needed in any of these methods.

Usage:

var original = new ColoredCircle
{
    Id = 1,
    Name = "My Circle",
    Tags = { "round", "geometric" },
    Radius = 10.5,
    Measurements = { ["diameter"] = 21.0 },
    Color = "Blue",
    ColorGradients = { "lightblue", "darkblue" }
};

var clone = original.DeepClone();

// All properties from the entire hierarchy are cloned
Console.WriteLine(clone.Id);      // 1
Console.WriteLine(clone.Name);    // My Circle
Console.WriteLine(clone.Tags[0]); // round
Console.WriteLine(clone.Radius);  // 10.5
Console.WriteLine(clone.Color);   // Blue

Polymorphic Deep Cloning with Common Ancestors

When working with inheritance hierarchies that have multiple derived types from a common ancestor, you need to carefully implement deep cloning to maintain polymorphism. This is especially important when dealing with collections of different derived types.

The recommended approach is to use the CRTP pattern throughout the inheritance hierarchy along with interface hierarchies for polymorphism:

// Non-generic interface for all animals to enable polymorphism
public interface IAnimal
{
    string? Name { get; set; }
    int Age { get; set; }
}

// Base class in the hierarchy using CRTP pattern
public abstract class Animal<TDerived> : DeepCloneable<TDerived>, IAnimal
    where TDerived : Animal<TDerived>
{
    public string? Name { get; set; }
    public int Age { get; set; }

    protected override void DeepClone(TDerived clone)
    {
        ArgumentNullException.ThrowIfNull(clone);
        base.DeepClone(clone);
        clone.Name = Name;
        clone.Age = Age;
    }
}

// Interface for mammals, extending the animal interface
public interface IMammal : IAnimal
{
    int NumberOfLegs { get; set; }
    string? FurColor { get; set; }
}

// Intermediate class for mammals, continuing the CRTP pattern
public abstract class Mammal<TDerived> : Animal<TDerived>, IMammal
    where TDerived : Mammal<TDerived>
{
    public int NumberOfLegs { get; set; }
    public string? FurColor { get; set; }

    protected override void DeepClone(TDerived clone)
    {
        ArgumentNullException.ThrowIfNull(clone);
        base.DeepClone(clone);
        clone.NumberOfLegs = NumberOfLegs;
        clone.FurColor = FurColor;
    }
}

// Interface for dogs
public interface IDog : IMammal
{
    string? Breed { get; set; }
    bool IsTrained { get; set; }
}

// Concrete implementation - Dog
public class Dog : Mammal<Dog>, IDog
{
    public string? Breed { get; set; }
    public bool IsTrained { get; set; }

    protected override Dog CreateInstance() => new Dog();

    protected override void DeepClone(Dog clone)
    {
        ArgumentNullException.ThrowIfNull(clone);
        base.DeepClone(clone);
        clone.Breed = Breed;
        clone.IsTrained = IsTrained;
    }
}

// Interface for cats
public interface ICat : IMammal
{
    bool IsIndoor { get; set; }
    string? FavoriteToy { get; set; }
}

// Another concrete implementation - Cat
public class Cat : Mammal<Cat>, ICat
{
    public bool IsIndoor { get; set; }
    public string? FavoriteToy { get; set; }

    protected override Cat CreateInstance() => new Cat();

    protected override void DeepClone(Cat clone)
    {
        ArgumentNullException.ThrowIfNull(clone);
        base.DeepClone(clone);
        clone.IsIndoor = IsIndoor;
        clone.FavoriteToy = FavoriteToy;
    }
}

// Container for a collection of polymorphic animals using the common interface
public class AnimalShelter : DeepCloneable<AnimalShelter>
{
    // Collection of animals using the common interface for polymorphism
    public Collection<IAnimal> Animals { get; } = [];

    protected override AnimalShelter CreateInstance() => new AnimalShelter();

    protected override void DeepClone(AnimalShelter clone)
    {
        ArgumentNullException.ThrowIfNull(clone);
        base.DeepClone(clone);
        clone.Animals.DeepCloneFrom(Animals);
    }
}

With this approach:

  1. Each class in the hierarchy uses CRTP for type safety
  2. Each level has a corresponding interface (IAnimal, IMammal, IDog) for polymorphism
  3. No casting is needed in the DeepClone methods because of CRTP
  4. The AnimalShelter uses Collection<IAnimal> for polymorphic access to any animal type

Usage example with different animal types:

// Create animals
var dog = new Dog 
{ 
    Name = "Buddy", 
    Age = 3, 
    NumberOfLegs = 4, 
    FurColor = "Golden", 
    Breed = "Labrador", 
    IsTrained = true 
};

var cat = new Cat 
{ 
    Name = "Whiskers", 
    Age = 2, 
    NumberOfLegs = 4, 
    FurColor = "Tabby", 
    IsIndoor = true, 
    FavoriteToy = "Mouse" 
};

// Create the shelter and add animals through the common interface
var shelter = new AnimalShelter();
shelter.Animals.Add(dog);   // Adding a Dog as IAnimal
shelter.Animals.Add(cat);   // Adding a Cat as IAnimal

// Deep clone the shelter and all its animals
var clonedShelter = shelter.DeepClone();

// The cloned animals maintain their concrete types through the interfaces
var clonedDog = clonedShelter.Animals[0] as Dog;  // Type checking with 'as'
var clonedCat = clonedShelter.Animals[1] as Cat;

// Access specific derived type properties
Console.WriteLine(clonedDog?.Breed);        // "Labrador"
Console.WriteLine(clonedCat?.FavoriteToy);  // "Mouse"

// Modifying the clone doesn't affect the original
clonedDog!.Breed = "Golden Retriever";
clonedCat!.FavoriteToy = "Ball";

Console.WriteLine(dog.Breed);        // Still "Labrador"
Console.WriteLine(cat.FavoriteToy);  // Still "Mouse"

Working with Immutable Collections

Immutable collections require special handling. The library provides extension methods for common immutable collection types:

public class DocumentWithImmutables : DeepCloneable<DocumentWithImmutables>
{
    public string? Title { get; set; }
    public ImmutableArray<string> Keywords { get; set; }
    public ImmutableList<Reference> References { get; set; }
    public ImmutableDictionary<string, Author> Authors { get; set; }
    
    protected override DocumentWithImmutables CreateInstance() => new DocumentWithImmutables();
    
    protected override void DeepClone(DocumentWithImmutables clone)
    {
        ArgumentNullException.ThrowIfNull(clone);
        
        clone.Title = Title;
        
        // For immutable collections of value types, direct assignment is fine
        clone.Keywords = Keywords;
        
        // For immutable collections of reference types, use DeepClone extension method
        clone.References = References.DeepClone();
        clone.Authors = Authors.DeepClone();
    }
}

public class Reference : DeepCloneable<Reference>
{
    public string? Citation { get; set; }
    public int Year { get; set; }
    
    protected override Reference CreateInstance() => new Reference();
    
    protected override void DeepClone(Reference clone)
    {
        ArgumentNullException.ThrowIfNull(clone);
        
        clone.Citation = Citation;
        clone.Year = Year;
    }
}

public class Author : DeepCloneable<Author>
{
    public string? Name { get; set; }
    
    protected override Author CreateInstance() => new Author();
    
    protected override void DeepClone(Author clone)
    {
        ArgumentNullException.ThrowIfNull(clone);
        
        clone.Name = Name;
    }
}

Advanced Usage with Collections

public class ComplexObject : DeepCloneable<ComplexObject>
{
    public string? Name { get; set; }
    public List<ItemObject> Items { get; set; } = new();
    public Dictionary<string, DataObject> DataMapping { get; set; } = new();
    public HashSet<int> UniqueIds { get; set; } = new();

    protected override ComplexObject CreateInstance() => new ComplexObject();
    
    protected override void DeepClone(ComplexObject clone)
    {
        ArgumentNullException.ThrowIfNull(clone);
        
        clone.Name = Name;

        // Use DeepCloneFrom for all collections - this handles cloning correctly
        clone.Items.DeepCloneFrom(Items);
        clone.DataMapping.DeepCloneFrom(DataMapping);
        
        // Even for value type collections, DeepCloneFrom can be used for consistency
        // This is equivalent to copying each element manually
        clone.UniqueIds.DeepCloneFrom(UniqueIds);
    }
}

public class ItemObject : DeepCloneable<ItemObject>
{
    public int Id { get; set; }
    public string? Description { get; set; }

    protected override ItemObject CreateInstance() => new ItemObject();
    
    protected override void DeepClone(ItemObject clone)
    {
        ArgumentNullException.ThrowIfNull(clone);
        
        clone.Id = Id;
        clone.Description = Description;
    }
}

public class DataObject : DeepCloneable<DataObject>
{
    public byte[]? Data { get; set; }

    protected override DataObject CreateInstance() => new DataObject();
    
    protected override void DeepClone(DataObject clone)
    {
        ArgumentNullException.ThrowIfNull(clone);
        
        // For arrays, create a new copy
        clone.Data = Data?.ToArray();
    }
}

API Reference

IDeepCloneable Interface

The non-generic interface for implementing deep cloning functionality.

Methods
Name Return Type Description
DeepClone() object Creates a deep copy of the object, returned as an object that can be cast to the appropriate type

DeepCloneable<TDerived> Abstract Class

Base class that simplifies implementing deep cloning functionality and implements IDeepCloneable while providing strongly-typed cloning.

Methods to Implement
Name Return Type Description
CreateInstance() TDerived Creates a new instance of the derived type
DeepClone(TDerived clone) void Copies all properties and fields from the current instance to the clone

Container Extension Methods

Extension methods for deep cloning various container types.

Name Container Type Description
DeepClone<T> IEnumerable<T?>? Creates a deep clone of an enumerable collection
DeepClone<TKey, TValue> IDictionary<TKey, TValue?>? Creates a deep clone of a dictionary
DeepClone<TKey, TValue> IReadOnlyDictionary<TKey, TValue?>? Creates a deep clone of a read-only dictionary
DeepClone<T> HashSet<T?>? Creates a deep clone of a hash set, preserving the comparer
DeepClone<T> SortedSet<T?>? Creates a deep clone of a sorted set, preserving the comparer
DeepClone<T> Stack<T?>? Creates a deep clone of a stack, preserving the element order
DeepClone<T> ImmutableArray<T> Creates a deep clone of an immutable array
DeepClone<T> ImmutableList<T> Creates a deep clone of an immutable list
DeepClone<TKey, TValue> ImmutableDictionary<TKey, TValue> Creates a deep clone of an immutable dictionary
DeepClone<TKey, TValue> ImmutableSortedDictionary<TKey, TValue> Creates a deep clone of an immutable sorted dictionary
DeepClone<T> ImmutableHashSet<T> Creates a deep clone of an immutable hash set
DeepClone<T> ImmutableSortedSet<T> Creates a deep clone of an immutable sorted set
DeepClone<T> ImmutableStack<T> Creates a deep clone of an immutable stack
DeepClone<T> ImmutableQueue<T> Creates a deep clone of an immutable queue

DeepCloneFrom Extension Methods

Convenience methods for cloning elements directly into an existing collection.

Name Collection Types Description
DeepCloneFrom<T> ICollection<T> Clones all elements from source collection into the target collection
DeepCloneFrom<TKey, TValue> IDictionary<TKey, TValue> Clones all key-value pairs from source dictionary into the target dictionary
DeepCloneFrom<T> ISet<T> Clones all elements from source set into the target set

Usage Example:

// For collections of IDeepCloneable objects
targetList.DeepCloneFrom(sourceList);
targetDictionary.DeepCloneFrom(sourceDictionary);
targetSet.DeepCloneFrom(sourceSet);

// This is equivalent to (but more concise than):
foreach (var item in sourceList)
{
    targetList.Add(item.DeepClone());
}

Importance of base.DeepClone in Inheritance

When implementing DeepClone in derived classes, it's critical to call base.DeepClone(clone) first to ensure proper cloning of base class properties. This pattern creates a chain of calls that ensures all properties throughout the inheritance hierarchy are properly cloned.

protected override void DeepClone(MyDerivedClass clone)
{
    // ALWAYS call base.DeepClone first
    base.DeepClone(clone);
    
    // Then clone derived class properties
    clone.DerivedProperty1 = DerivedProperty1;
    clone.DerivedProperty2 = DerivedProperty2?.DeepClone();
    
    // Use DeepCloneFrom for collections
    clone.DerivedCollection.DeepCloneFrom(DerivedCollection);
}

Benefits of this approach:

  • Each class is responsible only for cloning its own properties
  • Changes to base class properties only need to be updated in the base class
  • The entire object graph is properly cloned through the inheritance chain
  • Maintainable and less error-prone as the hierarchy evolves

Implementation Tips

When implementing deep cloning, follow these guidelines:

  1. For value types and strings, a simple copy is sufficient
  2. For reference types, call their DeepClone() method (with null check)
  3. For collections, use the DeepCloneFrom extension methods
  4. Always call base.DeepClone(clone) first in derived classes
  5. For arrays, create a new array with the same dimensions
  6. Handle circular references to avoid infinite recursion
  7. Always check for null with ArgumentNullException.ThrowIfNull or null conditionals

Performance Considerations

Deep cloning can be a resource-intensive operation, especially for complex object graphs. Here are some tips to optimize performance:

  1. Lazy Clone When Possible: If a property is expensive to clone and rarely modified, consider implementing lazy cloning.
  2. Avoid Excessive Nesting: Highly nested object structures with many references are more expensive to clone.
  3. Consider Caching: For frequently cloned immutable objects, consider caching the cloned instances.
  4. Use ValueType Collections Wisely: Collections of value types don't need full deep cloning.
  5. Benchmark Critical Paths: If deep cloning is on a performance-critical path, benchmark different approaches.

Example of optimized cloning for a large object:

public class OptimizedObject : DeepCloneable<OptimizedObject>
{
    // Expensive to clone, but rarely modified
    private ExpensiveResource? _resource;
    private ExpensiveResource? _clonedResource;
    
    public ExpensiveResource? Resource 
    { 
        get => _resource;
        set 
        {
            _resource = value;
            _clonedResource = null; // Invalidate cached clone
        }
    }

    protected override OptimizedObject CreateInstance() => new OptimizedObject();
    
    protected override void DeepClone(OptimizedObject clone)
    {
        ArgumentNullException.ThrowIfNull(clone);
        
        // Lazy clone the expensive resource only when needed
        if (_resource != null)
        {
            // Cache the cloned resource to avoid redundant deep cloning
            _clonedResource ??= _resource.DeepClone();
            clone._resource = _clonedResource;
        }
    }
}

Troubleshooting Common Issues

1. Objects Not Fully Independent After Cloning

Symptom: Changes to a cloned object affect the original object.

Solution: Ensure all reference-type properties are being properly deep cloned. Check for:

  • Missing DeepClone calls on reference-type properties
  • Not using DeepCloneFrom for collections
  • Forgetting to call base.DeepClone in derived classes

2. Stack Overflow During Cloning

Symptom: StackOverflowException occurs during deep cloning.

Solution: This typically indicates a circular reference issue:

  • Implement circular reference detection in your DeepClone method
  • Use a Dictionary to track already-cloned objects
public class NodeWithCircularReferences : DeepCloneable<NodeWithCircularReferences>
{
    public string? Name { get; set; }
    public NodeWithCircularReferences? Parent { get; set; }
    public List<NodeWithCircularReferences> Children { get; set; } = new();
    
    // Dictionary for tracking already cloned instances across the object graph
    private static readonly ThreadLocal<Dictionary<NodeWithCircularReferences, NodeWithCircularReferences>> CloneTracker = 
        new(() => new Dictionary<NodeWithCircularReferences, NodeWithCircularReferences>());
    
    protected override NodeWithCircularReferences CreateInstance() => new();
    
    protected override void DeepClone(NodeWithCircularReferences clone)
    {
        ArgumentNullException.ThrowIfNull(clone);
        base.DeepClone(clone);
        
        // Ensure we have a clone tracker
        var cloneTracker = CloneTracker.Value!;
        
        // Track this instance and its clone
        cloneTracker[this] = clone;
        
        // Set simple properties
        clone.Name = Name;
        
        // Handle potential circular reference with Parent
        if (Parent != null)
        {
            // Check if parent was already cloned in this cloning operation
            if (cloneTracker.TryGetValue(Parent, out var parentClone))
                clone.Parent = parentClone;
            else
                clone.Parent = Parent.DeepClone(); // This will add to the tracker
        }
        
        // Handle collection of children that might have circular references
        foreach (var child in Children)
        {
            // Check if child was already cloned in this operation
            if (cloneTracker.TryGetValue(child, out var childClone))
                clone.Children.Add(childClone);
            else
                clone.Children.Add(child.DeepClone()); // This will add to the tracker
        }
    }
}

3. Type Mismatch in Polymorphic Collections

Symptom: After cloning a polymorphic collection, objects don't maintain their derived types.

Solution: Ensure you're implementing IDeepCloneable correctly for polymorphic use:

  • For base classes inheriting from DeepCloneable<TDerived>, the non-generic interface is already implemented
  • Override CreateInstance() to return the correct concrete type
  • Use proper type checking and casting in DeepClone methods if needed
  • Consider using interface hierarchies (like IAnimal, IMammal, IDog) for type-safe polymorphism

4. Immutable Collection Cloning Issues

Symptom: Immutable collections don't clone properly or reference equality is maintained.

Solution: Use the appropriate extension methods specific to immutable collections:

  • For value-type immutable collections, direct assignment is usually sufficient
  • For reference-type immutable collections, use the DeepClone extension methods
  • Never manually iterate and rebuild immutable collections

Contributing

Contributions are welcome! Here's how you can help:

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Please make sure to update tests as appropriate and adhere to the existing coding style.

License

This project is licensed under the MIT License - see the LICENSE.md file for details.

Acknowledgments

This library is inspired by the ppy/osu! project's cloning utilities.

Product Compatible and additional computed target framework versions.
.NET net5.0 is compatible.  net5.0-windows was computed.  net6.0 is compatible.  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 is compatible.  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 is compatible.  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 is compatible.  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 was computed.  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 is compatible. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.
  • .NETStandard 2.1

    • No dependencies.
  • net5.0

    • No dependencies.
  • net6.0

    • No dependencies.
  • net7.0

    • No dependencies.
  • net8.0

    • No dependencies.
  • net9.0

    • No dependencies.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on ktsu.DeepClone:

Package Downloads
ktsu.Coder.Core

Placeholder description, a single line concise description of the project, suitable for the package description in the nuget list UI.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.4 168 8/25/2025
2.0.3 59 8/16/2025
2.0.2 268 5/22/2025
2.0.1 2,764 5/21/2025
2.0.1-pre.1 131 5/20/2025
1.3.2-pre.7 64 5/9/2025
1.3.2-pre.6 143 5/8/2025
1.3.2-pre.5 144 5/7/2025
1.3.2-pre.4 132 5/6/2025
1.3.2-pre.3 135 5/5/2025
1.3.2-pre.2 127 5/4/2025
1.3.2-pre.1 129 5/4/2025
1.3.1 122 5/4/2025
1.3.1-pre.2 68 4/26/2025
1.3.1-pre.1 123 4/4/2025
1.3.0 2,734 3/30/2025
1.2.1-pre.2 85 3/29/2025
1.2.1-pre.1 472 3/25/2025
1.2.0 4,024 2/17/2025
1.1.17-pre.3 87 2/6/2025
1.1.17-pre.2 78 2/5/2025
1.1.17-pre.1 84 2/5/2025
1.1.16 285 1/2/2025
1.1.16-pre.29 79 2/4/2025
1.1.16-pre.28 72 2/3/2025
1.1.16-pre.27 80 2/1/2025
1.1.16-pre.26 80 1/30/2025
1.1.16-pre.25 79 1/28/2025
1.1.16-pre.24 73 1/26/2025
1.1.16-pre.23 64 1/24/2025
1.1.16-pre.22 83 1/22/2025
1.1.16-pre.21 77 1/20/2025
1.1.16-pre.20 73 1/18/2025
1.1.16-pre.19 65 1/16/2025
1.1.16-pre.18 50 1/14/2025
1.1.16-pre.17 64 1/13/2025
1.1.16-pre.16 73 1/11/2025
1.1.16-pre.15 68 1/10/2025
1.1.16-pre.14 76 1/10/2025
1.1.16-pre.13 66 1/8/2025
1.1.16-pre.12 80 1/7/2025
1.1.16-pre.11 75 1/6/2025
1.1.16-pre.10 97 1/4/2025
1.1.16-pre.9 84 1/3/2025
1.1.16-pre.8 89 1/3/2025
1.1.16-pre.7 80 1/3/2025
1.1.16-pre.6 93 1/2/2025
1.1.16-pre.5 99 12/31/2024
1.1.16-pre.4 84 12/29/2024
1.1.16-pre.3 74 12/28/2024
1.1.16-pre.2 79 12/27/2024
1.1.16-pre.1 76 12/27/2024
1.1.15-pre.1 77 12/27/2024
1.1.14 4,194 12/26/2024
1.1.13 117 12/26/2024
1.1.12 113 12/26/2024
1.1.11 113 12/26/2024
1.1.10 107 12/26/2024
1.1.10-pre.1 78 12/27/2024
1.1.9 119 12/26/2024
1.1.8 123 12/26/2024
1.1.7 1,833 12/23/2024
1.1.6 118 12/23/2024
1.1.5 581 12/22/2024
1.1.4 198 12/22/2024
1.1.3 992 12/12/2024
1.1.2 683 12/4/2024
1.1.1 584 12/2/2024
1.1.0 130 12/2/2024
1.0.18 116 12/2/2024
1.0.17 472 11/30/2024
1.0.16 420 11/26/2024
1.0.15 352 11/20/2024
1.0.14 1,089 11/13/2024
1.0.13 977 11/1/2024
1.0.12 1,200 10/15/2024
1.0.11 758 10/4/2024
1.0.10 907 9/19/2024
1.0.9 323 9/19/2024
1.0.8 115 9/19/2024
1.0.7 401 9/19/2024
1.0.6 139 9/18/2024
1.0.5 138 9/18/2024
1.0.4 134 9/18/2024
1.0.3 373 9/18/2024
1.0.2 875 9/14/2024
1.0.1 143 9/14/2024

## v2.0.4 (patch)

Changes since v2.0.3:

- Migrate to PolySharp polyfills ([@matt-edmondson](https://github.com/matt-edmondson))
## v2.0.3 (patch)

Changes since v2.0.2:

- Add project configuration files and refactor DeepClone project structure ([@matt-edmondson](https://github.com/matt-edmondson))
- Enhance CI/CD workflows and update SDK management ([@matt-edmondson](https://github.com/matt-edmondson))
- Refactor DeepClone project and update dependencies ([@matt-edmondson](https://github.com/matt-edmondson))
- Update configuration files and improve build scripts ([@matt-edmondson](https://github.com/matt-edmondson))
## v2.0.2 (patch)

Changes since v2.0.1:

- Update SDK version and improve code readability ([@matt-edmondson](https://github.com/matt-edmondson))
## v2.0.1 (patch)

Changes since v2.0.0:

- [patch] Fix version ([@matt-edmondson](https://github.com/matt-edmondson))
## v2.0.1-pre.1 (prerelease)

Incremental prerelease update.
## v2.0.0 (major)

Changes since 1.0.0:

- Refactor Get-VersionNotes function to improve commit message filtering by replacing pipeline usage with a foreach loop. This change enhances clarity and performance in handling version updates and skip conditions. ([@matt-edmondson](https://github.com/matt-edmondson))
- Update VERSION ([@matt-edmondson](https://github.com/matt-edmondson))
- Update repository URL in README.md ([@matt-edmondson](https://github.com/matt-edmondson))
- Remove unnecessary PackageReference from DeepClone.Test project ([@matt-edmondson](https://github.com/matt-edmondson))
- Replace LICENSE file with LICENSE.md and update copyright information ([@matt-edmondson](https://github.com/matt-edmondson))
- Enhance PSBuild module documentation and functions. Updated function descriptions for clarity, added examples, and improved parameter details across various functions. ([@matt-edmondson](https://github.com/matt-edmondson))
- Migrate ktsu.io to ktsu namespace ([@matt-edmondson](https://github.com/matt-edmondson))
- Remove Directory.Build.props and Directory.Build.targets files, delete unused scripts for managing metadata, changelog, license, and versioning. Add copyright headers to IDeepClonable.cs and TestCloneable.cs. ([@matt-edmondson](https://github.com/matt-edmondson))
- Add LICENSE template ([@matt-edmondson](https://github.com/matt-edmondson))
- Refine Get-BuildConfiguration function to ensure commit messages are processed as an array, improving the filtering of non-skip commits. This update enhances the accuracy of release eligibility checks by handling string outputs more effectively. ([@matt-edmondson](https://github.com/matt-edmondson))
- Fix changelog script to handle prerelease version drops correctly ([@matt-edmondson](https://github.com/matt-edmondson))
- [major] Complete rewrite ([@matt-edmondson](https://github.com/matt-edmondson))
- Renamed metadata files ([@matt-edmondson](https://github.com/matt-edmondson))
- Add scripts for automated metadata generation and versioning ([@matt-edmondson](https://github.com/matt-edmondson))
- Add test project, update README, and remove .gitattributes ([@matt-edmondson](https://github.com/matt-edmondson))
- Update test method names and replace LICENSE file ([@matt-edmondson](https://github.com/matt-edmondson))
- Apply new editorconfig ([@matt-edmondson](https://github.com/matt-edmondson))
- Refactor Get-BuildConfiguration function to ensure proper array handling of commit messages. This change guarantees accurate filtering of non-skip commits by enforcing array treatment, enhancing the reliability of release eligibility checks. ([@matt-edmondson](https://github.com/matt-edmondson))
- Add mailmap ([@matt-edmondson](https://github.com/matt-edmondson))
- Update project metadata and enhance documentation with detailed usage examples and API reference ([@matt-edmondson](https://github.com/matt-edmondson))
## v1.3.2-pre.7 (prerelease)

Changes since v1.3.2-pre.6:
## v1.3.2-pre.6 (prerelease)

Changes since v1.3.2-pre.5:
## v1.3.2-pre.5 (prerelease)

Changes since v1.3.2-pre.4:
## v1.3.2-pre.4 (prerelease)

Changes since v1.3.2-pre.3:
## v1.3.2-pre.3 (prerelease)

Changes since v1.3.2-pre.2:
## v1.3.2-pre.2 (prerelease)

Changes since v1.3.2-pre.1:
## v1.3.2-pre.1 (prerelease)

Changes since v1.3.1:

- Sync scripts\PSBuild.psm1 ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.3.1 (patch)

Changes since v1.3.0:

- Remove Directory.Build.props and Directory.Build.targets files, delete unused scripts for managing metadata, changelog, license, and versioning. Add copyright headers to IDeepClonable.cs and TestCloneable.cs. ([@matt-edmondson](https://github.com/matt-edmondson))
- Update project metadata and enhance documentation with detailed usage examples and API reference ([@matt-edmondson](https://github.com/matt-edmondson))
## v1.3.1-pre.2 (prerelease)

Changes since v1.3.1-pre.1:

- Sync .runsettings ([@ktsu[bot]](https://github.com/ktsu[bot]))
- Sync .github\workflows\dotnet.yml ([@ktsu[bot]](https://github.com/ktsu[bot]))
- Sync .editorconfig ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.3.1-pre.1 (prerelease)

Incremental prerelease update.
## v1.3.0 (minor)

Changes since v1.2.0:

- Add LICENSE template ([@matt-edmondson](https://github.com/matt-edmondson))
## v1.2.1-pre.2 (prerelease)

Changes since v1.2.1-pre.1:

- Sync scripts\make-version.ps1 ([@ktsu[bot]](https://github.com/ktsu[bot]))
- Sync scripts\make-changelog.ps1 ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.2.1-pre.1 (prerelease)

Changes since v1.2.0:

- Sync .gitignore ([@ktsu[bot]](https://github.com/ktsu[bot]))
- Sync Directory.Build.targets ([@ktsu[bot]](https://github.com/ktsu[bot]))
- Sync .editorconfig ([@ktsu[bot]](https://github.com/ktsu[bot]))
- Sync scripts\make-version.ps1 ([@ktsu[bot]](https://github.com/ktsu[bot]))
- Sync scripts\make-changelog.ps1 ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.2.0 (minor)

Changes since v1.1.0:

- Remove unnecessary PackageReference from DeepClone.Test project ([@matt-edmondson](https://github.com/matt-edmondson))
- Replace LICENSE file with LICENSE.md and update copyright information ([@matt-edmondson](https://github.com/matt-edmondson))
- Fix changelog script to handle prerelease version drops correctly ([@matt-edmondson](https://github.com/matt-edmondson))
- Renamed metadata files ([@matt-edmondson](https://github.com/matt-edmondson))
- Add scripts for automated metadata generation and versioning ([@matt-edmondson](https://github.com/matt-edmondson))
- Update test method names and replace LICENSE file ([@matt-edmondson](https://github.com/matt-edmondson))
- Apply new editorconfig ([@matt-edmondson](https://github.com/matt-edmondson))
- Add mailmap ([@matt-edmondson](https://github.com/matt-edmondson))
## v1.1.17-pre.3 (prerelease)

Changes since v1.1.17-pre.2:

- Sync scripts\make-version.ps1 ([@ktsu[bot]](https://github.com/ktsu[bot]))
- Sync scripts\make-changelog.ps1 ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.1.17-pre.2 (prerelease)

Changes since v1.1.17-pre.1:

- Sync .github\workflows\dotnet.yml ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.1.17-pre.1 (prerelease)

Changes since v1.1.16:

- Fix changelog script to handle prerelease version drops correctly ([@matt-edmondson](https://github.com/matt-edmondson))
- Add mailmap ([@matt-edmondson](https://github.com/matt-edmondson))
## v1.1.16 (patch)

No significant changes detected since v1.1.16-pre.29.
## v1.1.16-pre.29 (prerelease)

Changes since v1.1.16-pre.28:

- Sync .github\workflows\dotnet.yml ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.1.16-pre.28 (prerelease)

Changes since v1.1.16-pre.27:

- Sync .github\workflows\dotnet.yml ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.1.16-pre.27 (prerelease)

Changes since v1.1.16-pre.26:
## v1.1.16-pre.26 (prerelease)

Changes since v1.1.16-pre.25:
## v1.1.16-pre.25 (prerelease)

Changes since v1.1.16-pre.24:

- Bump MSTest from 3.7.2 to 3.7.3 ([@dependabot[bot]](https://github.com/dependabot[bot]))
## v1.1.16-pre.24 (prerelease)

Changes since v1.1.16-pre.23:
## v1.1.16-pre.23 (prerelease)

Changes since v1.1.16-pre.22:
## v1.1.16-pre.22 (prerelease)

Changes since v1.1.16-pre.21:

- Bump MSTest from 3.7.1 to 3.7.2 ([@dependabot[bot]](https://github.com/dependabot[bot]))
## v1.1.16-pre.21 (prerelease)

Changes since v1.1.16-pre.20:

- Bump coverlet.collector from 6.0.3 to 6.0.4 ([@dependabot[bot]](https://github.com/dependabot[bot]))
## v1.1.16-pre.20 (prerelease)

Changes since v1.1.16-pre.19:
## v1.1.16-pre.19 (prerelease)

Changes since v1.1.16-pre.18:
## v1.1.16-pre.18 (prerelease)

Changes since v1.1.16-pre.17:

- Bump MSTest from 3.7.0 to 3.7.1 ([@dependabot[bot]](https://github.com/dependabot[bot]))
## v1.1.16-pre.17 (prerelease)

Changes since v1.1.16-pre.16:
## v1.1.16-pre.16 (prerelease)

Changes since v1.1.16-pre.15:
## v1.1.16-pre.15 (prerelease)

Changes since v1.1.16-pre.14:

- Sync .github\workflows\dotnet.yml ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.1.16-pre.14 (prerelease)

Changes since v1.1.16-pre.13:

- Sync scripts\make-version.ps1 ([@ktsu[bot]](https://github.com/ktsu[bot]))
- Sync scripts\make-changelog.ps1 ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.1.16-pre.13 (prerelease)

Changes since v1.1.16-pre.12:
## v1.1.16-pre.12 (prerelease)

Changes since v1.1.16-pre.11:

- Sync .github\workflows\dotnet.yml ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.1.16-pre.11 (prerelease)

Changes since v1.1.16-pre.10:
## v1.1.16-pre.10 (prerelease)

Changes since v1.1.16-pre.9:
## v1.1.16-pre.9 (prerelease)

Changes since v1.1.16-pre.8:

- Sync scripts\make-version.ps1 ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.1.16-pre.8 (prerelease)

Changes since v1.1.16-pre.7:

- Fix changelog script to handle prerelease version drops correctly ([@matt-edmondson](https://github.com/matt-edmondson))
- Add mailmap ([@matt-edmondson](https://github.com/matt-edmondson))
## v1.1.16-pre.7 (prerelease)

Changes since v1.1.16-pre.6:

- Add scripts for automated metadata generation and versioning ([@matt-edmondson](https://github.com/matt-edmondson))
## v1.1.16-pre.6 (prerelease)

Changes since v1.1.16-pre.5:
## v1.1.16-pre.5 (prerelease)

Changes since v1.1.16-pre.4:

- Bump coverlet.collector from 6.0.2 to 6.0.3 ([@dependabot[bot]](https://github.com/dependabot[bot]))
## v1.1.16-pre.4 (prerelease)

Changes since v1.1.16-pre.3:
## v1.1.16-pre.3 (prerelease)

Changes since v1.1.16-pre.2:

- Sync .github\workflows\dotnet.yml ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.1.16-pre.2 (prerelease)

Changes since v1.1.16-pre.1:

- Sync .github\workflows\dotnet.yml ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.1.16-pre.1 (prerelease)

Incremental prerelease update.
## v1.1.15-pre.1 (prerelease)

Changes since v1.1.14:

- Renamed metadata files ([@matt-edmondson](https://github.com/matt-edmondson))
## v1.1.14 (patch)

Changes since v1.1.13:

- Sync icon.png ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.1.13 (patch)

Changes since v1.1.12:

- Sync Directory.Build.props ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.1.12 (patch)

Changes since v1.1.11:

- Sync Directory.Build.props ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.1.11 (patch)

Changes since v1.1.10:

- Sync Directory.Build.props ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.1.10 (patch)

Changes since v1.1.9:

- Replace LICENSE file with LICENSE.md and update copyright information ([@matt-edmondson](https://github.com/matt-edmondson))
## v1.1.10-pre.1 (prerelease)

Changes since v1.1.10:

- Renamed metadata files ([@matt-edmondson](https://github.com/matt-edmondson))
## v1.1.9 (patch)

Changes since v1.1.8:

- Sync Directory.Build.targets ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.1.8 (patch)

Changes since v1.1.7:

- Update test method names and replace LICENSE file ([@matt-edmondson](https://github.com/matt-edmondson))
## v1.1.7 (patch)

Changes since v1.1.6:

- Sync .github\workflows\dotnet.yml ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.1.6 (patch)

Changes since v1.1.5:

- Sync .github\workflows\dotnet.yml ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.1.5 (patch)

Changes since v1.1.4:

- Sync Directory.Build.props ([@ktsu[bot]](https://github.com/ktsu[bot]))
- Sync Directory.Build.targets ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.1.4 (patch)

Changes since v1.1.3:

- Sync Directory.Build.targets ([@ktsu[bot]](https://github.com/ktsu[bot]))
- Sync Directory.Build.props ([@ktsu[bot]](https://github.com/ktsu[bot]))
- Sync .github\workflows\dotnet.yml ([@ktsu[bot]](https://github.com/ktsu[bot]))
- Sync Directory.Build.targets ([@ktsu[bot]](https://github.com/ktsu[bot]))
- Sync Directory.Build.props ([@ktsu[bot]](https://github.com/ktsu[bot]))
- Sync .github\workflows\dotnet.yml ([@ktsu[bot]](https://github.com/ktsu[bot]))
- Sync Directory.Build.targets ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.1.3 (patch)

Changes since v1.1.2:

- Bump MSTest.TestFramework from 3.6.3 to 3.6.4 ([@dependabot[bot]](https://github.com/dependabot[bot]))
## v1.1.2 (patch)

Changes since v1.1.1:

- Bump MSTest.TestAdapter from 3.6.3 to 3.6.4 ([@dependabot[bot]](https://github.com/dependabot[bot]))
## v1.1.1 (patch)

Changes since v1.1.0:

- Remove unnecessary PackageReference from DeepClone.Test project ([@matt-edmondson](https://github.com/matt-edmondson))
## v1.1.0 (minor)

Changes since 1.0.0:

- Update VERSION ([@matt-edmondson](https://github.com/matt-edmondson))
- Update repository URL in README.md ([@matt-edmondson](https://github.com/matt-edmondson))
- Migrate ktsu.io to ktsu namespace ([@matt-edmondson](https://github.com/matt-edmondson))
- Add test project, update README, and remove .gitattributes ([@matt-edmondson](https://github.com/matt-edmondson))
## v1.0.18 (patch)

Changes since v1.0.17:

- Sync Directory.Build.targets ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.0.17 (patch)

Changes since v1.0.16:

- Sync Directory.Build.props ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.0.16 (patch)

Changes since v1.0.15:

- Sync Directory.Build.targets ([@ktsu[bot]](https://github.com/ktsu[bot]))
- Sync Directory.Build.props ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.0.15 (patch)

Changes since v1.0.14:

- Bump Microsoft.NET.Test.Sdk in the microsoft group ([@dependabot[bot]](https://github.com/dependabot[bot]))
- Bump MSTest.TestFramework from 3.6.2 to 3.6.3 ([@dependabot[bot]](https://github.com/dependabot[bot]))
## v1.0.14 (patch)

Changes since v1.0.13:

- Bump MSTest.TestAdapter from 3.6.2 to 3.6.3 ([@dependabot[bot]](https://github.com/dependabot[bot]))
## v1.0.13 (patch)

Changes since v1.0.12:

- Bump MSTest.TestAdapter from 3.6.1 to 3.6.2 ([@dependabot[bot]](https://github.com/dependabot[bot]))
- Bump MSTest.TestFramework from 3.6.1 to 3.6.2 ([@dependabot[bot]](https://github.com/dependabot[bot]))
## v1.0.12 (patch)

Changes since v1.0.11:

- Bump MSTest.TestFramework from 3.6.0 to 3.6.1 ([@dependabot[bot]](https://github.com/dependabot[bot]))
## v1.0.11 (patch)

Changes since v1.0.10:

- Bump MSTest.TestAdapter from 3.6.0 to 3.6.1 ([@dependabot[bot]](https://github.com/dependabot[bot]))
## v1.0.10 (patch)

Changes since v1.0.9:

- Sync .github\workflows\dotnet.yml ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.0.9 (patch)

Changes since v1.0.8:
## v1.0.8 (patch)

Changes since v1.0.7:

- Sync .github\dependabot.yml ([@ktsu[bot]](https://github.com/ktsu[bot]))
- Sync .github\dependabot.yml ([@ktsu[bot]](https://github.com/ktsu[bot]))
- Sync .github\workflows\dotnet.yml ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.0.7 (patch)

Changes since v1.0.6:

- Sync .github\workflows\dependabot-merge.yml ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.0.6 (patch)

Changes since v1.0.5:

- Sync .github\workflows\dependabot-merge.yml ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.0.5 (patch)

Changes since v1.0.4:

- Sync .github\workflows\dependabot-merge.yml ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.0.4 (patch)

Changes since v1.0.3:

- Sync .github\workflows\dependabot-merge.yml ([@ktsu[bot]](https://github.com/ktsu[bot]))
- Sync .github\workflows\dependabot-merge.yml ([@ktsu[bot]](https://github.com/ktsu[bot]))
- Bump MSTest.TestAdapter from 3.5.2 to 3.6.0 ([@dependabot[bot]](https://github.com/dependabot[bot]))
- Sync .github\workflows\dotnet.yml ([@ktsu[bot]](https://github.com/ktsu[bot]))
- Bump MSTest.TestFramework from 3.5.2 to 3.6.0 ([@dependabot[bot]](https://github.com/dependabot[bot]))
- Sync .github\workflows\dotnet.yml ([@ktsu[bot]](https://github.com/ktsu[bot]))
- Sync .github\workflows\dotnet.yml ([@ktsu[bot]](https://github.com/ktsu[bot]))
- Sync .github\workflows\dotnet.yml ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.0.3 (patch)

Changes since v1.0.2:

- Sync .github\dependabot.yml ([@ktsu[bot]](https://github.com/ktsu[bot]))
- Sync .github\workflows\dotnet.yml ([@ktsu[bot]](https://github.com/ktsu[bot]))
## v1.0.2 (major)

- Update VERSION ([@matt-edmondson](https://github.com/matt-edmondson))
- Migrate ktsu.io to ktsu namespace ([@matt-edmondson](https://github.com/matt-edmondson))
- Update DeepClone.csproj ([@matt-edmondson](https://github.com/matt-edmondson))
- Initial commit ([@matt-edmondson](https://github.com/matt-edmondson))