Decorator Pattern in C#: Definition, Use Cases, Pros, Cons, and Examples

Decorator Pattern in C#: Definition, Use Cases, Pros, Cons, and Examples

The Decorator Pattern in C# is a structural design pattern that dynamically adds new behavior or responsibilities to objects without modifying their original code.

The Decorator Pattern wraps an existing object inside another object called a decorator, which adds extra functionality before or after delegating operations to the original object. It provides a flexible alternative to subclassing for extending behavior. Decorators implement the same interface as the wrapped object, allowing multiple decorators to be combined dynamically. This pattern is useful when behavior should be added at runtime without affecting other instances of the same class. In C#, the Decorator Pattern is commonly used for logging, caching, validation, authorization, compression, and UI enhancements.

Why We Use Decorator Pattern in C#?

We use the Decorator Pattern in C# to extend object functionality dynamically while keeping the original class unchanged. It helps avoid large inheritance hierarchies and promotes composition over inheritance. The pattern also enables combining multiple behaviors flexibly at runtime.

Main reasons for using it:

• Add behavior dynamically
• Avoid subclass explosion
• Follow composition over inheritance
• Keep classes open for extension
• Reuse behaviors independently
• Improve flexibility and maintainability

When Should We Use Decorator Pattern in C#?

The Decorator Pattern should be used when:

• Object behavior must be extended dynamically
• Inheritance would create too many subclasses
• Functionality should be reusable across objects
• Features should be combined at runtime
• Existing classes should remain unchanged
• Optional responsibilities need to be attached to objects

Typical programming problems solved by the Decorator Pattern:

• Logging systems
• Data compression
• Encryption/decryption
• Request validation
• Authorization and security
• Caching systems
• UI styling and enhancements

Structure of Decorator Pattern in C#

Component Responsibility
Component Defines common interface for objects and decorators
Concrete Component Original object with core behavior
Decorator Base wrapper class holding component reference
Concrete Decorator Adds additional behavior dynamically
Client Uses components through common interface

Decorator Pattern Examples in C#

Example 1: Coffee Customization System

Coffee objects should support optional add-ons dynamically.

using System;

public interface ICoffee
{
    string GetDescription();
    double GetCost();
}

public class SimpleCoffee : ICoffee
{
    public string GetDescription()
    {
        return "Simple Coffee";
    }

    public double GetCost()
    {
        return 5.0;
    }
}

public abstract class CoffeeDecorator : ICoffee
{
    protected ICoffee _coffee;

    protected CoffeeDecorator(ICoffee coffee)
    {
        _coffee = coffee;
    }

    public virtual string GetDescription()
    {
        return _coffee.GetDescription();
    }

    public virtual double GetCost()
    {
        return _coffee.GetCost();
    }
}

public class MilkDecorator : CoffeeDecorator
{
    public MilkDecorator(ICoffee coffee)
        : base(coffee)
    {
    }

    public override string GetDescription()
    {
        return _coffee.GetDescription() + ", Milk";
    }

    public override double GetCost()
    {
        return _coffee.GetCost() + 1.5;
    }
}

public class SugarDecorator : CoffeeDecorator
{
    public SugarDecorator(ICoffee coffee)
        : base(coffee)
    {
    }

    public override string GetDescription()
    {
        return _coffee.GetDescription() + ", Sugar";
    }

    public override double GetCost()
    {
        return _coffee.GetCost() + 0.5;
    }
}

class Program
{
    static void Main()
    {
        ICoffee coffee = new SimpleCoffee();

        coffee = new MilkDecorator(coffee);
        coffee = new SugarDecorator(coffee);

        Console.WriteLine(coffee.GetDescription());
        Console.WriteLine(coffee.GetCost());
    }
}

Example 2: Logging Decorator

Additional logging should be added without changing service classes.

using System;

public interface IService
{
    void Execute();
}

public class MainService : IService
{
    public void Execute()
    {
        Console.WriteLine("Service executed.");
    }
}

public abstract class ServiceDecorator : IService
{
    protected IService _service;

    protected ServiceDecorator(IService service)
    {
        _service = service;
    }

    public virtual void Execute()
    {
        _service.Execute();
    }
}

public class LoggingDecorator : ServiceDecorator
{
    public LoggingDecorator(IService service)
        : base(service)
    {
    }

    public override void Execute()
    {
        Console.WriteLine("Logging started.");

        _service.Execute();

        Console.WriteLine("Logging finished.");
    }
}

class Program
{
    static void Main()
    {
        IService service =
            new LoggingDecorator(new MainService());

        service.Execute();
    }
}

Example 3: Data Encryption Decorator

Data should optionally support encryption before saving.

using System;

public interface IDataSource
{
    void WriteData(string data);
}

public class FileDataSource : IDataSource
{
    public void WriteData(string data)
    {
        Console.WriteLine("Saving data: " + data);
    }
}

public abstract class DataSourceDecorator : IDataSource
{
    protected IDataSource _dataSource;

    protected DataSourceDecorator(IDataSource dataSource)
    {
        _dataSource = dataSource;
    }

    public virtual void WriteData(string data)
    {
        _dataSource.WriteData(data);
    }
}

public class EncryptionDecorator : DataSourceDecorator
{
    public EncryptionDecorator(IDataSource dataSource)
        : base(dataSource)
    {
    }

    public override void WriteData(string data)
    {
        string encrypted = Convert.ToBase64String(
            System.Text.Encoding.UTF8.GetBytes(data));

        _dataSource.WriteData(encrypted);
    }
}

class Program
{
    static void Main()
    {
        IDataSource source =
            new EncryptionDecorator(new FileDataSource());

        source.WriteData("Sensitive information");
    }
}

Most Common Real-World Use Cases of Decorator Pattern in C#

Use Case Description
Logging Systems Adding request or method logging dynamically
Caching Systems Adding cache layers around services
Authorization Applying security checks before execution
Validation Pipelines Adding validation logic dynamically
Compression and Encryption Wrapping data streams with transformations
UI Styling Adding visual behavior to UI components
Middleware Pipelines Extending request processing behavior

Advantages of Using Decorator Pattern in C#

1. Dynamic Behavior Extension

Functionality can be added at runtime.

2. Avoids Large Inheritance Trees

Uses composition instead of excessive subclassing.

3. Flexible Combination of Features

Multiple decorators can be stacked together.

4. Improves Reusability

Decorators can be reused across multiple objects.

5. Supports Open/Closed Principle

Classes remain closed for modification but open for extension.

6. Better Separation of Concerns

Each decorator focuses on a single responsibility.

Disadvantages (Weak Points) of Decorator Pattern in C#

1. Increased Complexity

Many small decorator classes may be created.

2. Harder Debugging

Nested decorators can make debugging more difficult.

3. Object Initialization Complexity

Decorator chains may become difficult to configure.

4. Performance Overhead

Additional wrapper layers add method call overhead.

5. Difficult Object Identity Tracking

Wrapped objects may become harder to identify directly.

Decorator Pattern vs Similar Patterns

Pattern Main Purpose Key Difference from Decorator Typical Usage
Decorator Add behavior dynamically Wraps objects to extend functionality Logging, caching, validation
Adapter Convert incompatible interfaces Focuses on compatibility rather than extension Legacy system integration
Proxy Control object access Focuses on access management and optimization Lazy loading, security
Composite Represent hierarchical structures Focuses on tree structures File systems, UI hierarchies
Facade Simplify subsystem usage Provides simplified interface instead of behavior extension Framework abstractions

Summary

The Decorator Pattern in C# is a powerful structural design pattern for dynamically extending object behavior without modifying existing classes. It promotes flexibility, composition over inheritance, and reusable feature combinations. The pattern is widely used in middleware pipelines, logging systems, caching layers, validation frameworks, and UI customization. Although it can introduce additional complexity and wrapper layers, it provides excellent scalability and maintainability for applications that require dynamic feature composition.

Structural Patterns in C#

6. Adapter
7. Bridge
8. Composite
9. Decorator
10. Facade
11. Flyweight
12. Proxy