StructuralC#verifiedVerified
Proxy Pattern in C#
Provides a surrogate or placeholder for another object to control access, add lazy initialization, caching, logging, or access control.
How to Implement the Proxy Pattern in C#
1Step 1: Define the subject interface
public interface IImage
{
string Display();
}2Step 2: Real subject (expensive to create)
public class HighResImage(string filename) : IImage
{
private readonly string _data = $"[Heavy data for {filename}]";
public string Display() => $"Displaying {filename}: {_data}";
}3Step 3: Proxy delays creation until first use (lazy loading)
public class ImageProxy(string filename) : IImage
{
private HighResImage? _realImage;
public string Display()
{
_realImage ??= new HighResImage(filename);
return _realImage.Display();
}
}
// Usage:
// IImage image = new ImageProxy("photo.jpg");
// // Image not loaded yet
// Console.WriteLine(image.Display()); // Now loadedusing System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
// [step] Define a service interface for API access
public interface IApiService
{
Task<string> GetAsync(string endpoint, CancellationToken ct = default);
Task<string> PostAsync(string endpoint, string body,
CancellationToken ct = default);
}
// [step] Real service implementation
public class ApiService : IApiService
{
public async Task<string> GetAsync(
string endpoint, CancellationToken ct = default)
{
await Task.Delay(100, ct);
return $"Response from {endpoint}";
}
public async Task<string> PostAsync(
string endpoint, string body, CancellationToken ct = default)
{
await Task.Delay(100, ct);
return $"Created at {endpoint}";
}
}
// [step] Caching proxy with TTL
public class CachingProxy(
IApiService inner, TimeSpan ttl,
ILogger<CachingProxy> logger) : IApiService
{
private readonly ConcurrentDictionary<string, (string Value, DateTime Expiry)>
_cache = new();
public async Task<string> GetAsync(
string endpoint, CancellationToken ct = default)
{
if (_cache.TryGetValue(endpoint, out var entry)
&& entry.Expiry > DateTime.UtcNow)
{
logger.LogDebug("Cache hit: {Endpoint}", endpoint);
return entry.Value;
}
var result = await inner.GetAsync(endpoint, ct);
_cache[endpoint] = (result, DateTime.UtcNow + ttl);
logger.LogDebug("Cached: {Endpoint}", endpoint);
return result;
}
public Task<string> PostAsync(
string endpoint, string body, CancellationToken ct = default)
{
// Invalidate cache for this endpoint on mutation
_cache.TryRemove(endpoint, out _);
return inner.PostAsync(endpoint, body, ct);
}
}
// [step] Rate-limiting proxy
public class RateLimitingProxy(
IApiService inner, int maxPerSecond,
ILogger<RateLimitingProxy> logger) : IApiService
{
private readonly SemaphoreSlim _semaphore = new(maxPerSecond, maxPerSecond);
private readonly System.Timers.Timer _timer;
public RateLimitingProxy(IApiService inner, int maxPerSecond,
ILogger<RateLimitingProxy> logger, bool start = true)
: this(inner, maxPerSecond, logger)
{
_timer = new System.Timers.Timer(1000);
_timer.Elapsed += (_, _) =>
{
var toRelease = maxPerSecond - _semaphore.CurrentCount;
if (toRelease > 0) _semaphore.Release(toRelease);
};
if (start) _timer.Start();
}
public async Task<string> GetAsync(
string endpoint, CancellationToken ct = default)
{
await _semaphore.WaitAsync(ct);
logger.LogDebug("Rate limit permit acquired for GET {Endpoint}",
endpoint);
return await inner.GetAsync(endpoint, ct);
}
public async Task<string> PostAsync(
string endpoint, string body, CancellationToken ct = default)
{
await _semaphore.WaitAsync(ct);
logger.LogDebug("Rate limit permit acquired for POST {Endpoint}",
endpoint);
return await inner.PostAsync(endpoint, body, ct);
}
}Proxy Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Proxy Pattern in the Real World
“A corporate receptionist acts as a proxy for the CEO. When someone wants to meet the CEO, the receptionist checks credentials, schedules the meeting, and logs the visit before granting access. The visitor interacts with the receptionist using the same protocol they would use with the CEO—the receptionist simply adds control and record-keeping around that access.”