Learnixo
Back to blog
Backend Systemsbeginner

Prototype — Clone Instead of Rebuild

The Prototype pattern in C#: create new objects by cloning existing ones. Deep vs shallow copy, ICloneable pitfalls, and practical uses for expensive-to-construct objects.

Asma Hafeez KhanMay 24, 20264 min read
csharpdesign-patternsprototypecreationaldotnetcloning
Share:𝕏

Prototype — Clone Instead of Rebuild

The Prototype pattern creates new objects by copying an existing object (the prototype) rather than constructing from scratch. Use it when creating a new instance is expensive or complex.


The Problem

C#
// Expensive to create — hits database, loads config, initialises subsystems
public class ReportTemplate
{
    public string   Title    { get; set; } = "";
    public string   Layout   { get; set; } = "";
    public List<ReportSection> Sections { get; set; } = new();

    // Expensive constructor — loads from database
    public ReportTemplate(string templateId)
    {
        var data = LoadFromDatabase(templateId);   // slow
        Title    = data.Title;
        Layout   = data.Layout;
        Sections = data.Sections;
    }
}

// Creating 100 reports from the same template:
// BAD — 100 database calls
for (int i = 0; i < 100; i++)
    var t = new ReportTemplate("annual-report");   // slow × 100

Deep Clone Implementation

C#
public class ReportSection
{
    public string Heading { get; set; } = "";
    public string Body    { get; set; } = "";

    public ReportSection Clone() => new()
    {
        Heading = Heading,   // string is immutable — reference copy is fine
        Body    = Body,
    };
}

public class ReportTemplate
{
    public string  Title   { get; set; } = "";
    public string  Layout  { get; set; } = "";
    public List<ReportSection> Sections { get; set; } = new();

    // Deep clone — new list with cloned sections
    public ReportTemplate Clone()
    {
        return new ReportTemplate
        {
            Title   = Title,
            Layout  = Layout,
            Sections = Sections.Select(s => s.Clone()).ToList(),   // deep copy
        };
    }
}

// Cache one loaded template, clone for each report
var template = LoadOnce("annual-report");

for (int i = 0; i < 100; i++)
{
    var report = template.Clone();   // fast — no DB call
    report.Title = $"Annual Report {DateTime.UtcNow.Year} — Division {i}";
    // customise and use...
}

Shallow vs Deep Copy

C#
// Shallow copy: copies references — both original and clone share nested objects
var original = new ReportTemplate { Title = "Original" };
original.Sections.Add(new ReportSection { Heading = "Intro" });

// MemberwiseClone() — shallow
var shallow = (ReportTemplate)original.MemberwiseClone();
shallow.Title = "Copy";                    // only changes copy's title ✓
shallow.Sections[0].Heading = "Modified";  // ALSO changes original's section ✗
// original.Sections[0].Heading is now "Modified" — unintended!

// Deep copy: new instances for all nested objects
var deep = original.Clone();
deep.Sections[0].Heading = "Independent";  // original unchanged ✓

Prototype Registry

C#
// Registry — store and retrieve named prototypes
public class TemplateRegistry
{
    private readonly Dictionary<string, ReportTemplate> _templates = new();

    public void Register(string name, ReportTemplate template)
        => _templates[name] = template;

    public ReportTemplate Create(string name)
    {
        if (!_templates.TryGetValue(name, out var proto))
            throw new KeyNotFoundException($"Template '{name}' not registered");
        return proto.Clone();
    }
}

// Set up once at startup
var registry = new TemplateRegistry();
registry.Register("annual",   LoadTemplate("annual-report"));
registry.Register("quarterly",LoadTemplate("quarterly-report"));

// Fast clones anywhere in the app
var report = registry.Create("annual");

Modern Alternative: Records with with

C#
// Immutable records make prototype trivial
public record OrderTemplate(
    string  CustomerName,
    string  DeliveryAddress,
    decimal DiscountRate,
    List<string> DefaultItems
);

var standard = new OrderTemplate(
    CustomerName:   "Standard Customer",
    DeliveryAddress: "TBD",
    DiscountRate:    0.0m,
    DefaultItems:    new() { "SKU-001", "SKU-002" }
);

// Non-destructive mutation — creates a new instance with one field changed
var loyal = standard with { DiscountRate = 0.10m };
var vip   = standard with { DiscountRate = 0.20m };

// Note: DefaultItems is still shared (shallow) — deep copy manually if mutating it

Interview Answer

"The Prototype pattern creates new objects by cloning an existing one rather than constructing from scratch — useful when construction is expensive (database load, complex initialisation) or when you need many similar objects with small variations. In C#, implement a Clone() method that returns a deep copy — don't use MemberwiseClone() for objects with nested reference types, as it creates a shallow copy that shares nested objects. A Prototype Registry caches named prototypes and hands out clones on demand — one DB load, unlimited fast copies. Modern C# records with with { } syntax are a built-in lightweight prototype for immutable data: var vip = standardOrder with { DiscountRate = 0.20m } creates a new record instance. The trade-off: deep cloning of complex graphs can be tricky and performance-sensitive — test and benchmark."

Enjoyed this article?

Explore the Backend Systems learning path for more.

Found this helpful?

Share:𝕏

Leave a comment

Have a question, correction, or just found this helpful? Leave a note below.