Remigiusz ZalewskiRemigiusz Zalewski

Partial classes in C# - Full Guide

partial-classes-csharp

Introduction

Partial classes get dismissed as "a way to make a giant file smaller," which undersells what they're actually for. Their real job is separating code that's safe to regenerate from code a human wrote and doesn't want clobbered - which is exactly the pattern EF Core's database scaffolding, Windows Forms designers, and source generators all lean on. This video covers the feature properly: partial classes, partial methods, and the generated-vs-handwritten split they enable.

šŸŽ¬ Watch the full video here:


The basic syntax

partial lets a single class's definition span multiple files - the compiler merges them into one type before anything else happens:

// Customer.cs
public partial class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
}

// Customer.Validation.cs
public partial class Customer
{
    public bool IsValid() => !string.IsNullOrWhiteSpace(Name);
}

Both files compile into one Customer type. Neither file "owns" the class more than the other - every partial declaration is required to carry the keyword, and they must all agree on accessibility and base type.

The real reason it exists: generated code

EF Core's dotnet ef dbcontext scaffold generates entity classes and a DbContext from an existing database - and marks every generated file partial specifically so you can add your own logic in a second file without it being wiped out the next time you re-scaffold:

// Customer.cs - generated, regenerated on every scaffold
public partial class Customer { public int Id { get; set; } }

// Customer.Extensions.cs - hand-written, safe forever
public partial class Customer
{
    public string DisplayName => $"{Name} (#{Id})";
}

Re-run the scaffold command and Customer.cs gets overwritten; Customer.Extensions.cs is untouched because the tool never generated it and doesn't know it exists. This is the actual design intent - not "split a big file," but "let tooling own one file and a human own another, safely."

Partial methods: optional hooks with zero cost if unused

A partial method is declared in generated code as a hook a human may implement, and if nobody implements it, the compiler removes the call site entirely - no runtime cost, no null check needed:

public partial class Customer
{
    partial void OnNameChanged(string newName);

    public void Rename(string newName)
    {
        Name = newName;
        OnNameChanged(newName);
    }
}

If no other file provides a body for OnNameChanged, the call in Rename is erased at compile time. If you do implement it in a handwritten partial file, it runs exactly where the generated code calls it. This is precisely the mechanism OnModelCreatingPartial uses in EF Core's scaffolded DbContext - a customization point that costs nothing when unused.

Partial properties (C# 13+)

Recent C# extends the same idea to properties, useful for source generators that need to declare a property's shape in generated code while a hand-written partner file supplies the actual implementation:

public partial class ViewModel
{
    public partial string Status { get; set; }
}

Common pitfalls

  • Splitting a class into partials purely to make files smaller, with no generated/handwritten distinction - it usually signals the class itself is doing too much and should be split into separate types instead.
  • Editing a generated partial file directly, only to have it overwritten the next time the generator runs.
  • Forgetting a partial method must return void (pre-C# 13) - it can't be used for anything that needs a result back.
  • Inconsistent modifiers across partial declarations (e.g., one file marking it sealed, another not) causing a compile error.
  • Assuming partial classes span multiple assemblies. They don't - all parts must be in the same assembly.

Key Takeaways

  • Partial classes exist primarily to separate generated code from hand-written extensions, not just to shrink files.
  • EF Core's database scaffolding and Windows Forms designers both rely on this split to regenerate code safely.
  • Partial methods are optional hooks - unimplemented ones are erased entirely at compile time, at zero runtime cost.
  • C# 13 extends the same generated/handwritten split to partial properties, primarily for source generator scenarios.

Get the Full Source Code

The complete runnable solution - the generated/handwritten split example and the partial method hook pattern - is available to Patreon supporters. If you want the pattern ready to apply to your own generated code instead of rebuilding it from the walkthrough above, you can find it on Patreon.

Resources