From b7e567fd7b0c396e21233ee684ec3f56b28c7977 Mon Sep 17 00:00:00 2001 From: BALAJI BASHYAM Date: Tue, 4 Aug 2026 15:23:14 +0000 Subject: [PATCH 1/5] Add lightweight C# 12 refactoring sample --- .../CSharp12RefactoringLab.slnx | 8 + csharp-language/csharp-12-features/README.md | 202 ++++++++++++++ .../CSharp12RefactoringLab.csproj | 12 + .../Formatting/TodoFormatter.cs | 41 +++ .../CSharp12RefactoringLab/Models/TodoItem.cs | 31 +++ .../src/CSharp12RefactoringLab/Program.cs | 45 ++++ .../Services/TodoService.cs | 88 +++++++ .../CSharp12FeatureTests.cs | 249 ++++++++++++++++++ .../CSharp12RefactoringLab.Tests.csproj | 47 ++++ 9 files changed, 723 insertions(+) create mode 100644 csharp-language/csharp-12-features/CSharp12RefactoringLab.slnx create mode 100644 csharp-language/csharp-12-features/README.md create mode 100644 csharp-language/csharp-12-features/src/CSharp12RefactoringLab/CSharp12RefactoringLab.csproj create mode 100644 csharp-language/csharp-12-features/src/CSharp12RefactoringLab/Formatting/TodoFormatter.cs create mode 100644 csharp-language/csharp-12-features/src/CSharp12RefactoringLab/Models/TodoItem.cs create mode 100644 csharp-language/csharp-12-features/src/CSharp12RefactoringLab/Program.cs create mode 100644 csharp-language/csharp-12-features/src/CSharp12RefactoringLab/Services/TodoService.cs create mode 100644 csharp-language/csharp-12-features/tests/CSharp12RefactoringLab.Tests/CSharp12FeatureTests.cs create mode 100644 csharp-language/csharp-12-features/tests/CSharp12RefactoringLab.Tests/CSharp12RefactoringLab.Tests.csproj diff --git a/csharp-language/csharp-12-features/CSharp12RefactoringLab.slnx b/csharp-language/csharp-12-features/CSharp12RefactoringLab.slnx new file mode 100644 index 0000000..2a77b17 --- /dev/null +++ b/csharp-language/csharp-12-features/CSharp12RefactoringLab.slnx @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/csharp-language/csharp-12-features/README.md b/csharp-language/csharp-12-features/README.md new file mode 100644 index 0000000..8807088 --- /dev/null +++ b/csharp-language/csharp-12-features/README.md @@ -0,0 +1,202 @@ +# C# 12 Everyday Refactoring Lab + +A focused .NET 10 console companion demonstrating practical C# 12 refactoring +with primary constructors, explicit public properties, collection expressions, +spread elements, default lambda parameters, and an alias for a tuple type. + +## Full tutorial + +[C# 12 Language Features: Primary Constructors, Collections & More](https://www.dotnet-guide.com/tutorials/csharp-language/csharp-12-features/) + +## Framework and language note + +The tutorial introduces C# 12 with the .NET 8 toolchain. + +This companion targets .NET 10 because that is the DOTNET GUIDE repository's +current SDK, but both projects explicitly set: + +```xml +12.0 +``` + +The compiler accepts C# 12 syntax while rejecting newer C# 13 or C# 14 features +accidentally introduced during maintenance. + +The project doesn't use `latest` or `preview`. + +## What the sample demonstrates + +- a class primary constructor; +- explicit properties initialized from constructor parameters; +- validation through a property initializer; +- a service primary constructor; +- collection expressions targeting `List` and arrays; +- spread elements for copying and composition; +- a tuple alias using C# 12 "alias any type"; +- a default lambda parameter; +- an explicit override of the lambda default; +- deterministic output; +- seven tests. + +## Primary-constructor boundary + +Primary-constructor parameters are parameters, not class members. + +This class: + +```csharp +public sealed class TodoItem(string title) +{ + public string Title { get; } = title; +} +``` + +has a public `Title` property because the property is explicitly declared. + +It doesn't automatically gain a public property named `title`. + +The tests protect this distinction with reflection. + +## Collection-expression boundary + +Collection expressions are target-typed. + +These compile because the target type is known: + +```csharp +TodoItem[] array = [item]; +List list = [item]; +``` + +A bare declaration such as: + +```csharp +var items = [item]; +``` + +doesn't provide a target collection type and isn't used by this sample. + +Spread elements enumerate their source. + +They should not be described as allocation-free without measurement. + +## Alias boundary + +```csharp +using TodoCounts = (int Total, int Active, int Completed); +``` + +creates a source-code alias for a tuple type. + +It doesn't create a new nominal runtime type. + +Use a record or struct when a distinct domain type is required. + +## Default-lambda boundary + +The formatter declares its lambda with `var`. + +The compiler synthesizes a delegate type that preserves the optional parameter. + +The sample calls the lambda both with and without the optional prefix. + +## Prerequisite + +- .NET 10 SDK + +## Restore, build, test, and run + +```powershell +dotnet restore ` + .\CSharp12RefactoringLab.slnx + +dotnet build ` + .\CSharp12RefactoringLab.slnx ` + --configuration Release ` + --no-restore + +dotnet test ` + .\CSharp12RefactoringLab.slnx ` + --configuration Release ` + --no-build + +dotnet run ` + --project .\src\CSharp12RefactoringLab\CSharp12RefactoringLab.csproj ` + --configuration Release ` + --no-build +``` + +## Expected output + +```text +C# 12 Todo Refactoring Lab +Counts: total=4, active=2, completed=2 +TODO: Welcome to the C# 12 lab [done] +TODO: Adopt primary constructors [active] +TODO: Use collection expressions [done] +TODO: Try default lambda parameters [active] +TODO: Review explicit properties [done] +``` + +## Verify the language version + +```powershell +dotnet msbuild ` + .\src\CSharp12RefactoringLab\CSharp12RefactoringLab.csproj ` + -getProperty:LangVersion +``` + +Expected: + +```text +12.0 +``` + +## Project structure + +```text +CSharp12RefactoringLab.slnx +README.md +src/ +`-- CSharp12RefactoringLab/ + |-- CSharp12RefactoringLab.csproj + |-- Program.cs + |-- Formatting/ + | `-- TodoFormatter.cs + |-- Models/ + | `-- TodoItem.cs + `-- Services/ + `-- TodoService.cs +tests/ +`-- CSharp12RefactoringLab.Tests/ + |-- CSharp12RefactoringLab.Tests.csproj + `-- CSharp12FeatureTests.cs +``` + +## Deliberately omitted + +- inline arrays; +- stack allocation; +- unsafe code; +- benchmarks; +- allocation claims; +- Minimal APIs; +- C# 11 list patterns; +- preview features; +- external services. + +Inline arrays are advanced struct-based contiguous storage. They deserve a +separate measured sample rather than a contrived use in this Todo application. + +## Verification + +- Companion target framework: .NET 10 +- Explicit language version: C# 12.0 +- Tutorial framework: .NET 8 +- Application packages: none +- External services required: none +- Expected tests: 7 +- Last reviewed: 2026-08-04 + +This sample demonstrates language semantics. It doesn't claim universal +performance improvements. \ No newline at end of file diff --git a/csharp-language/csharp-12-features/src/CSharp12RefactoringLab/CSharp12RefactoringLab.csproj b/csharp-language/csharp-12-features/src/CSharp12RefactoringLab/CSharp12RefactoringLab.csproj new file mode 100644 index 0000000..27eb670 --- /dev/null +++ b/csharp-language/csharp-12-features/src/CSharp12RefactoringLab/CSharp12RefactoringLab.csproj @@ -0,0 +1,12 @@ + + + + Exe + net10.0 + 12.0 + enable + enable + true + + + \ No newline at end of file diff --git a/csharp-language/csharp-12-features/src/CSharp12RefactoringLab/Formatting/TodoFormatter.cs b/csharp-language/csharp-12-features/src/CSharp12RefactoringLab/Formatting/TodoFormatter.cs new file mode 100644 index 0000000..5921045 --- /dev/null +++ b/csharp-language/csharp-12-features/src/CSharp12RefactoringLab/Formatting/TodoFormatter.cs @@ -0,0 +1,41 @@ +using CSharp12RefactoringLab.Models; + +namespace CSharp12RefactoringLab.Formatting; + +public static class TodoFormatter +{ + public static string[] BuildLabels( + IEnumerable items, + string? explicitPrefix = null) + { + var format = + ( + TodoItem item, + string prefix = "TODO" + ) => + $"{prefix}: {item.Title} [{GetState(item)}]"; + + return explicitPrefix is null + ? + [ + .. items.Select( + item => + format( + item)) + ] + : + [ + .. items.Select( + item => + format( + item, + explicitPrefix)) + ]; + } + + private static string GetState( + TodoItem item) => + item.IsComplete + ? "done" + : "active"; +} \ No newline at end of file diff --git a/csharp-language/csharp-12-features/src/CSharp12RefactoringLab/Models/TodoItem.cs b/csharp-language/csharp-12-features/src/CSharp12RefactoringLab/Models/TodoItem.cs new file mode 100644 index 0000000..ee07e00 --- /dev/null +++ b/csharp-language/csharp-12-features/src/CSharp12RefactoringLab/Models/TodoItem.cs @@ -0,0 +1,31 @@ +namespace CSharp12RefactoringLab.Models; + +public sealed class TodoItem( + string title, + bool isComplete = false) +{ + public string Title { get; } = + NormalizeTitle( + title); + + public bool IsComplete { get; } = + isComplete; + + public TodoItem Complete() => + IsComplete + ? this + : new TodoItem( + Title, + isComplete: + true); + + private static string NormalizeTitle( + string value) + { + ArgumentException + .ThrowIfNullOrWhiteSpace( + value); + + return value.Trim(); + } +} \ No newline at end of file diff --git a/csharp-language/csharp-12-features/src/CSharp12RefactoringLab/Program.cs b/csharp-language/csharp-12-features/src/CSharp12RefactoringLab/Program.cs new file mode 100644 index 0000000..4c51851 --- /dev/null +++ b/csharp-language/csharp-12-features/src/CSharp12RefactoringLab/Program.cs @@ -0,0 +1,45 @@ +using CSharp12RefactoringLab.Formatting; +using CSharp12RefactoringLab.Models; +using CSharp12RefactoringLab.Services; + +TodoItem[] seedItems = +[ + new TodoItem( + "Adopt primary constructors"), + + new TodoItem( + "Use collection expressions", + isComplete: + true) +]; + +var service = + new TodoService( + seedItems); + +service.Add( + new TodoItem( + "Try default lambda parameters")); + +service.Add( + new TodoItem( + "Review explicit properties", + isComplete: + true)); + +var counts = + service.GetCounts(); + +Console.WriteLine( + "C# 12 Todo Refactoring Lab"); + +Console.WriteLine( + $"Counts: total={counts.Total}, active={counts.Active}, completed={counts.Completed}"); + +foreach (string label in + TodoFormatter.BuildLabels( + service.GetAllWithWelcome())) +{ + Console.WriteLine( + label); +} \ No newline at end of file diff --git a/csharp-language/csharp-12-features/src/CSharp12RefactoringLab/Services/TodoService.cs b/csharp-language/csharp-12-features/src/CSharp12RefactoringLab/Services/TodoService.cs new file mode 100644 index 0000000..8fcac1d --- /dev/null +++ b/csharp-language/csharp-12-features/src/CSharp12RefactoringLab/Services/TodoService.cs @@ -0,0 +1,88 @@ +using CSharp12RefactoringLab.Models; + +using TodoCounts = + ( + int Total, + int Active, + int Completed + ); + +namespace CSharp12RefactoringLab.Services; + +public sealed class TodoService( + IEnumerable seedItems) +{ + private readonly List + _items = + [ + .. seedItems + ]; + + public void Add( + TodoItem item) + { + ArgumentNullException + .ThrowIfNull( + item); + + _items.Add( + item); + } + + public TodoItem[] Snapshot() => + [ + .. _items + ]; + + public TodoItem[] GetActive( + int limit = 10) + { + ArgumentOutOfRangeException + .ThrowIfNegativeOrZero( + limit); + + return + [ + .. _items + .Where( + item => + !item.IsComplete) + .Take( + limit) + ]; + } + + public TodoItem[] GetAllWithWelcome() + { + TodoItem welcome = + new( + "Welcome to the C# 12 lab", + isComplete: + true); + + return + [ + welcome, + .. _items + ]; + } + + public TodoCounts GetCounts() + { + int completed = + _items.Count( + item => + item.IsComplete); + + return ( + Total: + _items.Count, + + Active: + _items.Count + - completed, + + Completed: + completed); + } +} \ No newline at end of file diff --git a/csharp-language/csharp-12-features/tests/CSharp12RefactoringLab.Tests/CSharp12FeatureTests.cs b/csharp-language/csharp-12-features/tests/CSharp12RefactoringLab.Tests/CSharp12FeatureTests.cs new file mode 100644 index 0000000..fc6cbfa --- /dev/null +++ b/csharp-language/csharp-12-features/tests/CSharp12RefactoringLab.Tests/CSharp12FeatureTests.cs @@ -0,0 +1,249 @@ +using CSharp12RefactoringLab.Formatting; +using CSharp12RefactoringLab.Models; +using CSharp12RefactoringLab.Services; + +namespace CSharp12RefactoringLab.Tests; + +public sealed class CSharp12FeatureTests +{ + [Fact] + public void + Primary_constructor_initializes_explicit_properties() + { + var item = + new TodoItem( + " Learn C# 12 ", + isComplete: + true); + + Assert.Equal( + "Learn C# 12", + item.Title); + + Assert.True( + item.IsComplete); + + Assert.Throws< + ArgumentException>( + () => + new TodoItem( + " ")); + } + + [Fact] + public void + Primary_constructor_parameters_are_not_public_properties() + { + Type itemType = + typeof( + TodoItem); + + Assert.Null( + itemType.GetProperty( + "title")); + + Assert.Null( + itemType.GetProperty( + "isComplete")); + + Assert.NotNull( + itemType.GetProperty( + nameof( + TodoItem.Title))); + + Assert.NotNull( + itemType.GetProperty( + nameof( + TodoItem.IsComplete))); + } + + [Fact] + public void + Collection_expressions_copy_seed_and_snapshot_data() + { + TodoItem[] seed = + [ + new TodoItem( + "First") + ]; + + var service = + new TodoService( + seed); + + seed[0] = + new TodoItem( + "Changed outside"); + + TodoItem[] firstSnapshot = + service.Snapshot(); + + firstSnapshot[0] = + new TodoItem( + "Changed snapshot"); + + TodoItem[] secondSnapshot = + service.Snapshot(); + + Assert.Equal( + "First", + secondSnapshot[0] + .Title); + } + + [Fact] + public void + Spread_expression_prepends_welcome_without_mutating_service() + { + var service = + new TodoService( + [ + new TodoItem( + "Stored item") + ]); + + TodoItem[] composed = + service.GetAllWithWelcome(); + + TodoItem[] stored = + service.Snapshot(); + + Assert.Equal( + 2, + composed.Length); + + Assert.Equal( + "Welcome to the C# 12 lab", + composed[0].Title); + + Assert.Single( + stored); + + Assert.Equal( + "Stored item", + stored[0].Title); + } + + [Fact] + public void + Tuple_alias_returns_named_counts() + { + var service = + new TodoService( + [ + new TodoItem( + "Active"), + + new TodoItem( + "Completed", + isComplete: + true), + + new TodoItem( + "Also active") + ]); + + var counts = + service.GetCounts(); + + Assert.Equal( + 3, + counts.Total); + + Assert.Equal( + 2, + counts.Active); + + Assert.Equal( + 1, + counts.Completed); + } + + [Fact] + public void + Default_lambda_parameter_and_explicit_override_format_labels() + { + TodoItem[] items = + [ + new TodoItem( + "Write tests"), + + new TodoItem( + "Ship sample", + isComplete: + true) + ]; + + string[] defaults = + TodoFormatter.BuildLabels( + items); + + string[] custom = + TodoFormatter.BuildLabels( + items, + explicitPrefix: + "TASK"); + + Assert.Equal( + "TODO: Write tests [active]", + defaults[0]); + + Assert.Equal( + "TODO: Ship sample [done]", + defaults[1]); + + Assert.Equal( + "TASK: Write tests [active]", + custom[0]); + + Assert.Equal( + "TASK: Ship sample [done]", + custom[1]); + } + + [Fact] + public void + Active_filter_applies_limit_and_preserves_order() + { + var service = + new TodoService( + [ + new TodoItem( + "First active"), + + new TodoItem( + "Completed", + isComplete: + true), + + new TodoItem( + "Second active"), + + new TodoItem( + "Third active") + ]); + + TodoItem[] active = + service.GetActive( + limit: + 2); + + Assert.Collection( + active, + first => + Assert.Equal( + "First active", + first.Title), + second => + Assert.Equal( + "Second active", + second.Title)); + + Assert.Throws< + ArgumentOutOfRangeException>( + () => + service.GetActive( + limit: + 0)); + } +} \ No newline at end of file diff --git a/csharp-language/csharp-12-features/tests/CSharp12RefactoringLab.Tests/CSharp12RefactoringLab.Tests.csproj b/csharp-language/csharp-12-features/tests/CSharp12RefactoringLab.Tests/CSharp12RefactoringLab.Tests.csproj new file mode 100644 index 0000000..225270f --- /dev/null +++ b/csharp-language/csharp-12-features/tests/CSharp12RefactoringLab.Tests/CSharp12RefactoringLab.Tests.csproj @@ -0,0 +1,47 @@ + + + + net10.0 + 12.0 + enable + enable + true + false + true + Exe + + + + + + + + + all + + runtime; + build; + native; + contentfiles; + analyzers; + buildtransitive + + + + + + + + + + + + + \ No newline at end of file From 166f0b8fb343be620216535d0374f0679e39ab9e Mon Sep 17 00:00:00 2001 From: BALAJI BASHYAM Date: Tue, 4 Aug 2026 15:23:14 +0000 Subject: [PATCH 2/5] Add C# 12 sample to repository README --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index ba3cf29..5feb6c9 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Each sample folder contains a focused implementation of one tutorial topic. The | [`cloud-native/dockerize-aspnet-core-clean-images`](cloud-native/dockerize-aspnet-core-clean-images/) | Focused .NET 10 container sample demonstrating a multi-stage Dockerfile, locked restore, runtime-only image, non-root execution, port 8080, runtime configuration, health checks, hardened Compose settings, and CI smoke testing | [Dockerizing ASP.NET Core: Multi-Stage Builds, Clean Images & a Production-Ready Ship Workflow](https://www.dotnet-guide.com/tutorials/cloud-native/dockerize-aspnet-core-clean-images/) | | [`cloud-native/health-resilience-zero-downtime`](cloud-native/health-resilience-zero-downtime/) | Focused .NET 10 Orders API demonstrating tagged liveness/readiness checks, structured health output, shutdown readiness draining, typed HttpClient retries, circuit breaking, attempt timeouts, and deterministic integration testing | [.NET 8 Cloud-Native: Health Probes, Polly Resilience & Zero-Downtime Kubernetes Deployments](https://www.dotnet-guide.com/tutorials/cloud-native/health-resilience-zero-downtime/) | | [`cloud-native/polly-resilience`](cloud-native/polly-resilience/) | Focused .NET 10 Polly v8 catalog sample demonstrating explicit stale-cache fallback, timeout-driven degradation, outbound concurrency isolation, strategy event counters, and deterministic integration testing | [Polly Resilience (Polly v8): Timeouts, Retries, Circuits, Bulkheads, Hedging](https://www.dotnet-guide.com/tutorials/cloud-native/polly-resilience/) | +| [`csharp-language/csharp-12-features`](csharp-language/csharp-12-features/) | Focused .NET 10 console lab locked to C# 12.0, demonstrating primary constructors with explicit properties, collection expressions and spreads, default lambda parameters, tuple-type aliases, deterministic output, and unit tests | [C# 12 Language Features: Primary Constructors, Collections & More](https://www.dotnet-guide.com/tutorials/csharp-language/csharp-12-features/) | ## Companion articles - [Common Microsoft.Extensions.AI mistakes](https://www.dotnet-guide.com/articles/dotnet-ai/microsoft-extensions-ai-common-mistakes/) @@ -267,6 +268,24 @@ tutorials/ | `-- PollyCatalogResilience.Tests/ | |-- PollyCatalogResilience.Tests.csproj | `-- CatalogResilienceTests.cs +|-- csharp-language/ +| `-- csharp-12-features/ +| |-- CSharp12RefactoringLab.slnx +| |-- README.md +| |-- src/ +| | `-- CSharp12RefactoringLab/ +| | |-- CSharp12RefactoringLab.csproj +| | |-- Program.cs +| | |-- Formatting/ +| | | `-- TodoFormatter.cs +| | |-- Models/ +| | | `-- TodoItem.cs +| | `-- Services/ +| | `-- TodoService.cs +| `-- tests/ +| `-- CSharp12RefactoringLab.Tests/ +| |-- CSharp12RefactoringLab.Tests.csproj +| `-- CSharp12FeatureTests.cs |-- aspnet-core/ | |-- api-security-in-practice/ | | |-- ApiSecurityMinimal.slnx From dda80b4e30fa3f6f04128133329facb2d554665f Mon Sep 17 00:00:00 2001 From: BALAJI BASHYAM Date: Tue, 4 Aug 2026 15:23:14 +0000 Subject: [PATCH 3/5] Test C# 12 sample in GitHub Actions --- .github/workflows/build-samples.yml | 86 +++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/.github/workflows/build-samples.yml b/.github/workflows/build-samples.yml index 992d132..203a157 100644 --- a/.github/workflows/build-samples.yml +++ b/.github/workflows/build-samples.yml @@ -20,6 +20,7 @@ on: - "cloud-native/dockerize-aspnet-core-clean-images/**" - "cloud-native/health-resilience-zero-downtime/**" - "cloud-native/polly-resilience/**" + - "csharp-language/csharp-12-features/**" - ".github/workflows/build-samples.yml" pull_request: @@ -40,6 +41,7 @@ on: - "cloud-native/dockerize-aspnet-core-clean-images/**" - "cloud-native/health-resilience-zero-downtime/**" - "cloud-native/polly-resilience/**" + - "csharp-language/csharp-12-features/**" - ".github/workflows/build-samples.yml" workflow_dispatch: @@ -693,3 +695,87 @@ jobs: cloud-native/polly-resilience/PollyCatalogResilience.slnx --configuration Release --no-build + + test-csharp-12-features: + name: Test C# 12 refactoring sample + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + - name: Check out repository + uses: actions/checkout@v5 + + - name: Install .NET 10 SDK + uses: actions/setup-dotnet@v5 + with: + dotnet-version: "10.0.x" + + - name: Restore + run: > + dotnet restore + csharp-language/csharp-12-features/CSharp12RefactoringLab.slnx + + - name: Build + run: > + dotnet build + csharp-language/csharp-12-features/CSharp12RefactoringLab.slnx + --configuration Release + --no-restore + + - name: Test + run: > + dotnet test + csharp-language/csharp-12-features/CSharp12RefactoringLab.slnx + --configuration Release + --no-build + + - name: Verify explicit C# language version + shell: bash + run: | + app_version="$( + dotnet msbuild \ + csharp-language/csharp-12-features/src/CSharp12RefactoringLab/CSharp12RefactoringLab.csproj \ + -getProperty:LangVersion + )" + + test_version="$( + dotnet msbuild \ + csharp-language/csharp-12-features/tests/CSharp12RefactoringLab.Tests/CSharp12RefactoringLab.Tests.csproj \ + -getProperty:LangVersion + )" + + echo "Application LangVersion: ${app_version}" + echo "Test LangVersion: ${test_version}" + + test "${app_version}" = "12.0" + test "${test_version}" = "12.0" + + - name: Run deterministic console sample + shell: bash + run: | + output="$( + dotnet run \ + --project csharp-language/csharp-12-features/src/CSharp12RefactoringLab/CSharp12RefactoringLab.csproj \ + --configuration Release \ + --no-build + )" + + printf '%s\n' "${output}" + + grep -Fqx \ + "C# 12 Todo Refactoring Lab" \ + <<< "${output}" + + grep -Fqx \ + "Counts: total=4, active=2, completed=2" \ + <<< "${output}" + + grep -Fqx \ + "TODO: Welcome to the C# 12 lab [done]" \ + <<< "${output}" + + grep -Fqx \ + "TODO: Try default lambda parameters [active]" \ + <<< "${output}" From 9d518a1f2da063dd0f4d5e4a82037ae4df9cd053 Mon Sep 17 00:00:00 2001 From: BALAJI BASHYAM Date: Tue, 4 Aug 2026 15:30:08 +0000 Subject: [PATCH 4/5] Verify exact C# 12 sample output --- .github/workflows/build-samples.yml | 44 ++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build-samples.yml b/.github/workflows/build-samples.yml index 203a157..90e54a8 100644 --- a/.github/workflows/build-samples.yml +++ b/.github/workflows/build-samples.yml @@ -764,18 +764,34 @@ jobs: printf '%s\n' "${output}" - grep -Fqx \ - "C# 12 Todo Refactoring Lab" \ - <<< "${output}" - - grep -Fqx \ - "Counts: total=4, active=2, completed=2" \ - <<< "${output}" - - grep -Fqx \ - "TODO: Welcome to the C# 12 lab [done]" \ - <<< "${output}" + expected="$( + cat <<'ENDOFOUTPUT' +C# 12 Todo Refactoring Lab +Counts: total=4, active=2, completed=2 +TODO: Welcome to the C# 12 lab [done] +TODO: Adopt primary constructors [active] +TODO: Use collection expressions [done] +TODO: Try default lambda parameters [active] +TODO: Review explicit properties [done] +ENDOFOUTPUT +)" + + if [[ "${output}" != "${expected}" ]]; then + echo "Console output did not match the expected output." + + diff \ + --unified \ + <(printf '%s\n' "${expected}") \ + <(printf '%s\n' "${output}") \ + || true + + exit 1 + fi + + line_count="$( + printf '%s\n' "${output}" | + wc --lines | + tr --delete ' ' + )" - grep -Fqx \ - "TODO: Try default lambda parameters [active]" \ - <<< "${output}" + test "${line_count}" = "7" From a5b05143c7be785f7f7a5af596628e960b5fd57c Mon Sep 17 00:00:00 2001 From: BALAJI BASHYAM Date: Tue, 4 Aug 2026 15:36:04 +0000 Subject: [PATCH 5/5] Verify exact C# 12 sample output --- .github/workflows/build-samples.yml | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build-samples.yml b/.github/workflows/build-samples.yml index 90e54a8..cca3564 100644 --- a/.github/workflows/build-samples.yml +++ b/.github/workflows/build-samples.yml @@ -765,16 +765,15 @@ jobs: printf '%s\n' "${output}" expected="$( - cat <<'ENDOFOUTPUT' -C# 12 Todo Refactoring Lab -Counts: total=4, active=2, completed=2 -TODO: Welcome to the C# 12 lab [done] -TODO: Adopt primary constructors [active] -TODO: Use collection expressions [done] -TODO: Try default lambda parameters [active] -TODO: Review explicit properties [done] -ENDOFOUTPUT -)" + printf '%s\n' \ + "C# 12 Todo Refactoring Lab" \ + "Counts: total=4, active=2, completed=2" \ + "TODO: Welcome to the C# 12 lab [done]" \ + "TODO: Adopt primary constructors [active]" \ + "TODO: Use collection expressions [done]" \ + "TODO: Try default lambda parameters [active]" \ + "TODO: Review explicit properties [done]" + )" if [[ "${output}" != "${expected}" ]]; then echo "Console output did not match the expected output."