Composition: Callbacks, Pipelines, Strategy
Functions that take functions: callback contracts, composed transforms, and the Strategy pattern in three lines.
Callbacks are contracts
A callback parameter tells callers what shape they may pass, not what will happen. Design them like any contract:
static List<string> TransformAll(
IEnumerable<string> source,
Func<string, string> transform) // ONE contract: string -> string
=> source.Select(transform).ToList();
Name the parameter for its role (transform, predicate, onComplete) โ
the name is the documentation.
Pipelines: compose small functions
Delegates compose; small single-purpose transforms chain into behavior:
Func<string, string> pipeline = s => s;
pipeline = pipeline.Compose(s => s.Trim())
.Compose(s => s.ToLowerInvariant());
(Or simply: source.Select(Trim).Select(Lower) โ LINQ is composition with
deferred execution, which Module 8 exploits fully.)
Strategy in three lines
Any place you would write switch (mode), consider passing the behavior:
static int Reduce(int[] values, int seed, Func<int, int, int> fold) =>
values.Aggregate(seed, fold);
var sum = Reduce(new[] { 1, 2, 3 }, 0, (a, b) => a + b);
var max = Reduce(new[] { 1, 2, 3 }, int.MinValue, Math.Max);
The switch version must change (and be re-tested) for every new mode; the delegate version accepts new behavior without modification โ open/closed in practice.
Check your understanding
- When is a switch BETTER than a delegate parameter? (Closed sets with pattern-exhaustiveness needs.)
- What does
Func<A,B>+Func<B,C>compose into? (Func<A,C>.)