Featured resource
2026 Tech Forecast
2026 Tech Forecast

1,500+ tech insiders, business leaders, and Pluralsight Authors share their predictions on what’s shifting fastest and how to stay ahead.

Download the forecast
  • Lab
    • Libraries: If you want this lab, consider one of these libraries.
    • Core Tech
Labs

Guided: C# 14 Delegates, Events, and Lambdas

This C# 14 CodeLab incorporates the use of features such as delegates, lambda functions, and event-driven behavior. You will work on a small .NET console application that has been scaffolded for you, building upon usage of delegates as contracts for certain behaviors and lambda functions to supply said behaviors. By the end of this lab, you will have defined a publisher-subscriber abstraction wherein you connect components by subscribing to event-driven outcomes.

Lab platform
Lab Info
Level
Advanced
Last updated
May 11, 2026
Duration
45m

Contact sales

By clicking submit, you agree to our Privacy Policy and Terms of Use, and consent to receive marketing emails from Pluralsight.
Table of Contents
  1. Challenge

    Introduction

    Welcome to the Guided: C# 14 Delegates, Events, and Lambdas CodeLab.

    In this lab, you will complete a single-player console game where you choose Move.Cooperate or Move.Defect and a computer opponent answers with a strategy callback. You have already been provided with a game runner in SoloRoundGame/ConsoleGameRunner.cs, the domain types in RoundContext, RoundResult, and RoundResolvedEventArgs, and the scaffolded members that shape each task you will complete (labelled as TODO in the comments). You will fill in the missing logic for round resolution, callbacks, events, subscriber updates, and formatting.

    You will use several callable "shapes" during the exercise. You use ComputerMoveSelector when you want a named delegate with game-specific meaning: upcoming round number in, computer move out. You use Action<RoundResult> when you want notification without a return value. You use Func<RoundContext, Move> and Func<RoundResult, string> when you want a value back from a callback. You use EventHandler<RoundResolvedEventArgs> when you want the engine to publish a completed round and let several subscribers react without giving subscribers control over round resolution.

    The game uses a fixed payoff table that is already described in RoundEngine: when both sides cooperate, both score 3; when both sides defect, both score 1; when only one side defects, the defector scores 5 and the cooperator scores 0. The steps below build outward from that rule set, starting with a single resolved round and then adding callbacks, lambdas, events, subscribers, and final result formatting.


    Feel free to refer to the solution directory if you are stuck or to compare your implementations.

    The validation tests should also output their results to help you troubleshoot, but if for some reason the output gets obscured or isn't specific enough, you can manually run a test in the Terminal with ./runTest taskX. Replace X with the task number, which are numbered 1 through 9.

  2. Challenge

    Build the round engine core

    You will work in SoloRoundGame/Engine/RoundEngine.csfor this step.


    You have been provided with constructs such as RoundContext with RoundNumber, HumanMove, and ComputerMove, as well as a RoundResult with Context, HumanPayoff, ComputerPayoff, and Summary. You will populate those values inside PlayRound and keep round creation in one place so later overloads and callbacks reuse the same logic.

    You are also given ResolvePayoffs, BuildSummary, and DescribeMove as helpers in RoundEngine.cs. You should use those helpers to keep the PlayRound methods focused on orchestration: build context, choose moves, create results, and trigger hooks. This step gives you the central source of truth for one completed round, which prevents repeated game-state recomputation across later callbacks and subscribers.


    You use a delegate when you want to pass behavior as data. ComputerMoveSelector gives that idea a domain name that fits the game. You use PostRoundCallback as an Action<RoundResult> because you only need post-round side effects and you would use ComputerStrategyOverride as Func<RoundContext, Move> because you need a callback that can inspect round data and then return a move.

    To start with

    1. check the Trust the authors... box, then click Yes, I trust the authors.
    2. You can also close the right-hand CHAT panel.

    At this point, you should have one engine path that resolves a round directly, one engine path that delegates computer choice to ComputerMoveSelector, and two callback hooks that later steps can build on.

  3. Challenge

    Write strategy lambdas

    You will work in SoloRoundGame/Strategies/ComputerStrategies.cs for this step.


    You use a lambda when you want short behavior without creating an extra named method. You use an expression lambda for a one-expression rule, a statement lambda for a block body, and a static lambda when you want to guarantee zero capture from surrounding locals or fields.

    You already get Func<RoundContext, Move> as the shape for every strategy. You can ignore RoundContext in simple strategies by using _, but you still keep the same delegate shape so ConsoleGameRunner can plug every strategy into ComputerStrategyOverride. This is why lambdas are useful in this case: you can swap computer behavior without changing engine code or adding branching logic inside RoundEngine. You should now have three interchangeable strategy callbacks that all share one delegate shape but express behavior in different lambda styles.

  4. Challenge

    Publish events and handle subscribers

    You will work across RoundEngine.cs, RoundResolvedEventArgs.cs, Scoreboard.cs, HistoryLog.cs, and AnalyticsObserver.cs for this step.


    You use an event when you want one publisher to notify multiple listeners about one completed action. The existing events are RoundResolvedEventArgs.Result for round data and RoundResolvedEventArgs.StopRequested for a small control signal that can flow back to the engine.

    You also have three subscriber classes with state that survives across rounds. Scoreboard holds cumulative totals, HistoryLog stores summary strings, and AnalyticsObserver tracks mutual defections. You do not need unsubscribe logic inside any subscriber. You only need handler methods that update local state correctly when calling code that subscribes or unsubscribes those handlers.

    By using these events, the game engine can publish one round result, and several other classes can react without taking over round resolution. By this point, you now have one publisher that emits round results, three subscribers that react to those results, and one stop-request path that flows from subscriber back to the game engine.

  5. Challenge

    Finish the formatting callback

    Lastly, you will need to setup a formatting callback to display the summary of each round through SoloRoundGame/Analysis/RoundAnalysis.cs.


    You use Func<RoundResult, string> when you want reusable formatting behavior instead of immediate formatting. The RoundResult.Summary already exists, so you only need one callback that prefixes a caller-supplied label. You now have one final callback that lets calling code add display context without changing the core round data.


    If you have reached this point, then the game should now be complete. You have built a reusable round engine that resolves game state, swaps computer behavior through delegates and lambdas, publishes updates through events, and lets separate classes handle scorekeeping, history, analytics, and formatting without collapsing those concerns into one class.

    In the TERMINAL, run the game to observe the behaviors you implemented with dotnet run --project SoloRoundGame. Play around with it, and feel free to make modifications to it if you wish.

About the author

George is a Pluralsight Author working on content for Hands-On Experiences. He is experienced in the Python, JavaScript, Java, and most recently Rust domains.

Real skill practice before real-world application

Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.

Learn by doing

Engage hands-on with the tools and technologies you’re learning. You pick the skill, we provide the credentials and environment.

Follow your guide

All labs have detailed instructions and objectives, guiding you through the learning process and ensuring you understand every step.

Turn time into mastery

On average, you retain 75% more of your learning if you take time to practice. Hands-on labs set you up for success to make those skills stick.

Get started with Pluralsight