Partial Class in C#: Syntax, Partial Methods and C# 14 Partial Members

A client’s Customer class had grown to 1,400 lines, and half of it was code a tool had generated. Every regeneration wiped out somebody’s hand-written fix.

In this tutorial, I’ll show you how a partial class in C# fixes that, using real .NET 10 runs: the syntax across two files, the rules, partial methods, partial properties, and the C# 14 partial events and constructors.

Quick answer: mark every part with partial, for example public partial class Customer in Customer.cs and Customer.Validation.cs. The compiler merges all parts into one class at build time.

What Is a Partial Class in C#?

A partial class is one class whose source is split across two or more declarations, usually in separate files. Each declaration carries the partial keyword, and the compiler stitches them into a single type.

At run time there’s no trace of the split; callers see one class. Naming the extra files is covered in my C# class naming conventions guide.

Partial Class Syntax Across Two Files

Here’s the smallest real project I’d use to show it. If you haven’t set one up before, my guide to {a(‘create-a-console-application-in-visual-studio-code’, ‘creating a console application in VS Code’)} covers it.

CustomerDemo
├── CustomerDemo.csproj
├── Customer.cs              (properties + ToString)
├── Customer.Validation.cs   (IsValid)
└── Program.cs

CustomerDemo.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

Customer.cs holds the data:

namespace CustomerDemo;

public partial class Customer
{
    public string Name { get; init; } = "";
    public string State { get; init; } = "";
    public decimal Balance { get; set; }

    public override string ToString() => $"{Name} ({State}) {Balance:C}";
}

Customer.Validation.cs adds the rules, and it reads Name and State as if they were declared right here:

namespace CustomerDemo;

public partial class Customer
{
    public bool IsValid() =>
        Name.Length > 0 && State.Length == 2 && Balance >= 0;
}

Program.cs uses the merged class:

using System.Globalization;
using CustomerDemo;

CultureInfo.CurrentCulture = new("en-US");

Customer[] customers =
[
    new() { Name = "Jenna Moore", State = "WA", Balance = 1250.50m },
    new() { Name = "Ryan Carter", State = "Texas", Balance = 80m },
];

foreach (var c in customers)
    Console.WriteLine($"{c} -> valid: {c.IsValid()}");

Output of dotnet run --project CustomerDemo:

Jenna Moore (WA) $1,250.50 -> valid: True
Ryan Carter (Texas) $80.00 -> valid: False
Partial class in C# split across Customer.cs and Customer.Validation.cs in VS Code, with dotnet run output
Two files, one Customer class: IsValid() reads properties declared in the other file

Jenna’s record passes and Ryan’s fails, because “Texas” isn’t a two-letter code. Both parts share the CustomerDemo namespace; put one part in a different namespace in C# and you get two unrelated classes instead.

Rules for Partial Classes: Modifiers, Base Class and Interfaces

The compiler merges more than members. Here, only the first part says sealed and names the base class, and the second part adds a second interface:

var order = new Order { Id = 1001, Total = 200m };

Console.WriteLine($"Base class: {typeof(Order).BaseType!.Name}");
Console.WriteLine($"Sealed: {typeof(Order).IsSealed}");
Console.WriteLine($"IAuditable: {order is IAuditable}, ITaxable: {order is ITaxable}");
Console.WriteLine($"Tax: {order.Tax}");

class Entity { public int Id { get; init; } }
interface IAuditable { }
interface ITaxable { decimal Tax { get; } }

sealed partial class Order : Entity, IAuditable
{
    public decimal Total { get; init; }
}

partial class Order : ITaxable
{
    public decimal Tax => Total * 0.0825m;
}

Output:

Base class: Entity
Sealed: True
IAuditable: True, ITaxable: True
Tax: 16.5000

The merged Order is sealed, inherits Entity, and implements both interfaces. That’s the rule set I keep in my head:

  • Every part needs partial, the same name and the same generic type parameters.
  • All parts live in the same assembly and namespace, so you can’t extend a NuGet package’s class.
  • Access modifiers must match on every part that states one.
  • abstract or sealed on any part applies to the whole class.
  • Parts that name a base class must name the same one; the rules of inheritance in C# still allow only one.
  • Interfaces and attributes from all parts add up, so each file can implement a different interface in C#.

partial also works on structs, interfaces and records, but not on enums or delegates.

Partial Methods in C#: Classic and Extended

Classic Partial Methods (Optional, Implicitly Private)

A classic partial method has no access modifier, returns void and has no out parameters. One part declares it; another part may implement it. If nobody does, the compiler deletes the method and every call to it:

var shipment = new Shipment("Seattle, WA");
shipment.Dispatch();

partial class Shipment(string city)
{
    partial void OnDispatching(string destination);  // no body
    partial void OnDispatched(string destination);

    public void Dispatch()
    {
        OnDispatching(city);                         // removed
        Console.WriteLine($"Truck left for {city}");
        OnDispatched(city);
    }
}
partial class Shipment
{
    partial void OnDispatched(string destination) =>
        Console.WriteLine($"Email: on its way to {destination}");
}

Output:

Truck left for Seattle, WA
Email: on its way to Seattle, WA
C# partial methods OnDispatching and OnDispatched in VS Code, only the implemented one prints output
OnDispatching has no body, so its call disappears; OnDispatched prints the email line

That’s why generated code loves them: the generator leaves hooks like OnDispatched and you implement only the ones you need. They can’t be virtual or override, so method overriding rules don’t apply.

Extended Partial Methods (C# 9 and Later)

Add an access modifier, a non-void return type or an out parameter and the method must be implemented. Leave the body out and the build stops:

var report = new Report();

partial class Report
{
    public partial bool Validate();
}

Build output:

C:\DotNetDemo\PartialClass\MissingBody.cs(5,25): error CS8795: Partial method 'Report.Validate()' must have an implementation part because it has accessibility modifiers.

The build failed. Fix the build errors and run again.

In real projects a source generator usually writes that body, as the [GeneratedRegex] example further down shows.

Partial Properties and Indexers (C# 13)

C# 13 extended the idea to properties and indexers. One part declares the property; the other implements it. On .NET 10 the implementing part can use the C# 14 field keyword instead of a hand-written backing field:

var cart = new Cart(["Laptop", "Mouse"]);
cart.Coupon = "  save10 ";

Console.WriteLine($"Coupon: {cart.Coupon}");
Console.WriteLine($"First item: {cart[0]}");

partial class Cart(string[] items)
{
    public partial string? Coupon { get; set; }
    public partial string this[int index] { get; }
}

partial class Cart
{
    public partial string? Coupon { get; set => field = value?.Trim().ToUpperInvariant(); }
    public partial string this[int index] => items[index];
}

Output:

Coupon: SAVE10
First item: Laptop

The coupon comes back as SAVE10. The implementing part can’t be a plain auto-property like { get; set; }, because it would look like the declaring part. Partial properties also work in a partial struct.

Does C# 14 Support Partial Constructors and Events?

Yes. C# 14, which ships with .NET 10, adds partial instance constructors and partial events. Each needs exactly one defining and one implementing declaration:

var unit = new Thermostat("Austin, TX");
unit.TooHot += (_, t) => Console.WriteLine($"Alert: {t} F");
unit.Report(98);
unit.Report(104);
partial class Thermostat
{
    public partial Thermostat(string city);
    public partial event EventHandler<int>? TooHot;
}
partial class Thermostat
{
    EventHandler<int>? handlers;
    public partial Thermostat(string city) =>
        Console.WriteLine($"Watching {city}");
    public partial event EventHandler<int>? TooHot
    { add => handlers += value; remove => handlers -= value; }
    public void Report(int t)
    { if (t > 100) handlers?.Invoke(this, t); }
}

Output:

Watching Austin, TX
Alert: 104 F
C# 14 partial constructor and partial event with add and remove accessors in VS Code, dotnet run output
The constructor body and the event accessors live in the second part

Only the implementing constructor may call this() or base(), and the implementing event must have add and remove accessors. My constructor in C# and events in C# guides cover the non-partial basics.

98 stays quiet and 104 fires the alert. The rest of the release is in what is new in C# 14.

Where Partial Classes Show Up in Real .NET Projects

I rarely split my own classes just because they’re long. Most partial classes I touch come from tools:

WhereYour fileGenerated / other part
Source generators[GeneratedRegex], [LoggerMessage] partial methodsCode emitted at build time
Windows FormsForm1.csForm1.Designer.cs (InitializeComponent)
WPFMainWindow.xaml.csCode generated from MainWindow.xaml
BlazorCounter.razor.cs code-behindClass compiled from Counter.razor
EF Core scaffoldingCustomer.Custom.csCustomer.cs from dotnet ef dbcontext scaffold

Here’s the regex generator in a file-based app. You write an extended partial method and the generator writes its body:

using System.Text.RegularExpressions;

string[] zips = ["98052", "7301", "10001-2345"];
foreach (var zip in zips)
    Console.WriteLine($"{zip}: {Patterns.UsZip().IsMatch(zip)}");

static partial class Patterns
{
    [GeneratedRegex(@"^\d{5}(-\d{4})?$")]
    public static partial Regex UsZip();
}

Output:

98052: True
7301: False
10001-2345: True

Only the five-digit ZIP and the ZIP+4 match, and the regex is parsed at compile time instead of at startup.

Common Partial Class Errors and How to Fix Them

I packed three mistakes into one file so you can see the real compiler messages:

Console.WriteLine("never runs");

public partial class Invoice { }
public class Invoice { }

public partial class Payment { }
internal partial class Payment { }

partial class Refund : Exception { }
partial class Refund : Attribute { }

Build output:

C:\DotNetDemo\PartialClass\PartialErrors.cs(4,14): error CS0260: Missing partial modifier on declaration of type 'Invoice'; another partial declaration of this type exists
C:\DotNetDemo\PartialClass\PartialErrors.cs(6,22): error CS0262: Partial declarations of 'Payment' have conflicting accessibility modifiers
C:\DotNetDemo\PartialClass\PartialErrors.cs(9,15): error CS0263: Partial declarations of 'Refund' must not specify different base classes

The build failed. Fix the build errors and run again.
Partial class compiler errors CS0260, CS0262 and CS0263 from dotnet run in VS Code
Three partial-class mistakes and the errors the C# compiler reports
ErrorCauseFix
CS0260One declaration is missing partialAdd partial to every part
CS0262Parts use different access modifiersUse the same modifier, or state it on one part only
CS0263Parts name different base classesName one base class, or name it once
CS8795An extended partial method has no bodyAdd the implementing declaration (or the generator)

Which Partial Feature Should You Use?

Use a partial class when a tool owns part of the code, classic partial methods for optional hooks, and extended methods or partial properties when a generator writes the body.

Want to keep going with C# classes? These pair well with this one:

Frequently Asked Questions

Can a partial class be split across different projects?

No. All parts must be in the same assembly and module, so they have to compile in the same project. To add behavior to a class from another assembly, use extension members or inheritance.

Can a partial class inherit from two classes?

No. C# allows one base class, and every part that names a base class must name the same one (otherwise CS0263). Different parts can add different interfaces.

What happens if a partial method isn’t implemented?

For a classic partial method (no access modifier, void, no out parameters) the compiler removes the method and every call to it. An extended partial method must be implemented, or you get CS8795.

Can records and structs be partial?

Yes. The partial modifier works on classes, records, structs and interfaces, but not on enums or delegates. See Partial Classes and Members on Microsoft Learn.

Which C# version added partial properties and partial constructors?

Partial properties and indexers came in C# 13 (.NET 9). Partial constructors and partial events came in C# 14, which ships with .NET 10.