Partial constructors and events in C# 14
C# 14 introduces a new capability to declare instance constructors and events as partial
members. This means you can split a constructor or event definition across two parts of a partial class, similar to how C# has long allowed partial methods and partial properties. This means that, one part of a partial class can declare a constructor or event, and another part can implement it. This is especially useful in scenarios like code generation, where one file might be auto-generated and another maintained manually by a developer.
Partial constructors in C# 14
Partial constructors let you split a class constructor into two declarations within a partial
class. One declaration provides just the signature (a defining declaration), and another provides the actual body (an implementing declaration). The compiler merges these so they behave as a single constructor at runtime.
Key rules for partial constructors:
- Exactly one defining and one implementing declaration: You must have one part of the partial class that declares the constructor (ending in a semicolon with no body), and another part that provides the implementation (with the constructor’s code block). The signatures (parameter types, names, etc.) must match exactly between the two. If either the defining or implementing part is missing (or if there are duplicates), the code will not compile.
- Only in partial classes: Both declarations must be in the same class, which itself must be marked
partial
. You cannot have a partial constructor in a non-partial class. - Constructor initializer only in the implementation: If the constructor needs to call another constructor or a base class constructor using
: this(...)
or: base(...)
, that initializer can only appear on the implementing declaration. The defining (signature-only) part is not allowed to include any constructor initializer. In practice, you write anythis(...)
orbase(...)
call in the implementing constructor’s header. - Primary constructor syntax in only one file: C# 14 allows using primary constructors (constructor parameters in the class declaration) with partial classes, but only one partial file can include the parameter list. In other words, if you choose to use the concise primary-constructor form in a partial class, you must put that in a single partial class declaration; other partial declarations of the same class should not repeat or declare their own parameter list.
Below is an example of a partial constructor in action. Imagine a class split across two files – one auto-generated part declares a constructor, and another part implements it:
// File: Car.AutoGenerated.cs (auto-generated partial class)
public partial class Car
{
// Defining partial constructor (signature only, no body)
public partial Car(string model);
}
// File: Car.cs (developer-maintained partial class)
public partial class Car
{
// Implementing partial constructor (actual body)
public partial Car(string model)
{
// (Optional) Can call another constructor or base constructor here
// : base() or : this(...) would be placed after the parameter list if needed.
if (string.IsNullOrEmpty(model))
throw new ArgumentException("Model must be provided", nameof(model));
Console.WriteLine($"Car model set to {model}");
}
}
Code language: C# (cs)
In this example, the Car
class’s constructor is split: the first part declares that Car(string model)
exists, and the second part provides the implementation that checks the argument and prints a message. The defining declaration cannot include any code – it’s just the signature ending in ;
. The implementing declaration has the body and could also include a base class call (if Car
inherited from another class) or call another constructor via this()
. At compile time, the compiler ensures there is exactly one of each part and then treats the constructor as a single unified constructor.
Partial events in C# 14
Partial events allow a similar split definition for events in a partial class. One part of the class defines the event (with no add/remove logic, just like a normal automatic event declaration), and another part provides the add/remove accessors (the code that runs when subscribers attach or detach event handlers).
Key rules for partial events:
- Exactly one defining and one implementing declaration: Just like with constructors, an event marked
partial
must be declared exactly twice in the partial class: one definition with no body, and one implementation with the accessor bodies. They must have the same event type and name, and appear in the same partial class. You cannot have extra partial event pieces beyond those two. - Defining declaration is field-like: The defining part of a partial event is written as you would normally declare an event backed by a field – for example,
public partial event EventHandler SomethingHappened;
(with a semicolon and no accessor block). However, because it’s partial, the compiler will not auto-generate any backing field or default add/remove logic for this event. It’s essentially a placeholder indicating “there will be an event of this name and type.” - Implementing declaration must have accessors: The implementing part of the partial event must provide both
add
andremove
accessors in an event block, just like a manually-implemented event. This is where you write what happens when listeners subscribe or unsubscribe. Since the defining part has no implicit backing field, your implementation typically needs to manage storage for subscribers (for example, via a private field, list, or other mechanism) or forward to another event source. - Only in partial classes (and not abstract or interface implementations): As with partial constructors, partial events can only be used inside a class marked
partial
. You also cannot mark a partial event asabstract
, and you can’t usepartial
to implement an interface’s event – the feature is intended for splitting within a class, not across interface boundaries.
Here’s an example demonstrating a partial event. One file declares the event, and another file implements custom add/remove logic:
// File: Sensor.AutoGenerated.cs (auto-generated partial class)
public partial class Sensor
{
// Defining partial event (no body, just declaration)
public partial event EventHandler DataReceived;
}
// File: Sensor.cs (developer-maintained partial class)
public partial class Sensor
{
// Implementing partial event (with custom add/remove logic)
private EventHandler _dataReceivedHandlers; // backing storage for event handlers
public partial event EventHandler DataReceived
{
add
{
Console.WriteLine("Subscriber added to DataReceived");
_dataReceivedHandlers += value;
}
remove
{
Console.WriteLine("Subscriber removed from DataReceived");
_dataReceivedHandlers -= value;
}
}
// Optional: a method to raise the event safely from this class
protected void OnDataReceived(EventArgs e)
{
_dataReceivedHandlers?.Invoke(this, e);
}
}
Code language: PHP (php)
In this example, the Sensor
class has a DataReceived
event split across two files. The auto-generated part of the class declares that there is a DataReceived
event of type EventHandler
. The developer’s part provides the actual implementation: it defines a private field to hold the subscribers and implements the add
and remove
blocks to log a message and update that private field. When code elsewhere does sensor.DataReceived += Handler;
, it will run the custom add
logic. Likewise, unsubscribing triggers the custom remove
logic. The class can use _dataReceivedHandlers
to raise the event (for example, in the OnDataReceived
method). Without the implementing part, the defining declaration alone wouldn’t compile, since the event has no backing by itself – C# requires the implementing part to complete the event’s functionality.
Comparison with earlier C# versions
Prior to C# 14, you could not declare constructors or events as partial. In C# 13 and earlier, the partial
keyword was only valid on classes, methods, and (from C# 13) properties/indexers. If you attempted to mark a constructor or an event as partial, the compiler would produce an error. This limitation meant there was no way to split the implementation of a constructor or an event between different files.
Developers often used workarounds to achieve similar flexibility. For example, consider a scenario where auto-generated code needs to call some custom logic during object construction. Without partial constructors, one common pattern was to use a partial method inside the constructor. The auto-generated part of a class could call this method, while the method’s implementation would be in another file. For instance, in older C# code you might see:
// Before C# 14: using a partial method to extend constructor logic
public partial class Car
{
public Car()
{
InitializeComponents(); // auto-generated initialization
OnConstructed(); // call a partial method hook
}
// This partial method can be implemented in another file to add custom behavior
partial void OnConstructed();
}
Code language: C# (cs)
In the second file, the developer could implement the OnConstructed
partial method to execute additional code after InitializeComponents()
. This pattern allowed injecting custom code at construction time, but it was indirect and a bit clunky. Similarly, for events, there was no easy workaround – one would either have to inherit and override functionality or modify the generated code, since you couldn’t split an event’s add/remove logic across files.
With C# 14’s partial constructors and events, these workarounds are no longer necessary. You can directly declare a constructor or event as partial and supply the implementation separately, making the code more straightforward. The new approach is clearer and eliminates the need for dummy partial methods or other tricks.
Use cases and benefits
The primary beneficiaries of partial constructors and events are scenarios involving code generation and tooling. Many frameworks and tools generate C# code (for example, UI designers, ORMs, or interface scaffolding tools) and often mark classes as partial so that developers can extend the generated classes without editing the auto-generated code. With C# 14:
- Code generators can define constructors for you – for instance, a source generator might create a partial constructor signature that the developer needs to implement with custom initialization logic. Conversely, a generator could implement a partial constructor that the developer declared, perhaps to inject framework-specific setup code behind the scenes. This makes it easier to mix generated initialization with user-written initialization in a safe way.
- Splitting complex construction logic – In large projects, you might want to organize constructor logic across multiple files (for example, separating dependency wiring vs. business logic). Partial constructors provide a structured way to do this if needed.
- Custom event patterns – Partial events enable advanced event handling scenarios. A prime example is the weak event pattern (to avoid memory leaks with events). A developer or source generator can declare a partial event in one file (perhaps annotated with something like
[WeakEvent]
), and implement it in another file such that theadd
/remove
use weak references to handlers. This means the subscribing objects don’t prevent garbage collection if they forget to unsubscribe. Before partial events, implementing a weak event pattern could require a lot of boilerplate or external libraries. Now a library author could provide a generator that automatically supplies the complex add/remove logic in a separate partial implementation, while the user’s code remains clean. Similarly, partial events could be used to forward event subscriptions to underlying systems (for example, wrapping an event from a lower-level API by handling add/remove and connecting them to the actual source). - Platform and interop code – In interoperability scenarios (such as Xamarin or .NET interop with native libraries), generated classes often need to call into native code in constructors or manage native event hookups. Partial constructors allow the native call setup to live in generated code while the rest of the constructor is user-defined (or vice versa). Partial events can let a class expose a .NET event that is actually backed by some platform-specific event mechanism implemented in another partial file.
In all these cases, partial constructors and events make code easier to maintain. The auto-generated code can declare what needs to exist, and the hand-written code can be limited to what the developer really cares about implementing. There’s also no runtime performance cost for using partial members – the separation exists only at compile time. The resulting program behaves as if you wrote the full constructor or event in one place.
By allowing instance constructors and events to be partial
, C# 14 rounds out the partial class feature set to cover nearly all member types. This enhancement improves the language’s ability to support clean separation of auto-generated and custom code. Developers get the flexibility to organize and extend class behavior without hacks, and source generators gain a powerful new tool to inject functionality in a modular way. Whether you’re working on large frameworks or just splitting code for clarity, partial constructors and events offer a more expressive and convenient approach in C# 14.