Modern C# · Lesson 19

Strategy functions with delegates

Pass a calculation policy instead of duplicating an entire workflow.

How it works

A delegate represents a callable operation with a particular signature. Func<int, int> accepts one integer and returns one integer. Checkout uses that shape as a small policy boundary: it calculates a subtotal in one place, then asks the supplied adjustment function to produce the final amount.

The first lambda returns the subtotal unchanged, so the result is ten. The second subtracts two, producing eight. The aggregation code is identical for both calls. This is useful when the varying part of a workflow is a small calculation, comparison, or formatting rule rather than an entirely different object model.

The signature describes input and output types, but not every business promise. A delegate could return a negative price, throw an exception, or capture mutable outside state. Checkout must decide which behavior it accepts. If final totals must never be negative, enforce that rule at the boundary instead of relying on every policy author to remember it.

Keep the policy focused. A discount function that secretly saves files or updates a user account would make a simple calculation difficult to reason about. Separating calculation from side effects makes the workflow easier to test: the same prices and policy can produce an expected result without arranging unrelated infrastructure.

Practice by adding an adjustment that caps the final price at seven and another that charges one extra unit for handling. Predict the results for an empty price list as well as the existing two items. Explain whether a discount should run before or after a cap when both rules are combined.

If a policy needs several related operations or long-lived state, an interface may describe it more clearly than a growing set of delegates. The important design question is where variation belongs. Start with a small explicit signature, give the operation a meaningful name at its call site, and test its boundary cases independently.

A working example

using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;

Console.WriteLine(Checkout(new[] { 4, 6 }, sum => sum));
Console.WriteLine(Checkout(new[] { 4, 6 }, sum => sum - 2));
static int Checkout(int[] prices, Func<int, int> adjust) {
    int subtotal = prices.Sum();
    return adjust(subtotal);
}

Expected output

10
8
Try this
The workflow owns aggregation; the delegate owns the adjustment rule.