Modernize Your Code, Modernize Your Thinking

 


Why “One Class per File” Is Outdated—and What Modern C# Developers Should Do Instead

For decades, C# developers have lived by a simple rule:

“One class or interface per file.”

This rule was so common that many of us (myself included) never questioned it. It became part of our coding DNA—just like putting braces on their own line (hello, 2005) or creating separate IService and Service folders.

But today, we’re writing code in C# 12, designing systems with Vertical Slice Architecture, navigating code with AI-powered tools, and building applications that emphasize features, not ceremony.

Yet this ancient rule still lingers.

Let’s be honest:

It made sense years ago.

It makes far less sense today.

If you want to modernize your codebase, you must begin by modernizing your mindset.

Let’s dive in.


🏛️ The Legacy of “One Class per File”

This rule grew out of a different era—an era when:

IDEs were primitive

Early Visual Studio couldn’t navigate symbols well. Finding things meant scrolling. Lots of scrolling.

Java influenced the C# community

Java requires one public class per file. Many C# devs carried that thinking forward.

We architected with large, rigid OOP systems

Everything was broken into dozens of micro-interfaces and boilerplate classes.

Partial classes created forced separation

WinForms, WebForms, and auto-generated EF entities all split files automatically.

Back then, this rule made sense.

But today?

It creates clutter

It works against feature-based architectures

It leads to “file explosion”

It reduces cohesion instead of improving it

Modern C# encouraging something better.


🚀 The Modern Philosophy

Use one file per concept, not one file per class.

A “concept” can include multiple related types:

  • A command + validator
  • A request + response + handler
  • A DTO + enum + mapper
  • A small set of helpers only used by one feature

This is how modern C# is intended to be used.


Modern Example: The Vertical Slice Way

Old thinking:
Break a feature into four separate files:

    UpdateExpenseGroupBudgetHandler.cs

    UpdateExpenseGroupBudgetRequest.cs

    UpdateExpenseGroupBudgetResponse.cs

    UpdateExpenseGroupBudgetValidator.cs

Sure, it's organized…


but it’s not cohesive.

Modern thinking:
Use one file per feature.

/ExpenseGroup/UpdateExpenseGroupBudget.cs

And inside:

namespace ExpenseGroup; 

public static class UpdateExpenseGroupBudget

{

    // Request DTO

    public record Request(int GroupId, decimal Revenue, decimal Margin);

 

    // Response DTO

    public record Response(bool Success);

 

    // Validator

    public class Validator: AbstractValidator<Request>

    {

        public Validator()

        {

            RuleFor(x => x.Revenue).GreaterThanOrEqualTo(0);

            RuleFor(x => x.Margin).GreaterThanOrEqualTo(0);

        }

    }

 

    // Handler

    public class Handler: IRequestHandler<Request, Response>

    {

        private readonly IExpenseService _service;

 

        public Handler(IExpenseService service)

        {

            _service = service;

        }

 

        public async Task<Response> Handle(Request request, CancellationToken ct)

        {

            await _service.UpdateGroupBudgetAsync(request.GroupId, request.Revenue, request.Margin);

            return new Response(true);

        }

    }

}

Why this is better:

  • You understand the entire feature without jumping across files
  • Everything required to maintain this feature is in one place
  • Fewer files = less mental load
  • AI tools can reason about your feature more easily
  • Visual Studio / VS Code keeps files relevant and small

This is modern C# the way it’s meant to be written.


🧩 When should you still separate classes?

Modern does NOT mean messy.

You should use separate files when:

A class is large (>300 lines)

A class is reused across slices

Domain entities, repository interfaces, mapper profiles, and global helpers.

A type is important enough to deserve its own location

If it matters independently, give it its own space.

The rule isn’t dead—it’s just no longer universal.


🔥 Example: When NOT to combine

// Don't put all of this in one file:

 

public class Project {}

public class Employee {}

public class Region {}

public class District {}

These are domain types, not feature types.
They belong in their own files because they are reused everywhere.


💡 The Mental Shift

Modern architecture—especially Vertical Slice—forces a new mindset:

Don’t organize by technical type

(Models folder, Interface folder, Services folder)

Organize by user behavior

(Features → Commands → Queries → UI → Integration)

It’s about workflow, not taxonomy.

That’s what modernizing your thinking looks like.


🧠 Why This Matters Even More Today

Two big reasons:

1. Feature-first development

Modern systems minimize coupling and maximize independence.
Feature-level files support this goal.

2. AI-assisted development

Copilot, Cursor, GPT, and Sonnet reason more effectively when:

  • The feature lives in one place
  • Dependencies are visible in one file
  • Request/Response/Handler/Validator are co-located

One-file-per-feature actually improves your AI developer experience.


🛠 Modernizing your codebase: a quick checklist

Consolidate related types into single feature files

Use file-scoped namespaces

Adopt Vertical Slice Architecture

Keep domain models in their own files

Let AI tools help refactor toward feature cohesion

Treat “one class per file” as optional, not law


🎯 Final Thoughts

The phrase I keep coming back to is:

“Modernize your code by modernizing your thinking.”

Clinging to old conventions slows down development.
C# has evolved.
Your architecture has evolved.
AI-augmented tooling has evolved.

It's time our coding habits evolve right along with them.

If you're still organizing your codebase like it's 2003…
you’re missing out on the clarity, speed, and elegance that modern C# offers.

 

Comments

Popular posts from this blog

Customizing PWA Manifest and Icons for a Polished User Experience 🚀

Yes, Blazor Server can scale!

Offline-First Strategy with Blazor PWAs: A Complete Guide 🚀