Question

Clone a Generic List in C#: Shallow vs Deep Copy

csharpgenericslistclone

Question

I have a generic List<T> of objects in C# and want to clone it. The objects in the list can be cloned, but List<T> does not provide a Clone() method.

What is the simplest way to create a copy of the list, including cloned copies of its items when needed?

Short Answer

You will learn why List<T> has no Clone() method, how to create a new list with the same items, and how to create a true deep copy by cloning each object. You will also learn when each approach is appropriate.

Concept

A List<T> is a collection object that stores a sequence of values. To "clone a list," you must first decide what should be copied:

  • The list container only: create a new List<T> containing the same values.
  • The list and every referenced object inside it: create a new list and clone each item.

For value types such as int, decimal, and DateTime, copying the values is usually enough because the values themselves are stored directly.

For reference types such as class instances, copying a list normally copies references. The new list is independent, but both lists point to the same objects. This is called a shallow copy.

A deep copy creates separate item objects as well. Changes to an item in one list then do not affect the corresponding item in the other list.

List<T> does not implement a general-purpose Clone() method because it cannot know the correct cloning behavior for every possible T. Some objects should share references, some need custom copying rules, and some cannot be safely cloned at all.

Mental Model

Think of a list as a folder containing address cards.

  • A shallow copy makes a new folder and puts photocopies of the card locations into it. Both folders lead to the same physical cards. Editing a card affects what both folders show.
  • A deep copy makes a new folder and creates a separate photocopy of every card. Editing a card in one folder does not change the matching card in the other.

The folders are always separate after copying. The important question is whether the cards inside are shared or independently copied.

Take Quiz

Syntax and Examples

Create a new list with the List<T> constructor:

List<string> names = new List<string> { "Ava", "Noah" };
List<string> copiedNames = new List<string>(names);

For strings, this is usually sufficient. Although string is a reference type, strings are immutable: their contents cannot be changed after creation.

For a list of mutable class objects, this is a shallow copy:

public class Product
{
    public string Name { get; set; } = "";
}

List<Product> products = new List<Product>
{
    new Product { Name = "Keyboard" }
};

List<Product> copiedProducts = new List<Product>(products);

copiedProducts[0].Name = "Mechanical Keyboard";

Console.WriteLine(products[0].Name); // Mechanical Keyboard

The two lists are different list objects, but index 0 in both lists refers to the same Product.

To make a deep copy, create a new item for every source item. For example, if the type exposes a Clone method:

List<Product> clonedProducts = products
    .Select(product => product.Clone())
    .ToList();

This requires Product.Clone() to return a properly independent Product instance.

Step by Step Execution

Consider this shallow-copy example:

var original = new List<Customer>
{
    new Customer { Name = "Mina" }
};

var copy = new List<Customer>(original);
copy[0].Name = "Mina Patel";

Console.WriteLine(original[0].Name);

public class Customer
{
    public string Name { get; set; } = "";
}

Execution steps:

  1. original is created with one Customer object.
  2. new List<Customer>(original) creates a new list container.
  3. The reference to the Customer is copied into copy.
  4. copy[0] and original[0] refer to the same Customer instance.
  5. Changing copy[0].Name changes that shared object.
  6. The output is Mina Patel.

Now compare it with a deep copy:

var deepCopy = original
    .Select(customer => new Customer { Name = customer.Name })
    .ToList();

deepCopy[0].Name = "Different Customer";

Console.WriteLine(original[0].Name); // Mina Patel

Here, the Select expression creates a new Customer for every source customer, so the two lists no longer share item instances.

Real World Use Cases

  • Editing screens and forms: Copy an object or list before the user edits it, so Cancel can discard changes.
  • API request processing: Transform incoming data into separate internal models without mutating the original request objects.
  • Undo/redo history: Store snapshots of mutable application state. A deep copy may be necessary if nested objects can change.
  • Data transformation: Create a new list of DTOs, view models, or report rows from database entities.
  • Concurrent or background work: Use an independent snapshot of data when one task must not observe later changes made by another part of the application.
  • Configuration templates: Start from default settings, then make a separate copy for each user, tenant, or environment.
Take Quiz

Real Codebase Usage

In production code, developers usually choose copying behavior explicitly instead of relying on a vague Clone() operation.

Shallow-copy a collection before changing its membership

Use this when objects can be shared but you need to add or remove entries independently:

var updatedUsers = new List<User>(existingUsers);
updatedUsers.Add(newUser);

Removing from updatedUsers does not remove from existingUsers.

Project source objects into new objects

This is a clear and common deep-copy-like pattern:

var orderDtos = orders
    .Select(order => new OrderDto
    {
        Id = order.Id,
        Total = order.Total,
        Status = order.Status
    })
    .ToList();

Put copy logic on the type when it owns the rules

public class Address
{
    public string Street { get; init; } = "";
    public string City { get; init; } = "";

    public Address Copy() => new Address
    {
        Street = Street,
        City = City
    };
}

A clearly named Copy method can be easier to understand than ICloneable, because callers know its return type and can document whether nested objects are also copied.

Validate while copying

When source data may be missing or invalid, handle it deliberately:

var copies = source
    .Where(item => item is not null)
    .Select(item => item.Copy())
    .ToList();

Whether null items should be skipped, rejected, or preserved depends on the application's rules.

Common Mistakes

Assuming new List<T>(source) deep-copies objects

This creates a new list, not new instances of mutable class items:

var copy = new List<Product>(products); // Shallow copy

Avoid the mistake by constructing a new item for each source item when item independence is required.

Using MemberwiseClone() as a full deep copy

MemberwiseClone() creates a shallow copy. Nested references remain shared.

public Order ShallowCopy()
{
    return (Order)MemberwiseClone();
}

If Order contains a mutable Address, both orders will still reference the same address unless you copy it too.

Forgetting nested mutable objects

This is not fully deep if Address is mutable:

var copiedCustomer = new Customer
{
    Name = source.Name,
    Address = source.Address // Shared reference
};

Copy nested mutable data as well:

Address = source.Address.Copy()

Calling Clone() without a type-safe contract

ICloneable.Clone() returns object, so it requires a cast and does not promise whether the result is shallow or deep:

var productCopy = (Product)product.Clone();

Prefer a type-specific method such as Product Copy() or a copy constructor when you control the class design.

Using serialization only to copy objects

Serialization-based copying can be slow, fragile, and may fail for types that are not serializable. Use explicit copy logic for normal application models.

Comparisons

ApproachNew list container?New item objects?Best use
new List<T>(source)YesNo for reference typesChange list membership independently
source.ToList()YesNo for reference typesLINQ pipelines and concise shallow copies
source.Select(x => x.Copy()).ToList()YesYes, if Copy() is correctIndependent mutable objects
Copy constructor, such as new Product(source)Depends on useYesExplicit, type-safe copying
MemberwiseClone()Not applicableNo, it is shallowLimited internal cloning scenarios

new List<T>(source) and source.ToList() have equivalent copying behavior for a normal enumerable source: both create a new list and preserve the same item references.

For value types, shallow versus deep copying is generally not a concern because each list receives its own copied values. For mutable reference types, it is crucial.

Take Quiz

Cheat Sheet

// Shallow copy: new list, same item references
var copy = new List<T>(source);

// Also a shallow copy, using LINQ
var copy = source.ToList();

// Deep copy: each item provides a type-safe copy method
var deepCopy = source.Select(item => item.Copy()).ToList();

// Deep copy with a copy constructor
var deepCopy = source.Select(item => new Item(item)).ToList();
  • List<T> does not have a built-in Clone() method.
  • A shallow copy separates the list structure, not mutable objects within it.
  • Use a deep copy only when item changes must be isolated.
  • Copy nested mutable objects too when implementing a deep copy.
  • Immutable items, such as string, can safely be shared.
  • Prefer a type-safe Copy() method or copy constructor over ambiguous cloning contracts when possible.

FAQ

Does new List<T>(oldList) clone the objects in C#?

No. It creates a new list but copies each element value. For class instances, those values are references to the same objects.

Is ToList() a deep copy in C#?

No. ToList() creates a new list container, but it does not clone referenced objects.

Why does List<T> not implement ICloneable?

A list cannot decide whether each item should be copied shallowly, deeply, or not at all. That behavior depends on the item type and application rules.

How do I deep-copy a List<T>?

Create a new list by transforming each source item into a new independent item, commonly with Select(item => item.Copy()).ToList() or a copy constructor.

Do I need a deep copy for List<string>?

Usually no. Strings are immutable, so sharing string instances is safe.

Does copying a list copy its capacity too?

No. A new list is created with enough capacity for its copied elements; its internal storage is not the same as the original list's storage.

Should I use ICloneable for my own classes?

It can work, but ICloneable.Clone() returns object and does not specify shallow or deep behavior. A type-specific Copy() method or copy constructor is often clearer.

Take Quiz

Mini Project

Description

Build a small product-catalog snapshot utility. It demonstrates the difference between copying a list structure and creating independently editable product objects, including a nested mutable Category object.

Goal

Create a deep copy of a product list, modify the copied data, and verify that the original catalog remains unchanged.

Requirements

Create Category and Product classes with copy methods. Create an original catalog containing at least two products. Create a deep-copied catalog by copying every product. Modify a product name and a nested category name in the copied catalog. Print values from both catalogs to prove that the original was not changed.

Take Quiz

Keep learning

Related questions

View more