Mastering Declarative Class Composition for a New Era of Source-Generated C#
C# has supported partial classes for many years – the foundation of code generation in ASP.NET, WinForms, Entity Framework, gRPC, MAUI, Orleans, and countless tools that generate C# code into separate files.
But until C# 14, there was one major limitation:
constructors and events could not be partial.
This meant:
- Source generators couldn’t contribute constructor logic
- Frameworks had to emit fragile workarounds
- DI frameworks had to guess what constructors exist
- Events with generated add/remove logic couldn’t participate in partial APIs
- Mixed human-written + generated logic was difficult to achieve cleanly
C# 14 fixes this with the introduction of:
- Partial Constructors
- Partial Events
A massive quality-of-life leap for modern tooling, metaprogramming, and declarative class building.
🔍 The Problem: Constructors and Events Were “All or Nothing”
C# allowed this:
public partial class Customer
{
public string Name { get; set; }
}
But not this:
public partial Customer() { }
or:
public partial event EventHandler Updated;
This meant:
❌ Constructors couldn’t be shared
Generated code could add methods or fields, but not constructor logic.
Frameworks such as:
- Entity Framework Core
- Blazor
- WPF
- WinForms
- Orleans
- Mediator frameworks
- UI code generators
…all had to rely on fragile hacks like:
- Factory methods
- Partial methods pretending to be constructors
- Code that invokes callbacks manually
❌ Events couldn’t be composed
You could not have:
- Human-written logic + generated logic in the same event
- Hooks into event add/remove logic
- Partial definitions of event wrappers
- Modular event pipelines across generated files
Partial methods helped, but they were not enough.
C# 14 finally addresses this.
⚡ The Solution: Partial Constructors and Partial Events
C# 14 allows both constructors and events to be declared as partial, enabling distributed definitions across multiple files.
🧠 Conceptual Model: “Composable Class Initialization and Event Pipelines”
Partial Constructors
A constructor may be defined across multiple partial class files:
public partial class User
{
public partial User(string name);
}
In another file:
public partial class User
{
public partial User(string name)
{
Name = name;
}
}
The compiler merges them into one constructor, in source order.
This allows:
- Framework-generated constructor logic
- Hand-authored constructor code
- DI-friendly declarative constructor composition
- Cleaner initialization pipelines
Partial Events
Like partial constructors, partial events can define:
- An event declaration
- Additional attached or detached logic
- Custom accessor logic
- Generated or manual event wiring
🧩 Real-World Example: Source-Generated Validation in a Constructor
File 1 — human-written constructor
public partial class Customer
{
public partial Customer(string name)
{
Name = name;
}
}
File 2 — generated validation logic
public partial class Customer
{
public partial Customer(string name)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException("Name cannot be empty.");
}
}
Resulting merged output (conceptually):
public Customer(string name)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException("Name cannot be empty.");
Name = name;
}
Pure, elegant, and composable.
🧩 Real-World Example: Partial Events in a UI or MVVM Framework
File 1 — Human code:
public partial class Dashboard
{
public partial event EventHandler Refreshed;
}
File 2 — Generated instrumentation:
public partial class Dashboard
{
public partial event EventHandler Refreshed
{
add
{
Console.WriteLine("Handler added");
_refreshed += value;
}
remove
{
Console.WriteLine("Handler removed");
_refreshed -= value;
}
}
}
File 3 — Another generator:
public partial class Dashboard
{
public partial event EventHandler Refreshed
{
add
{
Telemetry.Track("RefreshHandlerAdded");
}
remove
{
Telemetry.Track("RefreshHandlerRemoved");
}
}
}
This creates a pipeline of add/remove behavior enforced by the compiler.
🔬 Under the Hood: How the Compiler Merges Partial Members
Constructors
The compiler concatenates all partial constructor bodies in:
- The order they appear
- Across all partial class files
- With consistent parameter signatures
Rules:
- All partial constructor definitions must have identical signatures
- Only one can omit a body (declaration only)
- The merged order follows file/compile order (predictable)
Events
Partial events are merged by:
- Combining event declarations
- Combining accessor bodies (
add/remove) - Respecting explicit and implicit backing fields
If accessors exist in multiple files, their bodies are merged sequentially.
This is similar to how partial methods were extended in previous C# versions.
🧱 Advanced Usage: Framework-Level Scenarios
1. Dependency Injection (DI)
A source generator can add DI registration logic inside a constructor:
public partial Customer(IServiceProvider provider);
2. ORMs & Data Mappers
Generators can add:
- Change tracking hooks
- Lazy-loading logic
- Validation
- Mapping hints
…directly into the constructor body.
3. UI Systems
Event composition becomes trivial:
- Auto-refresh
- Telemetry
- Logging
- Lifecycle hooks
4. Game Engines
Entity constructors can have:
- Generated physics initialization
- Component injection
- Scripting hooks
5. API Frameworks
Auto-generated events can merge:
- Observability pipelines
- Caching layers
- Request mutation handlers
🧰 Integration Scenarios
Libraries & Frameworks
Clean extension points for metaprogramming.
Enterprise Apps
Class initialization becomes modular and declarative.
Tooling
Source generators gain fine-grained control.
UI & Data-Binding
Fine control over event pipelines.
Education
Demonstrates compiler-driven code composition elegantly.
🧩 Best Practices
✔️ For Partial Constructors
- Keep human-written logic at the end of the constructor
- Let generated files handle validation, tracking, and metadata
- Avoid side effects in generated constructors
- Keep signatures stable to avoid merge errors
✔️ For Partial Events
- Use explicit accessor blocks for clarity
- Define a private backing field where appropriate
- Be aware that events may now run multiple add/remove sequences
❌ Avoid
- Deeply nested partial event pipelines
- Ordering-dependent side effects
- Mixing conflicting accessors across tools
Summary
| Concept | Before C# 14 | After C# 14 |
|---|---|---|
| Partial constructors | Not allowed | Fully supported |
| Partial events | Not allowed | Fully supported |
| Source generator flexibility | Limited | Massive increase |
| Constructor composition | Manual, messy | Declarative and merged |
| Event pipelines | Single-file only | Multi-file composable |
| Boilerplate | High | Dramatically reduced |
Final Thoughts
The arrival of partial constructors and partial events in C# 14 is a major milestone for the ecosystem. They unlock a new era of:
- Declarative initialization
- Framework-driven composition
- Rich tooling without hacks
- Robust and predictable code generation
- Reduced boilerplate
- Better separation between human-written and generated logic
These features will benefit almost every modern C# developer – especially those building or using advanced frameworks and generators.