← Open in the full interactive course (progress tracking, search & more)

Source generators have been splitting classes and methods across a hand-written half and a generated half for years. C# 14 finally lets a constructor do the same split.

Continuing this Part's tour of C# 14 additions — lambda parameter modifiers (328) just removed a small tax on modifier-carrying lambda parameters. This lesson covers a different, unrelated addition: partial now works on constructors, not just classes and methods.

You've already seen the shape of this idea. Lesson 184 walked through a source generator using [LoggerMessage] on a partial void LogOrderShipped(...) method — a declaration you wrote by hand, with the actual logging body generated for you behind the scenes. This lesson shows the same declaring/implementing split, extended in C# 14 to constructors (and instance events): a constructor's signature can now live in one place while its body is supplied somewhere else — most often, by a source generator.

What Is It?

The Simple Explanation

A partial constructor is a constructor whose signature is declared in one part of a partial class (ending in a semicolon, no body) and whose actual body is implemented in another part of that same partial class. The compiler merges the two into a single, ordinary constructor — exactly the same declaring/implementing split lesson 184 already showed you for methods, now available for constructors too.

The Technical Definition

C# 14 extends the partial member concept — already usable on classes (since C# 2), methods (since C# 3, unrestricted since C# 9), and properties/indexers (since C# 13) — to instance constructors and instance events. A declaring partial constructor states the signature only; exactly one matching implementing partial constructor, elsewhere in the same partial class, supplies the body. Unlike the old, optional-if-unimplemented style of private void partial methods, a partial constructor requires both halves — a declaration with no matching implementation fails to compile.

Already Partial-Capable

🆕 Newly Partial-Capable — C# 14

Why Does It Exist?

The Problem — Generators Could Reach Every Member Except the Constructor

Lesson 184 showed a source generator contributing an ordinary method body to a hand-written partial class cleanly, with no manual wiring required on your part. Constructors were the conspicuous gap: before C# 14, a generator had no equivalent way to contribute directly to a constructor's own body. The workarounds were all awkward — a generator could emit an extra ordinary method (like a generated Initialize()) that you then had to remember to call yourself from inside your own hand-written constructor, or it could emit a second, generator-owned constructor overload with a different signature, hoping callers picked the right one. Either way, the clean separation lesson 184 demonstrated for methods simply didn't extend to the one member every object construction path runs through.

The Solution — the Same Split, One More Member Kind

C# 14 closes that gap directly: a constructor can now be split into a declaring half (the signature, written by hand, in the ordinary hand-authored part of the partial class) and an implementing half (the body, supplied wherever makes sense — often a generated file). No new mechanism was invented — this is the same rule partial methods and partial properties already followed, just recognizing that a constructor is a member like any other, and there was never a principled reason to leave it out.

Big Picture — Declaring vs. Implementing

Declaring Half
Signature only, ends in a semicolon — no body, no constructor initializer
Implementing Half
Same signature, plus a full body — this is where : base(...) or : this(...) goes, if needed
Same Partial Class
Both halves must belong to the same partial class — usually two different files
Compiler Merge
The two halves become one ordinary constructor at compile time — nothing extra exists at runtime

How It Works

FROM TWO FILES TO ONE COMPILED CONSTRUCTOR
1. THE HAND-WRITTEN FILE DECLARES THE CONSTRUCTOR'S SIGNATURE
2. A SECOND FILE — OFTEN GENERATED — IMPLEMENTS THAT EXACT SIGNATURE
3. THE COMPILER REQUIRES EXACTLY ONE OF EACH — NO MORE, NO FEWER
4. AT COMPILE TIME, THE TWO HALVES BECOME ONE ORDINARY CONSTRUCTOR

Simple Example

// ServiceConfig.cs — the hand-written half public partial class ServiceConfig { public string ConnectionString { get; } public int TimeoutSeconds { get; } // Declaring half: signature only, ends with a semicolon, no body public partial ServiceConfig(string connectionString, int timeoutSeconds); } // ServiceConfig.g.cs — the implementing half (conceptually, generator-emitted) public partial class ServiceConfig { // Implementing half: same signature, now with the real body public partial ServiceConfig(string connectionString, int timeoutSeconds) { if (string.IsNullOrWhiteSpace(connectionString)) throw new ArgumentException("Connection string is required.", nameof(connectionString)); ConnectionString = connectionString; TimeoutSeconds = timeoutSeconds > 0 ? timeoutSeconds : 30; } } // Calling code sees one ordinary constructor — the split is invisible from here var config = new ServiceConfig("Server=.;Database=Orders;", timeoutSeconds: 0); Console.WriteLine(config.TimeoutSeconds); // 30 — the default kicked in

Meaning: Neither file alone is a complete constructor — the declaring half has no logic, and the implementing half, on its own, wouldn't be recognized as fulfilling a contract without the declaration. Together, the compiler treats them as exactly one constructor, indistinguishable at the call site from one written the ordinary, single-file way.

Real-World Example — a Validation Generator

Lesson 184's [LoggerMessage] generator inspected a hand-written partial method's attribute and emitted its body. A validation-focused generator can now do the equivalent thing for a constructor: it reads validation attributes already declared on a class's properties, and emits the guard-clause logic straight into the constructor's implementing half — logic you'd otherwise have to hand-write and keep in sync every time a property's validation rule changes.

// Customer.cs — hand-written: properties carry the validation intent, // the constructor is only declared, never implemented by hand public partial class Customer { [Required, MaxLength(100)] public string Name { get; } [Range(0, 150)] public int Age { get; } public partial Customer(string name, int age); } // Customer.g.cs — generated: the guard clauses a human would have // hand-written and had to remember to update whenever a rule changed public partial class Customer { public partial Customer(string name, int age) { if (string.IsNullOrEmpty(name) || name.Length > 100) throw new ArgumentException("Name is required and must be 100 characters or fewer.", nameof(name)); if (age is < 0 or > 150) throw new ArgumentOutOfRangeException(nameof(age)); Name = name; Age = age; } }

The attributes are the single source of truth; the constructor body is a mechanical consequence of them, produced once per build and never hand-maintained. This is the same generator-friendly split lesson 184 already showed for logging — partial constructors simply mean it no longer stops at the constructor's door.

Analogy

A Fill-in-the-Blank Contract

Picture a legal contract with a clause deliberately left as a blank: "the tenant shall pay a security deposit of ____." One party — the landlord's lawyer — writes and finalizes the shape of the clause, right down to its exact wording. A separate party — the actual negotiation — fills in the number. Neither half means anything alone: a blank with no number filled in isn't a binding clause, and a bare number with no surrounding clause to attach to means nothing either. Only once both halves exist does the contract actually have a term. A partial constructor works exactly this way: the declaring half fixes the exact shape (the parameters, the modifiers, everything that has to match), and the implementing half fills in what actually happens — and just like the contract, leaving either half missing means there's no valid agreement at all.

Under the Hood

A partial constructor compiles down to exactly one ordinary constructor in the resulting type — there is no trace at runtime of the fact that its signature and body came from two different source files. This is purely a compile-time, source-organization feature, the same as every other partial member you've already met.

A few rules are worth being precise about, because the compiler enforces them strictly. The declaring and implementing signatures must match exactly — same parameter types, same parameter names, and (tying back directly to lesson 328) the same modifiers on each parameter, including ref, out, and in if any are used. Only the implementing half is allowed to carry a constructor initializer (: base(...) or : this(...)) — the declaring half has no body for an initializer to attach to, so it simply isn't legal there. And unlike the historical, optional style of plain private void partial methods (where an unimplemented declaration was silently dropped, calls and all, from the compiled output), a partial constructor's declaring half is mandatory to implement — leaving one unimplemented is a compile error, not a silent no-op. That distinction matters: nobody wants a constructor call to quietly do nothing because a generator run was skipped.

Common Confusion

1. "This is a brand-new concept" — no, it's the lesson 184 pattern, one member kind further

If lesson 184's [LoggerMessage] partial method made sense to you, partial constructors are not conceptually new — same declaring/implementing split, same "one hand-written half, one generated half" motivation, applied to a constructor instead of an ordinary method.

2. "Leaving the implementation out is fine, like old void partial methods" — no, not for constructors

The historically optional behavior applied only to a narrow, older style of private void partial methods. Partial constructors follow the newer, mandatory-implementation rule (the same one lesson 184's non-void, accessible partial methods already followed) — a declaring half with no implementation anywhere fails to compile, full stop.

Common Mistakes

Mistake 1 — Forgetting the class itself must be partial

Writing a partial constructor inside a class that isn't itself declared partial — exactly the setup requirement lesson 184 already flagged for partial methods, and just as easy to forget here. Both the declaring and implementing constructor halves must live inside a class explicitly marked partial in every file they appear in.

Mistake 2 — Letting the two signatures drift apart

Changing a parameter's type or modifier on the declaring half without updating the implementing half (or vice versa) — especially easy when the implementing half is generated and the declaring half is hand-edited later. Treat the declaring half as the contract; any change to it needs the matching generator input (or the other hand-written half) updated in lockstep, or the build simply won't compile until they agree again.

Mistake 3 — Putting a constructor initializer on the declaring half

Trying to write public partial Customer(string name) : this(name, 0); as the declaring half. A constructor initializer needs a body to attach to, and the declaring half never has one — it belongs exclusively on the implementing half, where the real constructor body lives.

When Should I Use It?

Rule of thumb: If you find yourself wanting a partial constructor and there's no source generator (or similarly automated second half) involved, you almost certainly just want a normal constructor. The whole point of the split is letting generated and hand-written code coexist cleanly — without a generator in the picture, there's nothing to keep apart.

Mental Model

Declaring half = the contract (signature only, semicolon, no body)
Implementing half = the fulfillment (same signature, real body, holds any : base(...)/: this(...))
Compiler = merges both into one ordinary constructor, and refuses to compile if either half is missing

Remember: this is lesson 184's [LoggerMessage] pattern, one member kind further — not a new idea, just a closed gap.

Key Takeaway


Check Your Understanding

You've seen how partial constructors extend a pattern you already recognized from source generators. Let's confirm it clicked.

1. What is the closest existing pattern that partial constructors extend?

Show answer

Correct: A

Why A is correct: Partial constructors are explicitly the same declaring/implementing split lesson 184's [LoggerMessage] partial method example demonstrated — C# 14 simply makes constructors eligible for the same split.

Why B is incorrect: Constructor overloading involves multiple, genuinely different constructors coexisting — a partial constructor is one single constructor whose signature and body are declared in two different places, not two different constructors.

Why C is incorrect: Abstract methods involve inheritance across different types; partial constructors involve splitting one member of one type across two files of the same partial class, with no inheritance involved.

Why D is incorrect: Extension methods add new members to a type they don't otherwise belong to; a partial constructor's two halves are both genuine parts of the same type's own declaration.

Reinforcement: Same split, new member kind — this is lesson 184's pattern, not a new idea.

2. A developer writes a declaring partial constructor but never writes a matching implementing half anywhere in the partial class. What happens?

Show answer

Correct: B

Why B is correct: The lesson was explicit about this distinction — a partial constructor's declaring half requires a matching implementing half; leaving it unimplemented is a compile error, not a silent no-op.

Why A is incorrect: This describes the historical behavior of the older, optional style of private void partial methods specifically — partial constructors deliberately don't follow that optional pattern.

Why C is incorrect: The compiler does not auto-generate a body for a missing implementing half — it requires you (or a generator) to supply one explicitly.

Why D is incorrect: There is no such automatic behavior — an unimplemented declaring constructor is simply a compile error.

Reinforcement: Partial constructors are mandatory on both halves — this is a deliberate difference from the old optional void-partial-method behavior.

3. Where is a constructor initializer, such as : base(connectionString), allowed to appear on a partial constructor?

Show answer

Correct: C

Why C is correct: As the Under the Hood section explained, the declaring half is signature-only with no body — a constructor initializer needs a body context to attach to, so it's only legal on the implementing half.

Why A is incorrect: The declaring half is exactly where this isn't allowed, precisely because it has no body.

Why B is incorrect: The placement isn't a matter of convenience — only the implementing half has the body context a constructor initializer requires.

Why D is incorrect: Constructor initializers remain fully usable with partial constructors — they simply belong on the implementing half specifically.

Reinforcement: Anything requiring a body — the initializer included — belongs on the implementing half, never the declaring half.

4. Which scenario is the strongest fit for reaching for a partial constructor?

Show answer

Correct: B

Why B is correct: This is exactly the motivating scenario the lesson walked through — a generator contributing the implementing half of a constructor based on information (like validation attributes) present in the hand-written half.

Why A is incorrect: If both halves live in your own hand-written code in the same file, a partial constructor adds ceremony with no benefit — the "When Should I Use It?" rule of thumb calls this out directly.

Why C is incorrect: Multiple constructors with different signatures is ordinary constructor overloading, unrelated to splitting one constructor's declaration from its implementation.

Why D is incorrect: A static class can't be instantiated and has no instance constructors at all — partial constructors don't apply to that scenario.

Reinforcement: Reach for a partial constructor specifically when a generator (or similarly automated process) is contributing one of the two halves.

Next in the C# 14 tour: user-defined compound assignment (330) — letting a type decide that += can mutate in place instead of always building something new.


dotnetmadeeasy.com — Learn C# and .NET, the right way.