BrandGhost
Template Method Design Pattern in C#: Complete Guide with Examples

Template Method Design Pattern in C#: Complete Guide with Examples

Template Method Design Pattern in C#: Complete Guide with Examples

When you need a consistent algorithm structure but want individual steps to vary, the template method design pattern in C# is the behavioral pattern built for exactly that. A base class defines the skeleton of an algorithm -- the fixed sequence of steps -- and defers specific step implementations to subclasses. The subclasses fill in the details without altering the overall flow. The result is code that enforces a predictable execution order while still allowing customization where it matters.

In this complete guide, we'll walk through everything you need to know about the template method design pattern -- from the conceptual structure and a hands-on C# implementation to hooks, dependency injection integration, comparisons with the strategy pattern, and practical guidance on when to use it. By the end, you'll have working code examples and a clear understanding of how this pattern fits into your .NET applications.

Understanding the Template Method Pattern

The template method pattern is a behavioral pattern from the Gang of Four (GoF) catalog that defines the skeleton of an algorithm in a base class method, letting subclasses override specific steps without changing the algorithm's structure. The key idea is that the high-level workflow is locked down in one place, and only the variable parts are open for customization.

The pattern involves two main participants:

The Abstract Class declares the template method itself -- a method that calls a series of steps in a defined order. Some of those steps are abstract, forcing subclasses to provide their own implementations. Others may be virtual with default behavior, acting as optional extension points. The template method is typically non-virtual (or sealed in C#) so that subclasses cannot alter the sequence of steps.

Concrete Classes extend the abstract class and override the abstract methods to supply step-specific behavior. They can also override virtual methods to customize optional steps. Crucially, concrete classes never override the template method itself -- they only fill in the individual steps that the template method orchestrates.

Think of it like a recipe framework. The template method says: preheat, prepare ingredients, cook, plate, and serve. Every dish follows that sequence. But how you prepare ingredients and how you cook differs between a pasta dish and a stir-fry. The framework is fixed; the details are swappable.

This structure enforces inversion of control at the class level -- the base class controls the flow, and the subclass supplies the variable behavior. The base class calls down into the subclass, not the other way around. This is sometimes called the "Hollywood Principle": don't call us, we'll call you.

Basic C# Implementation

Let's build a practical example: a data processing pipeline where different data sources require different parsing and validation logic, but the overall workflow -- load, validate, transform, and export -- is always the same.

Defining the Abstract Base Class

The abstract class declares the template method and the steps it orchestrates:

using System;
using System.Collections.Generic;

public abstract class DataProcessor
{
    public void ProcessData()
    {
        IReadOnlyList<string> rawData = LoadData();
        IReadOnlyList<string> validData = ValidateData(rawData);
        IReadOnlyList<string> transformed = TransformData(validData);

        BeforeExport(transformed);

        ExportData(transformed);

        Console.WriteLine("Processing complete.");
    }

    protected abstract IReadOnlyList<string> LoadData();

    protected abstract IReadOnlyList<string> ValidateData(
        IReadOnlyList<string> data);

    protected abstract IReadOnlyList<string> TransformData(
        IReadOnlyList<string> data);

    protected abstract void ExportData(
        IReadOnlyList<string> data);

    protected virtual void BeforeExport(
        IReadOnlyList<string> data)
    {
        // Hook: subclasses can override for
        // pre-export behavior. Default does nothing.
    }
}

The ProcessData method is the template method. It defines the exact sequence: load, validate, transform, optionally hook, and export. Subclasses cannot change this order -- they only supply implementations for the individual steps. The BeforeExport method is a hook with an empty default implementation, giving subclasses the option to inject behavior before the export step without being forced to.

Creating Concrete Subclasses

Here's a concrete subclass that processes CSV data:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

public sealed class CsvDataProcessor : DataProcessor
{
    private readonly string _filePath;

    public CsvDataProcessor(string filePath)
    {
        _filePath = filePath;
    }

    protected override IReadOnlyList<string> LoadData()
    {
        Console.WriteLine($"Loading CSV from {_filePath}");
        return File.ReadAllLines(_filePath).ToList();
    }

    protected override IReadOnlyList<string> ValidateData(
        IReadOnlyList<string> data)
    {
        Console.WriteLine("Validating CSV rows...");
        return data
            .Where(line => !string.IsNullOrWhiteSpace(line))
            .ToList();
    }

    protected override IReadOnlyList<string> TransformData(
        IReadOnlyList<string> data)
    {
        Console.WriteLine("Trimming whitespace from CSV fields...");
        return data
            .Select(line => string.Join(",",
                line.Split(',').Select(f => f.Trim())))
            .ToList();
    }

    protected override void ExportData(
        IReadOnlyList<string> data)
    {
        Console.WriteLine(
            $"Exporting {data.Count} cleaned CSV rows.");
    }
}

And here's a second concrete subclass that processes JSON data:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

public sealed class JsonDataProcessor : DataProcessor
{
    private readonly string _filePath;

    public JsonDataProcessor(string filePath)
    {
        _filePath = filePath;
    }

    protected override IReadOnlyList<string> LoadData()
    {
        Console.WriteLine($"Loading JSON from {_filePath}");
        string content = File.ReadAllText(_filePath);
        return new List<string> { content };
    }

    protected override IReadOnlyList<string> ValidateData(
        IReadOnlyList<string> data)
    {
        Console.WriteLine("Validating JSON structure...");
        return data
            .Where(json => json.TrimStart().StartsWith("{")
                || json.TrimStart().StartsWith("["))
            .ToList();
    }

    protected override IReadOnlyList<string> TransformData(
        IReadOnlyList<string> data)
    {
        Console.WriteLine(
            "Normalizing JSON whitespace...");
        return data
            .Select(json => json.Replace("
", "
"))
            .ToList();
    }

    protected override void ExportData(
        IReadOnlyList<string> data)
    {
        Console.WriteLine(
            $"Exporting {data.Count} JSON document(s).");
    }

    protected override void BeforeExport(
        IReadOnlyList<string> data)
    {
        Console.WriteLine(
            "Backing up original JSON before export...");
    }
}

Notice that JsonDataProcessor overrides the BeforeExport hook to add backup behavior, while CsvDataProcessor doesn't override it at all -- it relies on the default empty implementation. Both classes follow the same algorithmic structure defined by ProcessData, but their individual steps differ entirely.

Putting It All Together

Here's how a client uses the data processors:

DataProcessor csvProcessor = new CsvDataProcessor(
    "data/input.csv");
csvProcessor.ProcessData();
// Output:
// Loading CSV from data/input.csv
// Validating CSV rows...
// Trimming whitespace from CSV fields...
// Exporting 42 cleaned CSV rows.
// Processing complete.

DataProcessor jsonProcessor = new JsonDataProcessor(
    "data/input.json");
jsonProcessor.ProcessData();
// Output:
// Loading JSON from data/input.json
// Validating JSON structure...
// Normalizing JSON whitespace...
// Backing up original JSON before export...
// Exporting 1 JSON document(s).
// Processing complete.

The client works with the DataProcessor abstraction. It calls ProcessData, and the template method handles the orchestration. The client doesn't know or care which concrete subclass is running -- it just knows the algorithm will execute in the correct order.

Hooks and Optional Steps

Hooks are a core feature of the template method design pattern in C#. A hook is a virtual method in the abstract base class that has a default implementation -- typically empty -- and that subclasses can optionally override. Hooks give subclasses the ability to extend the algorithm at specific points without requiring every subclass to provide an implementation.

The distinction between abstract methods and hooks is important:

  • Abstract methods are mandatory. Every concrete subclass must override them. These represent the steps that are essential to the algorithm and that genuinely vary between implementations.
  • Hooks (virtual methods) are optional. They have a default behavior (often doing nothing), and subclasses override them only when they need to inject extra logic at that point in the algorithm.

Hooks can also control flow. A common pattern is a boolean hook that the template method checks to decide whether to execute an optional step:

public abstract class ReportGenerator
{
    public void GenerateReport()
    {
        GatherData();
        FormatReport();

        if (ShouldIncludeCharts())
        {
            AddCharts();
        }

        PublishReport();
    }

    protected abstract void GatherData();

    protected abstract void FormatReport();

    protected abstract void PublishReport();

    protected virtual bool ShouldIncludeCharts() => false;

    protected virtual void AddCharts()
    {
        // Default: no charts added
    }
}

A subclass that needs charts overrides both ShouldIncludeCharts (returning true) and AddCharts (providing the chart logic). A subclass that doesn't need charts ignores both hooks entirely. The template method remains the single source of truth for the algorithm's flow.

Template Method with Dependency Injection

In real .NET applications, you'll want to register template method classes with the dependency injection container. The template method pattern works naturally with IServiceCollection -- you register concrete subclasses and resolve them by their abstract base class or by a shared interface.

Here's how you'd set this up:

using Microsoft.Extensions.DependencyInjection;

var services = new ServiceCollection();

services.AddTransient<DataProcessor, CsvDataProcessor>(
    sp => new CsvDataProcessor("data/input.csv"));

var provider = services.BuildServiceProvider();

var processor = provider
    .GetRequiredService<DataProcessor>();
processor.ProcessData();

If you need to resolve multiple implementations of the same base class, you can register them with keyed services or use a factory pattern. One approach is to register an IEnumerable<DataProcessor>:

var services = new ServiceCollection();

services.AddTransient<DataProcessor>(
    sp => new CsvDataProcessor("data/input.csv"));
services.AddTransient<DataProcessor>(
    sp => new JsonDataProcessor("data/input.json"));

var provider = services.BuildServiceProvider();

IEnumerable<DataProcessor> processors = provider
    .GetServices<DataProcessor>();

foreach (DataProcessor processor in processors)
{
    processor.ProcessData();
}

This approach lets you process all registered data sources in sequence without the calling code knowing which concrete processors exist. Adding a new data source means registering a new subclass -- the orchestration code doesn't change. This pairs well with the facade design pattern when you want to expose a simplified interface over multiple template method processors.

When to Use the Template Method Pattern

The template method design pattern in C# is a strong fit when you have a fixed algorithm structure with variable steps. Here are the most common scenarios:

Data processing pipelines -- When multiple data sources need the same workflow (load, validate, transform, export) but each source has different parsing and validation logic. The template method locks down the pipeline order while letting each processor handle its own format.

File parsing workflows -- When you need to parse different file formats (CSV, XML, JSON, Parquet) but the overall parsing lifecycle -- open, read headers, parse rows, close -- is consistent across formats.

Report generation -- When different report types follow the same generation lifecycle (gather data, format, optionally add charts, publish) but differ in how they gather data and format output.

Game AI turn logic -- When game characters follow the same turn structure (assess situation, choose action, execute action, end turn) but each character type has different assessment and action logic.

Test fixtures -- When test setup and teardown follow a consistent structure, but the specific setup steps vary by test category.

When Not to Use It

Don't reach for the template method when the algorithm itself varies between implementations. If the sequence of steps changes -- not just the step implementations -- the pattern becomes a constraint rather than a benefit. In those cases, the strategy design pattern is usually a better fit because it lets you swap entire algorithms via composition rather than locking down a fixed sequence via inheritance.

Also be cautious when the inheritance hierarchy gets deep. The template method pattern relies on inheritance, and deep inheritance trees introduce fragility. If you find yourself building a chain of abstract base classes, each adding its own template method hooks, consider whether composition-based alternatives would serve you better.

Template Method vs Strategy Pattern

The template method pattern and the strategy design pattern solve related but different problems, and they're often confused because both deal with varying behavior. The key distinction is in how they achieve that variation.

The template method pattern uses inheritance. A base class defines the algorithm skeleton and calls abstract or virtual methods. Subclasses fill in specific steps. The algorithm structure is fixed at compile time in the base class. You customize behavior by creating new subclasses.

The strategy pattern uses composition. A context holds a reference to a strategy interface, and the strategy implementation defines the entire algorithm. You customize behavior by injecting a different strategy object at runtime. The context doesn't know or care about the algorithm's internal structure -- it delegates entirely to the strategy.

Here's the practical trade-off: the template method pattern gives you more control over the algorithm's flow. Because the base class owns the sequence of steps, it can enforce invariants, add logging between steps, or guarantee that certain steps always happen in a specific order. The strategy pattern gives you more flexibility -- you can swap the entire algorithm at runtime, combine strategies, or inject strategies via dependency injection without an inheritance relationship.

If your algorithm has a fixed structure with a few variable steps, the template method is the natural choice. If the algorithm itself varies or you need to swap behaviors at runtime through dependency injection, the strategy pattern is typically a better fit. You can also combine them: use a template method for the overall workflow structure and inject strategy objects for individual steps that need runtime flexibility.

The decorator design pattern is another alternative worth considering when you want to add behavior to an object without modifying its class -- but unlike the template method, decorators wrap behavior around an existing object rather than defining a fixed algorithm skeleton.

Frequently Asked Questions

What is the template method design pattern?

The template method design pattern is a behavioral pattern that defines the skeleton of an algorithm in a base class method and lets subclasses override specific steps without changing the algorithm's overall structure. The base class controls the sequence -- which steps run and in what order -- while subclasses provide the concrete implementations for individual steps. In C#, you implement this with an abstract class that has a non-virtual template method calling abstract and virtual methods.

How do I prevent subclasses from overriding the template method?

In C#, you don't need to do anything special if the template method is defined in a class (non-virtual methods can't be overridden by default). If you want to be explicit, you can mark the method as sealed if it was originally declared as virtual or override in a parent class. The goal is to ensure subclasses can only customize individual steps, not the overall algorithm flow.

What is a hook in the template method pattern?

A hook is a virtual method in the abstract base class that provides a default implementation -- usually empty -- and that subclasses can optionally override. Hooks let subclasses extend the algorithm at specific points without being forced to provide an implementation. Common uses include pre-processing, post-processing, logging, or boolean checks that control whether an optional step executes.

Can I use interfaces instead of abstract classes for the template method pattern?

The template method pattern relies on defining a concrete method (the template method) that calls abstract or virtual methods. Interfaces in C# support default interface methods (since C# 8), so it is technically possible to define a template method in an interface. However, abstract classes are the conventional and more readable choice because they clearly communicate the inheritance-based intent of the pattern and provide a natural place for shared state and helper methods.

When should I use the template method pattern instead of the strategy pattern?

Use the template method when your algorithm has a fixed structure and only specific steps vary between implementations. Use the strategy pattern when the entire algorithm varies or when you need to swap algorithms at runtime via composition. The template method pattern is inheritance-based and gives you tighter control over the execution order. The strategy pattern is composition-based and gives you more runtime flexibility.

Does the template method pattern violate the composition over inheritance principle?

The template method pattern does use inheritance, which some developers see as a drawback. However, it uses inheritance in a controlled way -- the base class defines a fixed structure, and subclasses only customize specific extension points. If the inheritance hierarchy stays shallow (one level of concrete subclasses below the abstract base), the pattern works well. If you find yourself building deep inheritance chains or needing runtime flexibility, consider refactoring toward composition-based patterns like the strategy pattern or the proxy design pattern.

How does the template method pattern support the Open/Closed Principle?

The pattern supports the Open/Closed Principle by allowing you to extend the algorithm's behavior (by creating new subclasses) without modifying the base class or existing subclasses. The template method in the base class stays closed for modification -- its sequence doesn't change. New behavior is added by creating a new concrete class that overrides the abstract and virtual methods. This makes it straightforward to add new processing strategies without touching existing, tested code.

Wrapping Up the Template Method Design Pattern in C#

The template method design pattern in C# is a practical behavioral pattern that locks down your algorithm's structure while keeping individual steps flexible. The base class owns the sequence, subclasses fill in the details, and hooks provide optional extension points -- all without the calling code needing to know which concrete implementation is running.

Start by identifying places in your codebase where multiple classes follow the same workflow but with different step implementations. Look for duplicated sequences of method calls across classes -- that's the algorithm skeleton waiting to be extracted into a template method. Pair it with dependency injection to manage your concrete subclasses, and consider the observer pattern if you need to broadcast notifications at specific points in the algorithm. You can find the template method pattern alongside other behavioral and structural patterns in the complete list of design patterns.

Factory Method Design Pattern in C#: Complete Guide

Master the Factory Method design pattern in C# with code examples, real-world scenarios, and practical guidance for flexible object creation.

How to Implement Factory Method Pattern in C#: Step-by-Step Guide

How to implement Factory Method pattern in C#: step-by-step guide with code examples, best practices, and common pitfalls for creational design patterns.

Vertical Slice Template - Dev Leader Weekly 15

Welcome to another issue of Dev Leader Weekly! We'll dive into a sample project you can download to use as a vertical slice template!

An error has occurred. This application may no longer respond until reloaded. Reload