From c153aba61e79871e14c7d7dc98275d5b9477dec1 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 29 Sep 2026 13:15:45 +0000 Subject: [PATCH] Add three C# demo examples for Slice with Lambda - MetaphorFree.cs: guard -> SomeDependency.mightThrowWhenPreconditionNotSatisfied(a) -> tail, capture-to-parameter story for the deck - WeatherStation.cs: SprinklerController, LightSensor.Read() byte -> SunExposure, tail branches - IceCreamShop.cs: third repetition, Thermometer byte -> Warmth, tail branches --- .../exercise/csharp/IceCreamShop.cs | 79 +++++++++++++++++++ .../exercise/csharp/MetaphorFree.cs | 54 +++++++++++++ .../exercise/csharp/WeatherStation.cs | 71 +++++++++++++++++ 3 files changed, 204 insertions(+) create mode 100644 ideas/split-loop-with-lambda/exercise/csharp/IceCreamShop.cs create mode 100644 ideas/split-loop-with-lambda/exercise/csharp/MetaphorFree.cs create mode 100644 ideas/split-loop-with-lambda/exercise/csharp/WeatherStation.cs diff --git a/ideas/split-loop-with-lambda/exercise/csharp/IceCreamShop.cs b/ideas/split-loop-with-lambda/exercise/csharp/IceCreamShop.cs new file mode 100644 index 0000000..d5da947 --- /dev/null +++ b/ideas/split-loop-with-lambda/exercise/csharp/IceCreamShop.cs @@ -0,0 +1,79 @@ +// Example 3 — Ice cream shop demo of Slice with Lambda (C#) +// +// Condensed mechanics: https://sammancoaching.org/refactorings/slice.html +// (where that page says mock, we use a lambda instead). +// +// Third repetition, same complexity as ScheduleWatering in WeatherStation.cs +// (guard -> one dependency call -> map to enum -> constant tail), so +// participants can practice extracting a lambda a second time. The C# +// delegate syntax is worth the repetition: +// +// Func readTemperature = () => _thermometer.Read(); +// +// Cast: +// Thermometer — hardware-ish dependency. Read() returns a raw +// byte value (not calibrated, the client decides +// what it means). +// Warmth — enum. Reading > 25 => Warm, otherwise Cold. +// (Sunny/Shady's cousin from WeatherStation.cs.) +// ThawAmount — enum. None / Medium / Low. +// IceCreamShop — the client. Owns the _thawedToday flag, so the +// guard is about client state, not sensor state. + +using System; + +public enum Warmth +{ + Cold, + Warm +} + +public enum ThawAmount +{ + None, + Medium, + Low +} + +public class Thermometer +{ + private readonly Random _rng = new Random(); // stand-in for the hardware + + public byte Read() + { + return (byte)_rng.Next(50); // raw sensor value + } +} + +public class IceCreamShop +{ + private readonly Thermometer _thermometer; + private bool _thawedToday; // event-style client state + + public IceCreamShop(Thermometer thermometer) + { + _thermometer = thermometer; + } + + public ThawAmount ThawStock() + { + if (_thawedToday) // guard: client state, stays + { + return ThawAmount.None; + } + + _thawedToday = true; // set only after thawing + byte temperature = _thermometer.Read(); // middle: unwanted dependency + Warmth warmth = ToWarmth(temperature); + if (warmth == Warmth.Warm) // tail: complex behaviour below + { // the extraction point + return ThawAmount.Medium; + } + return ThawAmount.Low; + } + + private static Warmth ToWarmth(byte temperature) + { + return temperature > 25 ? Warmth.Warm : Warmth.Cold; + } +} diff --git a/ideas/split-loop-with-lambda/exercise/csharp/MetaphorFree.cs b/ideas/split-loop-with-lambda/exercise/csharp/MetaphorFree.cs new file mode 100644 index 0000000..73820d4 --- /dev/null +++ b/ideas/split-loop-with-lambda/exercise/csharp/MetaphorFree.cs @@ -0,0 +1,54 @@ +// Example 1 — Metaphor-free demo of Slice with Lambda (C#) +// +// Condensed mechanics: https://sammancoaching.org/refactorings/slice.html +// (where that page says mock, we use a lambda instead). +// +// Shape of the method (per zeroth-draft-of-refactoring-pattern.md): +// +// guard clauses — must run first +// middle: dependency — one call on an object we don't want in the +// extracted method +// tail: depends on b — uses the value the dependency produced +// +// Deliberately NOT a loop: this is the smallest method that shows the +// mechanics. The dependency both validates its precondition (using the +// parameter a) and produces b — one expression, one thing to move into +// the lambda. +// +// Unwanted dependency: SomeDependency — might throw when its precondition +// on a is not satisfied, and returns an unpredictable value. An approval +// test on Calculate is flaky until the lambda is stubbed. + +using System; + +public class Calculator +{ + private readonly SomeDependency _someDependency; + + public Calculator(SomeDependency someDependency) + { + _someDependency = someDependency; + } + + public int Calculate(int a) + { + if (a < 0) return 0; // guard clause: stays + int b = _someDependency.mightThrowWhenPreconditionNotSatisfied(a); // middle: unwanted dependency + int c = a + b; // tail: depends on b + return c * 2; + } +} + +public class SomeDependency +{ + private readonly Random _rng = new Random(); // stand-in for unpredictable computation + + public int mightThrowWhenPreconditionNotSatisfied(int a) + { + if (a > 99) + { + throw new InvalidOperationException("precondition not satisfied"); + } + return _rng.Next(100); + } +} diff --git a/ideas/split-loop-with-lambda/exercise/csharp/WeatherStation.cs b/ideas/split-loop-with-lambda/exercise/csharp/WeatherStation.cs new file mode 100644 index 0000000..d374214 --- /dev/null +++ b/ideas/split-loop-with-lambda/exercise/csharp/WeatherStation.cs @@ -0,0 +1,71 @@ +// Example 2 — Weather Station domain demo of Slice with Lambda (C#) +// +// Condensed mechanics: https://sammancoaching.org/refactorings/slice.html +// (where that page says mock, we use a lambda instead). +// +// Cast (Head First Design Patterns Weather Station, reduced): +// LightSensor — hardware-ish dependency. Read() returns a byte. +// SunExposure — enum. Reading > 128 => Sunny, otherwise Shady. +// WateringDuration — enum. None / Medium / Short. +// (Long is YAGNI until the for-loop version of this +// exercise exists.) +// SprinklerController — the client. Owns the _wateredToday flag, so the +// guard is about client state, not sensor state. + +using System; + +public enum SunExposure +{ + Shady, + Sunny +} + +public enum WateringDuration +{ + None, + Medium, + Short +} + +public class LightSensor +{ + private readonly Random _rng = new Random(); // stand-in for hardware noise + + public byte Read() + { + return (byte)_rng.Next(256); + } +} + +public class SprinklerController +{ + private readonly LightSensor _lightSensor; + private bool _wateredToday; // event-style client state + + public SprinklerController(LightSensor lightSensor) + { + _lightSensor = lightSensor; + } + + public WateringDuration ScheduleWatering() + { + if (_wateredToday) // guard: client state, stays + { + return WateringDuration.None; + } + + _wateredToday = true; // set only after watering + byte reading = _lightSensor.Read(); // middle: unwanted dependency + SunExposure exposure = ToSunExposure(reading); + if (exposure == SunExposure.Sunny) // tail: complex behaviour below + { // the extraction point + return WateringDuration.Medium; + } + return WateringDuration.Short; + } + + private static SunExposure ToSunExposure(byte reading) + { + return reading > 128 ? SunExposure.Sunny : SunExposure.Shady; + } +}