C#
ICloneable
Programming
Software Development
Generics

Why no ICloneableT?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

C# has the non-generic ICloneable interface but never introduced a generic ICloneable<T>. This was a deliberate design decision by the .NET team. The core problem is that ICloneable is fundamentally ambiguous — it does not specify whether Clone() returns a deep copy or a shallow copy — and adding generics would not fix this ambiguity. The .NET design guidelines explicitly recommend against implementing ICloneable at all.

The Problem with ICloneable

The existing interface:

csharp
1public interface ICloneable
2{
3    object Clone();
4}

Issues:

  1. No deep vs shallow contract: Does Clone() deep-copy nested objects or just copy references? The interface does not say. Different implementations do different things.
  2. Returns object: Requires casting at the call site.
  3. Inheritance breaks cloning: If Dog : Animal and both implement Clone(), should Dog.Clone() return Dog or Animal?
csharp
1class Person : ICloneable
2{
3    public string Name { get; set; }
4    public Address Address { get; set; }
5
6    public object Clone()
7    {
8        // Is this correct? Shallow copy — Address is shared
9        return this.MemberwiseClone();
10        // Or should it deep copy Address too?
11    }
12}
13
14Person original = new Person { Name = "Alice", Address = new Address("NYC") };
15Person clone = (Person)original.Clone();  // Cast required
16clone.Address.City = "LA";
17Console.WriteLine(original.Address.City);  // "LA" — surprise! Shared reference

Why ICloneable<T> Would Not Help

A generic version would look like:

csharp
1// Hypothetical — does NOT exist in .NET
2public interface ICloneable<T>
3{
4    T Clone();
5}

This solves the casting problem but not the semantic ambiguity:

csharp
1class Person : ICloneable<Person>
2{
3    public Person Clone()
4    {
5        // Still ambiguous: deep or shallow?
6        return (Person)this.MemberwiseClone();  // shallow
7    }
8}

The caller still cannot know whether the clone shares mutable nested objects. This ambiguity is the real reason the .NET team decided against ICloneable<T> — adding generics addresses a symptom (casting) but not the disease (undefined semantics).

What the .NET Team Recommends

From the .NET Framework Design Guidelines:

Do not implement ICloneable. It is better to provide a strongly-typed Copy() method on the class that clearly documents its behavior.
csharp
1class Person
2{
3    public string Name { get; set; }
4    public Address Address { get; set; }
5
6    // Clearly named — caller knows exactly what happens
7    public Person ShallowCopy()
8    {
9        return (Person)this.MemberwiseClone();
10    }
11
12    public Person DeepCopy()
13    {
14        return new Person
15        {
16            Name = this.Name,  // string is immutable, safe to share
17            Address = new Address(this.Address.City)  // new instance
18        };
19    }
20}

Copy Constructor Pattern

csharp
1class Person
2{
3    public string Name { get; set; }
4    public Address Address { get; set; }
5
6    // Copy constructor — common in C++ tradition
7    public Person(Person other)
8    {
9        Name = other.Name;
10        Address = new Address(other.Address);  // deep copy
11    }
12}
13
14var clone = new Person(original);

The Inheritance Problem

Even with generics, cloning breaks with inheritance:

csharp
1class Animal : ICloneable<Animal>
2{
3    public string Name { get; set; }
4    public Animal Clone() => new Animal { Name = Name };
5}
6
7class Dog : Animal, ICloneable<Dog>
8{
9    public string Breed { get; set; }
10    public new Dog Clone() => new Dog { Name = Name, Breed = Breed };
11}
12
13Animal animal = new Dog { Name = "Rex", Breed = "Lab" };
14Animal clone = animal.Clone();  // Calls Animal.Clone(), not Dog.Clone()!
15// clone is an Animal, not a Dog — breed is lost

ICloneable<T> does not support covariant returns elegantly across inheritance hierarchies.

Alternative: Serialization-Based Deep Copy

For a true deep copy without manual coding:

csharp
1using System.Text.Json;
2
3public static T DeepClone<T>(T obj)
4{
5    var json = JsonSerializer.Serialize(obj);
6    return JsonSerializer.Deserialize<T>(json)!;
7}
8
9var clone = DeepClone(original);

This is slow but reliably deep-copies the entire object graph. For performance-sensitive code, use manual copy constructors.

record Types (C# 9+)

C# records provide built-in value-based copying via with:

csharp
1record Person(string Name, string City);
2
3var original = new Person("Alice", "NYC");
4var clone = original with { City = "LA" };
5// original.City = "NYC" (unchanged)
6// clone.City = "LA"

Records are shallow-copy by default, but since record properties are typically immutable, this is usually sufficient.

Common Pitfalls

  • Shallow copy surprises: MemberwiseClone() copies value types but shares reference types. Mutating a nested object in the clone affects the original.
  • ICloneable on collections: List<T>.Clone() does not exist. Use new List<T>(original) for a shallow copy or original.Select(x => x.DeepCopy()).ToList() for deep.
  • Struct cloning: Value types (structs) are copied by value automatically. ICloneable is unnecessary for structs unless they contain reference-type fields.
  • Thread safety: Cloning is not atomic. If another thread modifies the object during cloning, the clone may be in an inconsistent state. Use locking if needed.

Summary

  • ICloneable<T> does not exist because generics solve the casting issue but not the deep-vs-shallow ambiguity
  • The .NET team recommends not implementing ICloneable at all
  • Use explicit DeepCopy() / ShallowCopy() methods or copy constructors instead
  • record types with with expressions provide clean, built-in copying in C# 9+
  • For automatic deep copy, use serialization (JSON/binary) at the cost of performance

Course illustration
Course illustration

All Rights Reserved.