Refactor existing C# library code safely
Replacing a loop with LINQ is not automatically an improvement. The refactor must preserve comparison, ordering, null behavior, populated references and side effects.
Lab briefing

Original concept illustration (SVG)
| At a glance | Your route |
|---|---|
| Level and time | 300; 60 minutes (facilitation estimate) |
| Starting action | Characterize one method before replacing its loop. |
| Learner materials | Download 05-csharp.zip |
| Workspace | Open the extracted kit root; run the baseline from . relative to that root |
| Expected initial check | The supplied tests pass. New feature requirements still need their own tests. |
| Setup help | Download, extract, local Git and optional GitHub |
[!NOTE] Shorter code is not evidence of semantic equivalence.
Concepts · First task · Evidence checklist · Reset
Learning objectives
- Derive current behavior from the repository methods.
- Separate refactoring from bug fixes and new search semantics.
- Prove equivalence at selected boundary cases.
Before you start
Prepare 05-csharp using the setup guide.
Use the bundled
C# refactoring fixture.
Do not change dependency versions during a refactor.
Concepts and use cases
| Behavior | Preservation question |
|---|---|
SearchPatrons |
Does Contains remain case-sensitive as in this fixture? |
| Sort | Is the current comparison/culture behavior preserved? |
| Populate | Are related loans still attached? |
GetPatron |
Is absent-ID behavior still null? |
| Update | Are the same fields persisted and reloaded? |
Exercise scenario
The repository uses manual iteration to find and filter patrons/loans. Simplify one method while keeping the existing interface and result semantics.
Task 1 - Inspect and characterize
-
Read
JsonPatronRepository,JsonLoanRepository,JsonData, and their interfaces. -
Run the small fixture suite:
dotnet test tests/UnitTests/UnitTests.csproj -m:1 -p:UseSharedCompilation=false -
Add characterization cases before editing: found/missing, empty list, mixed case, duplicate display names with distinct IDs, and populated references.
-
Use isolated data; do not mutate distributed JSON to obtain expected results.
Task 2 - Ask for alternatives, then Plan
Compare the current loop in SearchPatrons with a LINQ alternative. Preserve
comparison and sort semantics, missing-input behavior, and populated loans.
Identify any material difference; do not edit or claim a speedup.
Choose one method. Require a before/after behavior table and a focused test command. If the current behavior is undesirable, document a separate feature instead of changing it under the name “refactoring.”
Task 3 - Implement one equivalent slice
- Ask Agent to simplify the chosen method only.
- Inspect deferred execution versus materialized lists. Do not return a lazy iterator where callers expect a snapshot.
- Run focused characterization and the original suite.
- Temporarily alter case comparison or omit population. Confirm a regression fails, then restore the refactor.
- Review public signatures and save/reload calls for unintended changes.
Check yourself
Your LINQ rewrite of SearchPatrons is shorter and the supplied suite is still green. What actually proves the behavior was preserved?
Choose an answer to see why it holds or fails.
- The supplied suite alone, since it already passed before you touched the loop The lab warns that tests can pass with or without population, so a green baseline suite does not prove equivalence.
- Proof that deliberately breaking case comparison or loan population makes a test fail Task 3 has you alter case comparison or omit population, confirm that a regression fails, and then restore the refactor.
- A note that the LINQ chain is shorter and faster than the loop it replaced The prompt in Task 2 says not to claim a speedup, and the lab notes that shorter code is not evidence of semantic equivalence.
Verify your work
- Existing tests and new characterization cases pass.
- Case, ordering and null semantics remain explicit.
- Related entities are still populated.
- Update/persistence side effects are unchanged.
- A deliberate semantic mutation is detected.
Troubleshooting
If tests pass with or without population, improve the assertions. If a sort changes on another machine, inspect culture rather than calling it random. If a missing ID starts throwing, that is a behavior change needing its own acceptance criteria.
Independent practice
Apply the same approach to one loan lookup. Explain when the simpler loop is preferable to a chain of LINQ operators.
Reset
Save the behavior table, restore only the refactored and test files in the copied project, and stop the console if it was started. Never hard-reset the curriculum.