Object-Oriented C# · Lesson 23

Delegates for a small replaceable calculation

Pass one focused behavior as a value when a whole strategy object is unnecessary.

How it works

A delegate represents a callable value with a particular signature. Func<T, TResult> is useful for a calculation that receives one input and produces one result. Passing that behavior into another function lets coordination stay fixed while a small policy varies. This can be simpler than declaring an interface and class for every tiny stateless operation.

Choose a signature that communicates the needed data. A delegate can capture values from its surrounding scope, so its behavior may depend on more than the explicit parameter. That is useful for configuration, but it also means a mutable captured variable can change later results. Keep such state deliberate, and prefer a named function when the behavior deserves a clear reusable identity.

Walk through the example. The Apply function receives an amount and a pricing delegate. One delegate is a named identity function; another lambda applies a ten-percent reduction. Both are called through the same Apply function and produce explicitly formatted results. The sample then supplies a tax-adding lambda directly at the call site, showing that no new coordinator class is needed for one focused variation.

Watch the boundary. A delegate is not automatically pure or safe. It can mutate captured state, throw, or perform external work. Document what the receiving API expects, especially if it may invoke the delegate more than once. Avoid hiding important order-dependent business actions inside an innocent-looking selector or pricing function whose caller expects a repeatable calculation.

Practice independently. Write a report formatter that receives a Func<int, string> for one numeric column. Supply plain, padded, and prefixed formatting behaviors, and compare the generated rows. Then create a deliberately stateful delegate that counts calls and observe how repeated invocation changes it. Explain why an API should document whether its delegate is a pure calculation or an action with effects.

A working example

using System;
using System.Collections.Generic;
using System.Linq;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;

decimal Apply(decimal amount, Func<decimal, decimal> policy) => policy(amount);
decimal Unchanged(decimal amount) => amount;
Func<decimal, decimal> discount = amount => amount * 0.9m;
Console.WriteLine(Apply(100m, Unchanged).ToString("F2", CultureInfo.InvariantCulture));
Console.WriteLine(Apply(100m, discount).ToString("F2", CultureInfo.InvariantCulture));
Console.WriteLine(Apply(100m, amount => amount * 1.2m).ToString("F2", CultureInfo.InvariantCulture));

Expected output

100.00
90.00
120.00
Try this
A delegate can carry state and effects; define the behavioral contract expected by its caller.