Decorator vs Proxy Pattern in C#: Key Differences Explained
If you've ever looked at the source code for a decorator and a proxy side by side, you might have done a double-take. Both patterns wrap an object. Both implement the same interface as the object they wrap. Both forward calls to that inner object. So why do we treat them as different patterns at all? The decorator vs proxy pattern C# comparison is one of the most common sources of confusion among developers learning structural design patterns -- and for good reason.
The answer comes down to intent. These two patterns solve fundamentally different problems despite having nearly identical structures. In this article, we'll break down what each pattern does, compare their implementations side by side using the same C# interface, and give you clear criteria for choosing the right one. If you want broader context on structural and other design patterns, check out The Big List of Design Patterns for a comprehensive overview.
Quick Refresher: What Is the Decorator Pattern?
The decorator pattern adds new behavior to an existing object by wrapping it in another object that implements the same interface. The decorator delegates to the wrapped object and layers additional functionality on top -- logging, caching, validation, retry logic, or any other cross-cutting concern.
The critical characteristic of the decorator pattern is that the wrapped object always exists and is always called. The decorator doesn't decide whether to call the inner object. It always does. Its job is to do something before, after, or around that call.
Here's a minimal example to set the stage:
using System;
public interface INotificationSender
{
void Send(string message);
}
public sealed class EmailNotificationSender : INotificationSender
{
public void Send(string message)
{
Console.WriteLine($"Sending email: {message}");
}
}
// Decorator: adds logging around the real sender
public sealed class LoggingNotificationSender : INotificationSender
{
private readonly INotificationSender _inner;
public LoggingNotificationSender(INotificationSender inner)
{
_inner = inner;
}
public void Send(string message)
{
Console.WriteLine($"[LOG] Sending notification: {message}");
_inner.Send(message);
Console.WriteLine("[LOG] Notification sent successfully");
}
}
The decorator always forwards the call. It just adds behavior around it. You can stack multiple decorators to compose different combinations of behavior, which is one of the pattern's biggest strengths. For a deeper look at how decorators compose in real applications, take a look at how composition in C# enables flexible object structures.
Quick Refresher: What Is the Proxy Pattern?
The proxy pattern controls access to an object by sitting between the client and the real object. Unlike the decorator, the proxy may or may not call the inner object. It might delay creation of the real object until it's actually needed (lazy initialization). It might check permissions before allowing the call through. It might return a cached result without touching the real object at all.
The critical characteristic of the proxy pattern is that it manages access to the underlying object. The proxy decides if and when the real object gets involved.
Common proxy types include:
- Virtual proxy -- delays creation of an expensive object until the first time it's actually used.
- Protection proxy -- checks authorization before forwarding the call.
- Caching proxy -- serves cached results to avoid redundant work.
- Remote proxy -- represents an object that lives in a different process, machine, or network boundary.
Each of these controls access in a different way, but the underlying principle is the same: the proxy gates the interaction between the client and the real object.
Structural Similarities
This is where the confusion starts when comparing decorator vs proxy pattern in C#. If you draw a UML diagram for each pattern, they look almost identical.
Both patterns share these structural traits:
- Same interface: The decorator, the proxy, and the real object all implement the same interface. Client code doesn't know which one it's talking to.
- Wrapping: Both hold a reference to an inner object and delegate calls to it.
- Transparent substitution: You can pass a decorator or a proxy anywhere the real object is expected without changing client code.
If you're familiar with other structural patterns, this wrapping technique shows up in the adapter pattern and the facade pattern as well -- though those serve different purposes entirely. The composite pattern also leverages shared interfaces, but for tree-like object structures rather than wrapping.
The structural overlap between the decorator and the proxy is why so many developers conflate them. The difference isn't in the shape of the code. It's in the reason the code exists.
The Key Difference: Intent
Here's the single most important distinction in the decorator vs proxy pattern C# comparison:
- Decorator adds behavior. It enhances what the wrapped object does by layering functionality on top.
- Proxy controls access. It governs whether, when, and how the wrapped object is reached.
A decorator always calls the inner object. A proxy might not. A decorator is about "what else should happen." A proxy is about "should this happen at all."
Think of it this way: a decorator is like adding a postage stamp and return address to a letter before mailing it. The letter still gets mailed. A proxy is like a mailroom clerk who checks whether you're authorized to send the letter in the first place -- and might refuse to send it.
This intent-based distinction means you'll reach for each pattern in completely different situations, even though the code structure looks remarkably similar.
Side-by-Side Code Comparison
The best way to see the decorator vs proxy pattern in C# difference is to implement both patterns against the same interface. We'll use an IDocumentRepository that stores and retrieves documents. One wrapper is a decorator that adds logging. The other is a proxy that adds lazy loading and access control.
The Shared Interface and Core Implementation
using System;
using System.Collections.Generic;
public sealed class Document
{
public string Id { get; }
public string Title { get; }
public string Content { get; }
public Document(string id, string title, string content)
{
Id = id;
Title = title;
Content = content;
}
}
public interface IDocumentRepository
{
Document GetById(string id);
void Save(Document document);
}
public sealed class SqlDocumentRepository : IDocumentRepository
{
public Document GetById(string id)
{
Console.WriteLine(
$"[SQL] Querying database for document {id}");
return new Document(
id,
"Sample Document",
"Document content from database");
}
public void Save(Document document)
{
Console.WriteLine(
$"[SQL] Saving document {document.Id} to database");
}
}
Decorator: Adding Logging Behavior
The decorator always calls the inner repository. It adds logging before and after each operation, but it never prevents the operation from happening.
using System;
using System.Diagnostics;
public sealed class LoggingDocumentRepository : IDocumentRepository
{
private readonly IDocumentRepository _inner;
public LoggingDocumentRepository(IDocumentRepository inner)
{
_inner = inner;
}
public Document GetById(string id)
{
Console.WriteLine(
$"[LOG] Retrieving document {id}");
var stopwatch = Stopwatch.StartNew();
var document = _inner.GetById(id);
stopwatch.Stop();
Console.WriteLine(
$"[LOG] Retrieved document {id} " +
$"in {stopwatch.ElapsedMilliseconds}ms");
return document;
}
public void Save(Document document)
{
Console.WriteLine(
$"[LOG] Saving document {document.Id}");
var stopwatch = Stopwatch.StartNew();
_inner.Save(document);
stopwatch.Stop();
Console.WriteLine(
$"[LOG] Saved document {document.Id} " +
$"in {stopwatch.ElapsedMilliseconds}ms");
}
}
Notice how the decorator never makes decisions about whether to proceed. It always calls _inner. That's the hallmark of the decorator pattern.
Proxy: Controlling Access
The proxy does something fundamentally different. It decides whether the call should go through at all, and it controls when the real repository is created.
using System;
public sealed class SecureDocumentRepositoryProxy : IDocumentRepository
{
private readonly Func<IDocumentRepository> _repositoryFactory;
private readonly IUserContext _userContext;
private IDocumentRepository? _repository;
public SecureDocumentRepositoryProxy(
Func<IDocumentRepository> repositoryFactory,
IUserContext userContext)
{
_repositoryFactory = repositoryFactory;
_userContext = userContext;
}
public Document GetById(string id)
{
if (!_userContext.HasPermission("documents:read"))
{
throw new UnauthorizedAccessException(
"User does not have permission to read documents");
}
return GetRepository().GetById(id);
}
public void Save(Document document)
{
if (!_userContext.HasPermission("documents:write"))
{
throw new UnauthorizedAccessException(
"User does not have permission to save documents");
}
GetRepository().Save(document);
}
private IDocumentRepository GetRepository()
{
// Lazy initialization: create the real
// repository only when first needed
_repository ??= _repositoryFactory();
Console.WriteLine(
"[PROXY] Repository access granted");
return _repository;
}
}
public interface IUserContext
{
bool HasPermission(string permission);
}
This proxy does two things the decorator would never do. First, it might throw an exception and prevent the call from reaching the real repository entirely. Second, it delays the creation of the real repository until the first authorized call -- a virtual proxy technique. The proxy controls access. The decorator adds behavior.
When to Choose Decorator
Understanding the decorator vs proxy pattern in C# starts with knowing when each pattern fits. The decorator pattern is your tool when you want to layer optional, composable behavior on top of existing functionality. Reach for the decorator when these conditions apply:
Adding cross-cutting concerns. Logging, metrics, validation, retry logic -- these concerns apply uniformly across many services. The decorator pattern lets you write each concern once and apply it wherever needed. This is one of the pattern's strongest use cases and pairs naturally with how other design pattern comparisons separate behavior into composable units.
Stacking multiple behaviors. When you need logging and caching and validation on the same service, decorators compose cleanly. Each decorator wraps the previous one, creating a pipeline of behavior. You control the order, and adding or removing a layer doesn't affect the others.
Keeping behavior optional. Different environments might need different behaviors. Production might need logging, metrics, and retry logic. Development might only need logging. With decorators, you wire up exactly the layers each environment needs through your dependency injection configuration.
Respecting the Open/Closed Principle. When you can't or shouldn't modify the wrapped class, decorators let you extend behavior without touching the original source. This is particularly valuable in shared libraries and stable codebases.
When to Choose Proxy
The proxy pattern is your tool when you need to control how, whether, or when a client accesses the underlying object. Reach for the proxy when these conditions apply:
Lazy initialization of expensive objects. If creating the real object involves opening a database connection, loading a large file, or making a network call, a virtual proxy delays that cost until the first method call. The client doesn't know or care that the real object hasn't been created yet.
Access control and authorization. When certain operations require specific permissions, a protection proxy checks authorization before forwarding the call. This keeps security logic separate from business logic and ensures every access point is covered.
Remote service wrapping. When the real object lives across a network boundary, a remote proxy handles serialization, network calls, and error handling. The client works with a local interface while the proxy manages the communication details. Similar to how the adapter pattern bridges interface mismatches, the remote proxy bridges location differences.
Caching to avoid redundant work. A caching proxy intercepts calls and returns stored results when appropriate, preventing the real object from doing duplicate work. Unlike a caching decorator -- which always calls the inner object and caches the result -- a caching proxy might skip the inner call entirely when valid cached data exists.
Can You Combine Them?
Absolutely. The decorator vs proxy pattern in C# isn't an either-or choice. Decorators and proxies work together naturally because they share the same structural pattern. You can wrap a proxy with a decorator, or a decorator-wrapped object with a proxy. Each layer does its own job without interfering with the others.
Here's a practical example: a proxy handles authorization and lazy loading while a decorator adds logging on top. This is similar to how other pattern combinations work together, as explored in builder vs fluent interface comparisons where complementary patterns enhance each other.
using System;
public static class DocumentRepositoryFactory
{
public static IDocumentRepository Create(
IUserContext userContext)
{
// Start with the proxy controlling access
IDocumentRepository repository =
new SecureDocumentRepositoryProxy(
() => new SqlDocumentRepository(),
userContext);
// Layer the decorator on top for logging
repository = new LoggingDocumentRepository(repository);
return repository;
}
}
// Usage
// var userContext = new MyUserContext();
// var repo = DocumentRepositoryFactory.Create(userContext);
// var doc = repo.GetById("doc-123");
When a client calls GetById on this composed object, here's what happens in order:
- The
LoggingDocumentRepositorydecorator logs the start of the operation. - It calls into the
SecureDocumentRepositoryProxy. - The proxy checks if the user has
documents:readpermission. - If authorized, the proxy lazily creates the
SqlDocumentRepository(if it hasn't already) and forwards the call. - The
SqlDocumentRepositoryexecutes the query. - Control returns up the chain, and the logging decorator records the elapsed time.
Each wrapper does exactly one job. The proxy controls access. The decorator adds observability. Neither knows about the other, and both can be independently tested, added, or removed.
You can also register this composition through dependency injection for cleaner production code:
using Microsoft.Extensions.DependencyInjection;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddDocumentRepository(
this IServiceCollection services)
{
services.AddScoped<SqlDocumentRepository>();
services.AddScoped<IDocumentRepository>(sp =>
{
var userContext =
sp.GetRequiredService<IUserContext>();
IDocumentRepository repository =
new SecureDocumentRepositoryProxy(
() => sp.GetRequiredService<
SqlDocumentRepository>(),
userContext);
repository =
new LoggingDocumentRepository(repository);
return repository;
});
return services;
}
}
This pattern of combining proxy and decorator is common in enterprise applications where you need both access control and observability on the same service.
Comparison Summary Table
Here's a quick-reference table summarizing the decorator vs proxy pattern in C# differences:
| Feature | Decorator | Proxy |
|---|---|---|
| Primary intent | Add behavior | Control access |
| Calls inner object? | Always | Conditionally |
| Inner object creation | Receives it via constructor | May create it lazily |
| Typical use cases | Logging, caching (additive), validation, retry | Lazy loading, authorization, remote access, caching (gatekeeping) |
| Composability | Highly stackable, multiple decorators chain | Typically one proxy per concern |
| Client awareness | None -- transparent | None -- transparent |
| Inner object lifecycle | Does not manage it | Often manages it |
| Relationship to inner | Enhances it | Gates it |
Both patterns implement the same interface, wrap an inner object, and are transparent to client code. The difference is always in what they do with that wrapping.
Frequently Asked Questions
Is a caching wrapper a decorator or a proxy?
This is one of the most practical questions when comparing decorator vs proxy pattern in C#. If the wrapper always calls the inner object and stores the result for future use, that's a decorator -- it's adding caching behavior on top. If the wrapper checks its cache first and skips the inner call when cached data is available, that's a proxy -- it's controlling access to the inner object. The distinction hinges on whether the inner object is always invoked.
Can a single class be both a decorator and a proxy?
Technically yes, but it violates the single responsibility principle. If your wrapper both adds behavior and controls access, you should split it into two classes -- one decorator and one proxy. Compose them together so each class has a clear, single purpose. The combined usage example in this article demonstrates exactly this approach.
Do decorators and proxies have performance implications?
Each wrapper adds a layer of indirection, which means an additional method call in the chain. In practice, this overhead is negligible for most applications -- the .NET runtime handles virtual dispatch efficiently. The bigger performance consideration is what the wrapper itself does. A caching proxy can dramatically improve performance by avoiding expensive operations, while a logging decorator adds the cost of writing log entries.
How does the proxy pattern differ from the facade pattern?
The facade pattern simplifies a complex subsystem by providing a unified interface over multiple objects. The proxy pattern wraps a single object and controls access to it. The facade changes the interface (simplifies it), while the proxy preserves the same interface. They solve different problems entirely.
Should I use the proxy pattern for all authorization checks?
Not necessarily. If your application uses a middleware pipeline or aspect-oriented approach for authorization, you may not need protection proxies at all. Proxies are most useful when authorization logic is tightly coupled to specific operations on specific objects. For broad, policy-based authorization, middleware or filters are often a better fit.
How do these patterns relate to the abstract factory or factory method patterns?
These are different pattern categories entirely. Factory Method and Abstract Factory are creational patterns -- they deal with how objects are created. Decorator and proxy are structural patterns -- they deal with how objects are composed and interact. You might use a factory to create the right proxy or decorator instance, but the patterns themselves solve different problems.
Can I use dependency injection to wire up proxies and decorators?
Yes, and it's the recommended approach for decorator vs proxy pattern in C# applications. Most DI containers support registering decorators and proxies through factory delegates. The code example in the combination section of this article shows exactly how to register a proxy wrapped by a decorator using IServiceCollection. This keeps your composition logic centralized and easy to modify.
Wrapping Up Decorator vs Proxy in C#
The decorator vs proxy pattern C# distinction is simple once you internalize it: decorators add behavior, proxies control access. The code looks nearly identical, but the intent is what separates them. A decorator always calls the wrapped object and enhances the interaction. A proxy decides whether, when, and how the wrapped object gets called.
When you're unsure which one you need, ask yourself a single question: "Am I adding something to the call, or am I gating the call?" If you're adding -- logging, timing, validation, metrics -- that's a decorator. If you're gating -- checking permissions, delaying creation, intercepting for caching -- that's a proxy. And when you need both, compose them together. Each pattern does its job, and neither needs to know about the other.

