# Example 3 — Buyers: extract lambda in its purest form Third rep of the Split Loop with Lambda mechanics, in C#. Example 1 (`longRangeForecast`) had a drift trap and a dead call; the demo (`printSalesForecasts`) had three dependencies and grew into a role interface. This one has **one loop and one lambda-able expression, and nothing else** — the move in its purest form. Diagrams use the same [vocabulary as the main storyboard](storyboard.md). Kata theme: the sellers exist (`DailyForecasts` — Steve, Julie, Francis), the buyers don't. `Buyers` computes a buyer's shopping-list total; `PriceList` stands for the external pricing service. Exercise repo: `../exercise3/csharp`, end state: `../exercise3/solution`. --- ## Step 0 — one loop, one unwanted dependency **Goal:** the test is red or flaky — why? (`PriceList` adds random surge pricing; it stands for the external pricing service.) ![](svg/b0-start.svg) **Before:** ```csharp public int ShoppingListTotal(string buyerName) { var buyer = ByName(buyerName); var total = 0; foreach (var flavour in buyer.Favourites) { total += buyer.ScoopsOf(flavour) * priceList.PriceFor(flavour); // ← unwanted dependency } return total; } ``` **Checkpoint:** run the test. Flaky. What does the loop need from outside? *Given a flavour, what does it cost?* That question is the lambda. --- ## Step 1 — extract the pricing call into a lambda, and slide it **Goal:** the move, with nothing else in the way. Extract, then slide — the invocation stays in the loop. ![](svg/b1-lambda-slide.svg) **After:** ```csharp public int ShoppingListTotal(string buyerName) { Func priceFor = flavour => priceList.PriceFor(flavour); // ← SLID to the top... var buyer = ByName(buyerName); var total = 0; foreach (var flavour in buyer.Favourites) { total += buyer.ScoopsOf(flavour) * priceFor(flavour); // ← ...invocation stays here } return total; } ``` **Checkpoint:** compiles; test still flaky (the lambda still calls `PriceList` — that is fine). Unlike the scoring-loop example, `priceList` is a *field*, so the lambda slides all the way to the top. Slide, don't move. --- ## Step 2 — extract the loop; the lambda becomes a parameter **Goal:** select the loop, Extract Method. The loop touches no fields — the lambda is the only thing crossing the boundary. ![](svg/b2-extract-test.svg) **After:** ```csharp public int ShoppingListTotal(string buyerName) // ← no-arg overload: callers unchanged { return ShoppingListTotal(ByName(buyerName), priceList.PriceFor); // ← the "real" pricing, as a method group } public int ShoppingListTotal(Buyer buyer, Func priceFor) // ← the seam: pricing is a parameter { var total = 0; foreach (var flavour in buyer.Favourites) { total += buyer.ScoopsOf(flavour) * priceFor(flavour); } return total; } ``` (The extracted loop method touches no fields; it sits in the same class as `ByName` for convenience. Move it to a class without `PriceList` any time — that is step 4 of the full pattern, unblocked.) **The test — the whole point:** ```csharp [TestMethod] public void ShoppingListTotalUsesThePricingWePassIn() { // pure pricing function: everything costs 2. // Anke: 2 flavours x 2 scoops x 2 = 8 — deterministic, glanceable. var buyers = new Buyers(); var anke = new Buyer("Anke", new List { IceCream.Strawberry, IceCream.Vanilla }); Assert.AreEqual(8, buyers.ShoppingListTotal(anke, _ => 2)); } ``` **Checkpoint:** green — deterministically. `PriceList` is never touched. - `grep PriceFor src/` — the extracted method has no hits. - C# note: the extracted method stays an instance method here because the class also holds `ByName`; if you want a `static`, extract the loop only and keep the lookup in the composed method. **Debrief in one line:** the loop now reads "for each favourite flavour, add scoops times price" — and the pricing is a question anyone can answer with one line of test code.