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