BiblioTech has grown, module by module, into a complete domain (LibraryItem, Book, Magazine, Member, Loan, Library), persistence in four different forms (text, JSON, SQLite, Entity Framework Core), and five different user interfaces. With this module, the course takes a step back: instead of adding new functionality, it's time to consolidate and polish all of that code. This first lesson focuses on the foundation of any polishing process: coding standards. A coding standard isn't a minor aesthetic matter — it's what lets anyone (including your own future self, six months from now) read code they didn't write and understand it effortlessly. You'll review and expand conventions you already know from Module 1, and see some new ones that apply to the project as a whole, not just to a single line.

Contents

  1. Why coding standards matter in a growing project
  2. .NET naming conventions: review and expansion
  3. EditorConfig and code analyzers
  4. Single responsibility principle at the method and class level
  5. Useful comments versus noise comments
  6. XML documentation (///) on Library's public API
  7. Consistent nullability across the whole project
  8. Combined example: before and after a Library snippet

  1. Why coding standards matter in a growing project

In the course's early lessons, with programs just a few lines long, any writing style "worked": the code was so short it was equally understandable whether a variable was called x or pageCount. BiblioTech is no longer like that: it has a domain of several classes, several forms of persistence, and five user interfaces, spread across a real project that, at a company, several people would maintain at once. In that context, a consistent coding standard stops being a personal preference and becomes a necessity:

Without a consistent standard With a consistent standard
Each class "reads" differently; you have to relearn the style in every file The code reads uniformly across the whole project
Reviewing someone else's code takes longer (Lesson 5 of this module) Reviews focus on logic, not on arguing about style
Nullability mistakes or confusing names slip through more easily Many bugs are avoided just by following the convention
Onboarding someone new to the project is slow New code "fits" immediately with the rest

It's not about imposing a style on a whim, but about eliminating repetitive decisions — capital or lowercase initial? where does the comment go? — so attention can go to what really matters: making sure the code does the right thing.

  1. .NET naming conventions: review and expansion

The Basic Syntax and Structure lesson (Module 1) introduced .NET's two central conventions:

Convention Rule Where it's used
PascalCase Every word starts with a capital letter Classes, methods, properties (Library, RegisterLoan, Title)
camelCase The first word starts lowercase Local variables and parameters (title, memberId)

Three additional conventions now join those, already applied in passing throughout BiblioTech but not formally named until now:

  • I prefix on interfaces: ILendable, ISearchable (Module 4). The I prefix signals, just by reading the name, that this is a contract and not a concrete class — a convention you'll see taken further in Lesson 3 of this module, with ILibraryRepository.
  • _ prefix on private fields: _membersById (Module 4, in Library). Distinguishes a private field from a public property or a local variable at a glance, with no need to check its declaration.
  • Async suffix on asynchronous methods: LendBookAsync (Module 4), GetMetadataByIsbnAsync (Module 5). Tells whoever calls the method that it must be used with await, without having to check the full signature.
class Library
{
    private Dictionary<int, Member> _membersById = new Dictionary<int, Member>(); // _prefix: private field

    public List<LibraryItem> Catalog { get; } = new List<LibraryItem>(); // PascalCase: public property

    public async Task LendBookAsync(Book book, Member member) // Async suffix: asynchronous method
    {
        // ...
    }
}

These three conventions aren't quirks of the course itself: they're the ones followed by the whole .NET standard library and the vast majority of published C# code, which means following them makes BiblioTech "fit" immediately with any other .NET project someone reads afterward.

  1. EditorConfig and code analyzers

Remembering a convention by heart and applying it by hand on every line is error-prone. Two tools automate much of that work:

  • EditorConfig (an .editorconfig file at the project root): a text file that declares formatting rules — indentation, spaces versus tabs, trailing newline, and also C# naming conventions — that editors like Visual Studio or VS Code apply automatically while writing and formatting code.
  • Code analyzers (Roslyn analyzers): tools that scan code looking for problems — from style to potential bugs — and show warnings directly in the editor, even before compiling. .NET ships with a set of analyzers enabled by default in any modern project (dotnet new), and they can be extended by installing additional NuGet packages (for example, Microsoft.CodeAnalysis.NetAnalyzers with stricter rules).
# .editorconfig (illustrative excerpt)
root = true

[*.cs]
indent_style = space
indent_size = 4
dotnet_naming_rule.interfaces_should_be_prefixed_with_i.severity = warning

This lesson doesn't go deep into the full .editorconfig syntax or analyzer configuration — each team usually adopts an already-prepared set — but it's important to know they exist: they automate exactly the conventions seen in the previous section, so a naming slip gets caught as a warning in the editor, not in a code review days later.

  1. Single responsibility principle at the method and class level

The single responsibility principle (one of the central ideas behind design patterns, which the next lesson revisits in more detail) says, simply: every method and every class should have a single reason to change. Applied day to day, it means a method should do one thing, and do it well, instead of mixing several unrelated responsibilities.

// Before: a method with two responsibilities mixed together
public void ProcessLoan(Book book, Member member)
{
    if (!book.Available)
    {
        Console.WriteLine("Not available.");
        return;
    }

    book.Lend();
    Console.WriteLine($"'{book.Title}' lent to {member.Name}."); // presentation responsibility
    // ... saving, validation, or notification logic could all creep in here too...
}
// After: each method has a single responsibility
public bool TryLend(Book book, Member member)
{
    if (!book.Available)
    {
        return false;
    }

    book.Lend();
    return true;
}

// The decision of what to show on the console (or another interface) lives outside, where it belongs
if (library.TryLend(book1, member1))
{
    Console.WriteLine($"'{book1.Title}' lent to {member1.Name}.");
}
else
{
    Console.WriteLine("Not available.");
}

TryLend now only decides whether the loan is possible and updates the book's state; what to do with that result (show it on the console, in a WPF window, or return it as JSON from an ASP.NET Core endpoint) is the caller's responsibility, not the method's own. This separation is exactly the one you already saw, without naming it that way, across Module 7's five interfaces: the same Library serves Windows Forms, WPF, ASP.NET Core, Blazor, and MAUI precisely because its logic assumes nothing about how its results are presented.

  1. Useful comments versus noise comments

Not every comment adds value. A comment that repeats what the code already says clearly is noise: it takes up space, and over time can drift out of date with the actual code, which is worse than having no comment at all.

// Noise: the comment says nothing the code doesn't already say
// Increment i by 1
i++;

// Add the price to the total
total += price;
// Useful: explains the "why", not the "what" (the code already says that)
// Availability is re-checked after the simulated delay, because it could have changed
// while waiting for the penalty service's response (Module 4).
if (!book.Available)
{
    throw new InvalidOperationException($"'{book.Title}' is not available for loan.");
}
Comment type When to write it
Explains the "what" of an obvious line Never: if it's needed, the variable or method name is poorly chosen
Explains the "why" behind a non-obvious decision Yes: a business rule, an external constraint, a historical reason
Warns of a non-obvious side effect Yes: for example, that a method modifies an object received as a parameter
Has gone out of date relative to the current code Never: worse than no comment, because it misleads

Rule of thumb: if you feel the need to comment on what a line of code does, first try renaming variables or extracting a method with a more descriptive name; reserve comments for what the code, by itself, can't express.

  1. XML documentation (///) on Library's public API

Module 1 briefly introduced documentation comments (///), based on XML tags, without yet using them on BiblioTech's model. Now that Library has a consolidated public API, it's time to document it:

class Library
{
    /// <summary>
    /// Attempts to register the loan of a book to a member.
    /// </summary>
    /// <param name="book">The book to be lent.</param>
    /// <param name="member">The member requesting the loan.</param>
    /// <returns>
    /// <c>true</c> if the loan was registered successfully; <c>false</c> if the book
    /// was not available.
    /// </returns>
    public bool TryLend(Book book, Member member)
    {
        if (!book.Available)
        {
            return false;
        }

        book.Lend();
        return true;
    }
}

<summary> briefly describes what the member does; <param> documents each parameter; <returns> explains the meaning of the returned value. The benefit isn't just for whoever reads the source code: any modern editor (Visual Studio, VS Code with the C# extension) shows this text automatically as contextual help while writing a call to TryLend, just as happens with the .NET standard library's own methods (Console.WriteLine, for example, has its own XML documentation). Documenting every line of even a small class this way would be excessive; standard practice is to reserve /// for the public API of the project's central classes — exactly the case of Library — and to skip it on internal details that already explain themselves through their name.

  1. Consistent nullability across the whole project

The Pattern Matching and Modern Features lesson (Module 4) presented #nullable enable and warned of a common mistake: enabling it halfway through a project and not attending to the warnings it generates. Now that BiblioTech is a complete project, that recommendation becomes a coding standard rule: #nullable enable must be applied consistently across every file in the project, not just the ones touched most recently.

#nullable enable

class Library
{
    public LibraryItem? FindByTitle(string title)
    {
        return Catalog.FirstOrDefault(item => item.Title == title);
        // FirstOrDefault can return null; the "?" in the return type makes that explicit
    }

    public List<LibraryItem> Catalog { get; } = new List<LibraryItem>();
}
Without consistent nullability With consistent nullability
Some methods warn that they can return null, others don't, with no clear criterion Every reference type that can be null declares it with ?, across the whole project
The compiler only warns in files where #nullable enable is turned on The compiler warns uniformly in any file
Risk of NullReferenceException in "forgotten" files The risk is concentrated exactly where it can actually happen, marked with ?

In a new project, the simplest way to achieve this consistency is to enable <Nullable>enable</Nullable> once in the project file (.csproj), which applies #nullable enable to every .cs file automatically, with no need to repeat the directive in each one.

  1. Combined example: before and after a Library snippet

Bringing all of the above together, here's what a Library snippet looks like without any of the standards covered in this lesson, and its revised version:

// Before: unclear names, mixed responsibilities, no documentation, no nullability
class Library
{
    public List<LibraryItem> lista = new List<LibraryItem>();

    public LibraryItem Get(string t)
    {
        foreach (var x in lista)
        {
            if (x.Title == t)
            {
                return x;
            }
        }
        return null;
    }

    public void Proc(string t, Member s)
    {
        var m = Get(t);
        if (m != null && m.Available)
        {
            m.Lend();
            Console.WriteLine($"'{m.Title}' lent to {s.Name}.");
        }
        else
        {
            Console.WriteLine("Could not lend it.");
        }
    }
}
// After: descriptive names, separated responsibilities, documented, explicit nullability
#nullable enable

class Library
{
    public List<LibraryItem> Catalog { get; } = new List<LibraryItem>();

    /// <summary>
    /// Finds an item in the catalog by its exact title.
    /// </summary>
    /// <param name="title">The title to search for.</param>
    /// <returns>The matching item, or <c>null</c> if none matches.</returns>
    public LibraryItem? FindByTitle(string title)
    {
        return Catalog.FirstOrDefault(item => item.Title == title);
    }

    /// <summary>
    /// Attempts to lend the item with the given title.
    /// </summary>
    /// <param name="title">The title of the item to lend.</param>
    /// <param name="member">The member requesting the loan.</param>
    /// <returns><c>true</c> if the loan was registered; <c>false</c> otherwise.</returns>
    public bool TryLendByTitle(string title, Member member)
    {
        LibraryItem? item = FindByTitle(title);

        if (item is null || !item.Available)
        {
            return false;
        }

        item.Lend();
        return true;
    }
}

FindByTitle and TryLendByTitle are now two methods each with a single responsibility, with names that say exactly what they do, documented with ///, and with FindByTitle's nullability explicit in its signature (LibraryItem?). Note that the decision of what to show on the console no longer lives inside Library: it stays, as in section 4, in the hands of whoever calls the method, whether that's the console, a form, or an HTTP endpoint.

Common Mistakes and Tips

  • Mixing naming conventions within the same project: using _field in one class and plain field in another, with no clear criterion, is more confusing than having no convention at all. Apply the same rule across the whole project.
  • Commenting the "what" instead of renaming: if you need a comment to explain what a simple line does, it's almost always preferable to improve the variable's name or extract a method with a descriptive name.
  • Documenting every single line of the project with ///: it's a disproportionate effort that tends to go out of date over time. Reserve /// for the public API of the central classes.
  • Enabling #nullable enable only in new files: leaves the project with an inconsistent criterion. Enable it at the project level (.csproj) so it's applied uniformly.
  • Tip: if you're unsure how to name something, ask yourself what name the .NET standard library itself would use for an equivalent concept (List, Dictionary, HttpClient...); that intuition almost always matches the correct convention.

Exercises

  1. Given the following Library method, identify three coding standard problems (naming, single responsibility, nullability) and rewrite it fixing them:

    public Member Find(int i)
    {
        foreach (var s in Members)
        {
            if (s.Id == i) return s;
        }
        return null;
    }
    
  2. Add XML documentation (/// with <summary>, <param>, and <returns>) to the corrected method from the previous exercise.

Solutions

Problems: (a) the parameter i and the name Find aren't very descriptive; (b) the method can return null but its signature doesn't reflect that (Member instead of Member?); (c) the name doesn't distinguish "find by id" from other possible future searches (by name, for example).

public Member? FindMemberById(int memberId)
{
    return Members.FirstOrDefault(member => member.Id == memberId);
}
/// <summary>
/// Finds a member by their identifier.
/// </summary>
/// <param name="memberId">The identifier of the member to find.</param>
/// <returns>The matching member, or <c>null</c> if no member exists with that identifier.</returns>
public Member? FindMemberById(int memberId)
{
    return Members.FirstOrDefault(member => member.Id == memberId);
}

Conclusion

In this lesson you've reviewed and expanded .NET's naming conventions (PascalCase/camelCase, I/_ prefixes, Async suffix), learned about EditorConfig and code analyzers as tools that automate those conventions, applied the single responsibility principle to separate logic from presentation, told useful comments apart from noise, documented Library's public API with ///, and established consistent nullability as a standard across the whole project. With this snippet of Library now cleaner and more readable, the next lesson takes a step beyond style: design patterns, proven solutions to recurring design problems, which you'll see applied directly to BiblioTech's own domain.

© Copyright 2026. All rights reserved