?

How to Clone Objects in C# .NET Core

There are numerous ways to clone an object in C# .NET Core. This article explores some of the approaches you can use to make a deep copy of an object and the pros and cons of each method.

BM
Brent Marquez
September 29, 2020 7 minute read

The problem: How do I make a deep copy of an object in C# .NET Core?

Sometimes you may need to make a copy of an object which functions as a  Prototype, which you use to build upon or adjust as necessary for use in a particular context. The issue arises when you do not want to mutate the original object but work with only a deep copy or clone of it. In languages like ES6 JavaScript, you have the spread syntax which you can use to quickly make a new copy of an object in memory, however that syntax does not exist in C#. So what are some ways we can create a deep clone of our object?

The solution

Here are some different approaches in C# you can take to create a deep copy of an object. The following section lists the pros and cons of each approach along with an implementation example.

Let's work with an object that we want to make a clone of and then change without mutating the original. The following is an object that includes a property containing a nested object for the purpose of demonstrating that a deep copy is made and references on the original are not retained.

public class MyObject
{
   public MyObject(string objectProp, NestedObjectProp nestedObjectProp)
   {
     this.ObjectProp = objectProp;
     this.NestedObjectProp = nestedObjectProp ;
   }
   public string ObjectProp { get; set; }
   public NestedObjectProp NestedObjectProp { get; set; }
}

public class NestedObjectProp
{
   public NestedObjectProp(string nestedPropA, string nestedPropB)
   {
     this.NestedPropB = nestedPropB;
     this.NestedPropA = nestedPropA;
   }
   public string NestedPropA { get; set; }
   public string NestedPropB { get; set; }
}

Option 1: Serialize and deserialize the object via an extension method

The pros

  • Automatically makes a deep copy of the object for you.
  • Flexible implementation: It does not matter what form you serialize it into - you can use a Binary, XML, or JSON Formatter, etc.
  • Does not require the creation or use of an interface.

The cons

  • Performance: Common serialization approaches are based on Reflection, which can have negative performance implications.
  • Runtime errors are possible if the object cannot be cleanly serialized/deserialized.

Tips

  • If serializing the object to JSON, use the Microsoft.AspNetCore.Mvc.NewtonsoftJson Nuget package since the built-in JSON serializer that comes with .NET Core 3 requires you to create parameterless constructors, while the NewtonsoftJson serializer does not.
  • If using a Binary formatter, you need to use the[Serializable]attribute on your classes.

Implementation example

  • Create an Extension Method that you can call on Objects. It makes a deep copy by serializing it and then returning a deserialized copy.
using Newtonsoft.Json;

public static class ExtensionMethods
{
  public static T DeepCopy<T>(this T self)
  {
    var serialized = JsonConvert.SerializeObject(self);
    return JsonConvert.DeserializeObject<T>(serialized);
  }
}
  • Now to make a clone, call .DeepCopy() on the object. The following example creates a deep copy of an instance of MyObject, and then alters properties on the clone. The original MyObject instance is therefore not mutated.
static void Main(string[] args)
{
  var myObj = new MyObject("original", new NestedObjectProp("nestedPropA", "nestedPropB"));
  
  var myObjClone = myObj.DeepCopy();
  
  myObjClone.ObjectProp = "changed objectProp on clone";
  myObjClone.NestedObjectProp.NestedPropB = "changed nestedPropB on clone";
}

Option 2: Implement the ICloneable interface

The ICloneable interface requires that an object which implements it needs to define a Clone() method.

The pros

  • This interface comes built-in with .NET Core.

The cons

  • Ambiguous specification: the Clone() method is not required to implement a deep copy strategy and therefore it is unclear to the user whether the method is making a deep copy or a shallow copy of the object.
  • The ICloneable Clone() method returns a weakly typed object requiring you to cast it for stronger typing.

Implementation example

  • Implement the ICloneable interface on the objects.
public class MyObject : ICloneable
{
  // ...The properties and constructor remain the same as above. 
  // Just implement the Clone method:

  public object Clone()
  {
    return new MyObject(ObjectProp, (NestedObjectProp) NestedObjectProp.Clone());
  }
}

public class NestedObjectProp : ICloneable
{
  // ...The properties and constructor remain the same as above. 
  // Just implement the Clone method:

  public object Clone()
  {
    return new NestedObjectProp(NestedPropA, NestedPropB);
  }
}
  • Call the Clone() method on the object to make a copy. Note the need to cast the clone for strong typing.
static void Main(string[] args)
{
  var myObj = new MyObject("original", new NestedObjectProp("nestedPropA", "nestedPropB"));
      
  var myObjClone = (MyObject) myObj.Clone();
      
  myObjClone.ObjectProp = "changed objectProp on clone";
  myObjClone.NestedObjectProp.NestedPropB = "changed nestedPropB on clone";
}

Option 3: Implement an explicit DeepCopy interface

The pros

  • Makes it clear to the user what kind of copy is made and can be a better alternative to implementing the more ambiguous ICloneable interface.

The cons

  • Tedious to implement if you have a deep hierarchy of objects because you need an interface implementation and deep copy logic for every member.

Implementation example

  • Create an interface which implements an explicit DeepCopy() method.
public interface IPrototype<T>
{
  T DeepCopy();
}
  • Implement the interface in your objects.
public class MyObject : IPrototype<MyObject>
{
  // ...The properties and constructor remain the same as above.

  public MyObject DeepCopy()
  {
    return new MyObject(ObjectProp, NestedObjectProp.DeepCopy());
  }
}

public class NestedObjectProp : IPrototype<NestedObjectProp>
{
  // ...The properties and constructor remain the same as above.

  public NestedObjectProp DeepCopy()
  {
    return new NestedObjectProp(NestedPropA, NestedPropB);
  }
}
  • Call DeepCopy() on the object to make a deep clone.
static void Main(string[] args)
{
  var myObj = new MyObject("original", new NestedObjectProp("nestedPropA", "nestedPropB"));
  
  var myObjClone = myObj.DeepCopy();
  
  myObjClone.ObjectProp = "changed objectProp on clone";
  myObjClone.NestedObjectProp.NestedPropB = "changed nestedPropB on clone";
}

Option 4: Use a copy constructor

The pros

  • Offers a bit more clarity on usage over implementing an ICloneable interface.

The cons

  • Copy Constructor is a term that comes from the C++ language and the concept is not idiomatic to C#.
  • Copy Constructors must be created for nested properties as well.

Implementation example

  • Create a separate constructor which takes in an instance of the same type, copies properties from it and returns a new instance.
public class MyObject
{
  public MyObject(string objectProp, NestedObjectProp nestedObjectProp)
  {
    this.ObjectProp = objectProp;
    this.NestedObjectProp = nestedObjectProp ;
  }

  public MyObject(MyObject other)
  {
    this.ObjectProp = other.ObjectProp;
    this.NestedObjectProp = new NestedObjectProp(other.NestedObjectProp);
  }
  
  public string ObjectProp { get; set; }
  public NestedObjectProp NestedObjectProp { get; set; }
}

public class NestedObjectProp
{
  public NestedObjectProp(string nestedPropA, string nestedPropB)
  {
    this.NestedPropB = nestedPropB;
    this.NestedPropA = nestedPropA;
  }

  public NestedObjectProp(NestedObjectProp other)
  {
    this.NestedPropA = other.NestedPropA;
    this.NestedPropB = other.NestedPropB;
  }
  
  public string NestedPropA { get; set; }
  public string NestedPropB { get; set; }
}
  • Create a deep copy by calling the Copy Constructor and passing in the instance of the original object.
static void Main(string[] args)
{
  var myObj = new MyObject("original", new NestedObjectProp("nestedPropA", "nestedPropB"));
  
  var myObjClone = new MyObject(myObj);
  
  myObjClone.ObjectProp = "changed objectProp on clone";
  myObjClone.NestedObjectProp.NestedPropB = "changed nestedPropB on clone";
}

A future option: Records

  • With the release of C# 9.0, there will be a new Record Type that will simplify creating immutable and cloneable types.

Hopefully, this presents you with some options for when there is a need to clone an object in C# .NET Core and what to consider when choosing one over the other. Special thanks and shoutout to colleagues Greg Todd, Rodney Foley and Erik Muir who helped inform this article and offered their insights on these approaches as well.

Learn more about our development expertise.
Share this

Comments