C#  

Partial Events and Constructors in C# 14 (.NET 10) Explained with Examples

Partial types have been part of C# for many years, enabling large classes, records, or structs to be split across multiple files. This capability is heavily used by tooling such as code generators, designers, and source generators.

Until now, partial support has been limited to events and constructors. Developers often had to rely on workarounds, base classes, or manual code merging.

C# 14 introduces partial events and partial constructors, making it easier to combine generated code and hand-written code in a clean, maintainable, and safe way.

1. The Problem Before C# 14

Partial Types with Limitations

Consider a partial class used with code generation:

  
    public partial class OrderService
{
}
  

While methods and properties could be split across files, constructors and events had to be fully defined in a single place .

This caused issues such as:

  • Code generators overwriting custom logic

  • Developers modifying generated files

  • Poor separation of concerns

2. Partial Constructors in C# 14

C# 14 allows constructors to be declared as partial , enabling their implementation to be split across multiple files.

Basic Partial Constructor Example

File 1 : Generated code

  
    public partial class OrderService
{
    partial void Initialize();
    
    public OrderService()
    {
        Initialize();
    }
}
  

File 2 : Developer-written code

  
    public partial class OrderService
{
    partial void Initialize()
    {
        // Custom initialization logic
        Console.WriteLine("OrderService initialized.");
    }
}
  

This pattern ensures:

  • Generated code remains untouched

  • Custom logic is safely injected

  • Clear separation of responsibilities

Partial Constructor with Dependencies

Partial constructors are especially useful when dependency initialization is split between generated and manual code.

File 1 :

  
    public partial class CustomerService
{
    private readonly ILogger<CustomerService> _logger;

    public CustomerService(ILogger<CustomerService> logger)
    {
        _logger = logger;
        OnConstructed();
    }

    partial void OnConstructed();
}
  

File 2:

  
    public partial class CustomerService
{
    partial void OnConstructed()
    {
        _logger.LogInformation("CustomerService constructed.");
    }
}
  

This avoids forcing all initialization logic into a single file.

3. Partial Events in C# 14

C# 14 also introduces support for partial events , allowing event declarations and implementations to be split across partial type definitions.

Motivation for Partial Events

Events are commonly used in:

  • Domain-driven design

  • UI frameworks

  • Workflow engines

  • Generated models

Previously, events had to be fully declared in one place, limiting extensibility.

Basic Partial Event Example

File 1 : Generated code

  
    public partial class PaymentProcessor
{
    public partial event EventHandler PaymentCompleted;

    protected void RaisePaymentCompleted()
    {
        PaymentCompleted?.Invoke(this, EventArgs.Empty);
    }
}
  

File 2 : Developer-written code

  
    public partial class PaymentProcessor
{
    public partial event EventHandler PaymentCompleted
    {
        add
        {
            Console.WriteLine("Subscriber added.");
            _paymentCompleted += value;
        }
        remove
        {
            Console.WriteLine("Subscriber removed.");
            _paymentCompleted -= value;
        }
    }

    private EventHandler _paymentCompleted;
}
  

This allows developers to customize event behavior without modifying generated code.

4. Real-World Use Cases

Partial events and constructors are particularly useful in the following scenarios:

  • Source generators

  • ORM-generated entities

  • API client generators

  • UI frameworks

  • Enterprise domain models

5. Combining Partial Constructors and Events

A realistic example combining both features:

File 1 :

  
    public partial class InventoryService
{
    public partial event EventHandler StockUpdated;

    public InventoryService()
    {
        OnInitialized();
    }

    partial void OnInitialized();

    protected void NotifyStockUpdated()
    {
        StockUpdated?.Invoke(this, EventArgs.Empty);
    }
}
  

File 2 :

  
    public partial class InventoryService
{
    partial void OnInitialized()
    {
        Console.WriteLine("InventoryService ready.");
    }

    public partial event EventHandler StockUpdated
    {
        add
        {
            Console.WriteLine("StockUpdated handler added.");
            _stockUpdated += value;
        }
        remove
        {
            _stockUpdated -= value;
        }
    }

    private EventHandler _stockUpdated;
}
  

This pattern is clean, extensible, and generator-friendly.

6. Comparison with Older Approaches

Older Patterns

  • Base classes for extensibility

  • Virtual methods

  • Manual merge of generated code

  • Partial methods with limited usage

C# 14 Partial Events and Constructors

  • No inheritance required

  • Clear intent

  • Better tooling support

  • Safer code generation

7. Performance Considerations

Partial events and constructors:

  • Introduce no runtime overhead

  • Are resolved at compile time

  • Do not affect event invocation performance

Example:

  
    // Event invocation cost remains unchanged
StockUpdated?.Invoke(this, EventArgs.Empty);
  

The partial nature affects code organization, not runtime behavior.

8. Best Practices

  • Keep generated and manual code in separate files.

  • Use partial constructors for initialization hooks.

  • Use partial events for extensibility points.

  • Avoid complex logic in generated files.

  • Clearly document extension points.

9. Common Mistakes to Avoid

  • Adding business logic directly to generated files.

  • Overusing partial types when simpler designs suffice.

  • Mixing responsibilities across partial definitions.

  • Forgetting to provide default implementations when required.

Conclusion

Partial events and constructors in C# 14 significantly improve how developers structure extensible and generator-friendly code. They remove long-standing limitations and provide a clean, maintainable way to combine generated and hand-written logic.

For teams working with modern tooling, source generators, and large codebases, these features enable better separation of concerns and safer evolution of code over time.

Happy Coding!

I write about modern C#, .NET, and real-world development practices. Follow me on C# Corner for regular insights, tips, and deep dives.