BehavioralC#verifiedVerified
Strategy Pattern in C#
Defines a family of algorithms, encapsulates each one, and makes them interchangeable so the algorithm can vary independently from the clients that use it.
How to Implement the Strategy Pattern in C#
1Step 1: Define the strategy interface
public interface ISortStrategy<T>
{
T[] Sort(T[] data);
}2Step 2: Concrete strategies
public class BubbleSort<T> : ISortStrategy<T> where T : IComparable<T>
{
public T[] Sort(T[] data)
{
var arr = (T[])data.Clone();
for (var i = 0; i < arr.Length - 1; i++)
for (var j = 0; j < arr.Length - i - 1; j++)
if (arr[j].CompareTo(arr[j + 1]) > 0)
(arr[j], arr[j + 1]) = (arr[j + 1], arr[j]);
return arr;
}
}
public class QuickSort<T> : ISortStrategy<T> where T : IComparable<T>
{
public T[] Sort(T[] data)
{
var arr = (T[])data.Clone();
QuickSortImpl(arr, 0, arr.Length - 1);
return arr;
}
private static void QuickSortImpl(T[] arr, int lo, int hi)
{
if (lo >= hi) return;
var pivot = arr[hi];
var i = lo;
for (var j = lo; j < hi; j++)
if (arr[j].CompareTo(pivot) < 0)
(arr[i], arr[j]) = (arr[j], arr[i++]);
(arr[i], arr[hi]) = (arr[hi], arr[i]);
QuickSortImpl(arr, lo, i - 1);
QuickSortImpl(arr, i + 1, hi);
}
}3Step 3: Context uses the strategy
public class Sorter<T>(ISortStrategy<T> strategy)
{
public T[] Sort(T[] data) => strategy.Sort(data);
}using Microsoft.Extensions.Logging;
// [step] Define the compression strategy with async support
public interface ICompressionStrategy
{
string Name { get; }
Task<byte[]> CompressAsync(
byte[] data, CancellationToken ct = default);
Task<byte[]> DecompressAsync(
byte[] data, CancellationToken ct = default);
}
public record CompressionResult(
byte[] Data, string Algorithm, double Ratio, TimeSpan Duration);
// [step] Concrete strategies with real-world patterns
public class GzipStrategy : ICompressionStrategy
{
public string Name => "gzip";
public async Task<byte[]> CompressAsync(
byte[] data, CancellationToken ct = default)
{
using var output = new MemoryStream();
await using var gzip = new System.IO.Compression.GZipStream(
output, System.IO.Compression.CompressionLevel.Optimal);
await gzip.WriteAsync(data, ct);
await gzip.FlushAsync(ct);
return output.ToArray();
}
public async Task<byte[]> DecompressAsync(
byte[] data, CancellationToken ct = default)
{
using var input = new MemoryStream(data);
using var output = new MemoryStream();
await using var gzip = new System.IO.Compression.GZipStream(
input, System.IO.Compression.CompressionMode.Decompress);
await gzip.CopyToAsync(output, ct);
return output.ToArray();
}
}
public class BrotliStrategy : ICompressionStrategy
{
public string Name => "brotli";
public async Task<byte[]> CompressAsync(
byte[] data, CancellationToken ct = default)
{
using var output = new MemoryStream();
await using var brotli = new System.IO.Compression.BrotliStream(
output, System.IO.Compression.CompressionLevel.Optimal);
await brotli.WriteAsync(data, ct);
await brotli.FlushAsync(ct);
return output.ToArray();
}
public async Task<byte[]> DecompressAsync(
byte[] data, CancellationToken ct = default)
{
using var input = new MemoryStream(data);
using var output = new MemoryStream();
await using var brotli = new System.IO.Compression.BrotliStream(
input, System.IO.Compression.CompressionMode.Decompress);
await brotli.CopyToAsync(output, ct);
return output.ToArray();
}
}
// [step] Context with strategy selection and telemetry
public sealed class CompressionService(
ILogger<CompressionService> logger)
{
private readonly Dictionary<string, ICompressionStrategy> _strategies = [];
public CompressionService Register(ICompressionStrategy strategy)
{
_strategies[strategy.Name] = strategy;
return this;
}
public async Task<CompressionResult> CompressAsync(
byte[] data, string algorithm = "gzip",
CancellationToken ct = default)
{
var strategy = _strategies.GetValueOrDefault(algorithm)
?? throw new ArgumentException(
$"Unknown algorithm: {algorithm}. " +
$"Available: {string.Join(", ", _strategies.Keys)}");
var sw = System.Diagnostics.Stopwatch.StartNew();
var compressed = await strategy.CompressAsync(data, ct);
sw.Stop();
var ratio = data.Length > 0
? (double)compressed.Length / data.Length : 0;
logger.LogInformation(
"Compressed {Original} -> {Compressed} bytes ({Ratio:P1}) " +
"using {Algorithm} in {Duration}ms",
data.Length, compressed.Length, ratio,
algorithm, sw.ElapsedMilliseconds);
return new CompressionResult(
compressed, algorithm, ratio, sw.Elapsed);
}
}Strategy Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Strategy Pattern in the Real World
“Consider a GPS app offering route options: fastest, shortest, or avoid tolls. The destination is the same, but the navigation algorithm changes based on your preference. The app (context) simply hands the journey off to whichever routing strategy you selected; you can switch strategies mid-trip without the app needing to change its structure.”