Start Debugging
2025-04-08 Actualizado 2025-04-12 csharp-14csharpdotnetdotnet-10 Edit on GitHub

Constructores y eventos parciales en C# 14

C# 14 te permite declarar constructores de instancia y eventos como miembros parciales, dividiendo las definiciones entre archivos para una generación de código más limpia y una mejor separación de responsabilidades.

C# 14 introduce una nueva capacidad para declarar constructores de instancia y eventos como miembros partial. Esto significa que puedes dividir la definición de un constructor o evento en dos partes de una clase parcial, de forma similar a como C# permite desde hace tiempo los métodos parciales y las propiedades parciales. Una parte de la clase parcial puede declarar un constructor o evento, y otra parte puede implementarlo. Esto es especialmente útil en escenarios como la generación de código, donde un archivo puede ser autogenerado y otro mantenido manualmente por un desarrollador.

Constructores parciales en C# 14

Los constructores parciales te permiten dividir el constructor de una clase en dos declaraciones dentro de una clase partial. Una declaración aporta solo la firma (una declaración definitoria) y otra aporta el cuerpo real (una declaración implementadora). El compilador las fusiona para que se comporten como un único constructor en tiempo de ejecución.

Reglas clave para los constructores parciales:

A continuación hay un ejemplo de un constructor parcial en acción. Imagina una clase repartida en dos archivos: una parte autogenerada declara un constructor y otra parte lo implementa:

// 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}");
    }
}

En este ejemplo, el constructor de la clase Car está dividido: la primera parte declara que existe Car(string model) y la segunda parte aporta la implementación que valida el argumento e imprime un mensaje. La declaración definitoria no puede incluir código; es solo la firma terminada en ;. La declaración implementadora tiene el cuerpo y también podría incluir una llamada a la clase base (si Car heredase de otra clase) o llamar a otro constructor mediante this(). En tiempo de compilación, el compilador se asegura de que haya exactamente una de cada parte y luego trata el constructor como un único constructor unificado.

Eventos parciales en C# 14

Los eventos parciales permiten una división similar en la definición de eventos dentro de una clase parcial. Una parte de la clase define el evento (sin lógica de add/remove, igual que una declaración de evento automática normal) y otra parte aporta los accesores add/remove (el código que se ejecuta cuando los suscriptores adjuntan o eliminan manejadores de eventos).

Reglas clave para los eventos parciales:

Aquí tienes un ejemplo que demuestra un evento parcial. Un archivo declara el evento y otro archivo implementa la lógica personalizada de add/remove:

// 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);
    }
}

En este ejemplo, la clase Sensor tiene un evento DataReceived repartido entre dos archivos. La parte autogenerada de la clase declara que existe un evento DataReceived de tipo EventHandler. La parte del desarrollador aporta la implementación real: define un campo privado para guardar los suscriptores e implementa los bloques add y remove para registrar un mensaje y actualizar ese campo privado. Cuando otro código hace sensor.DataReceived += Handler;, se ejecuta la lógica personalizada de add. De igual forma, darse de baja activa la lógica personalizada de remove. La clase puede usar _dataReceivedHandlers para disparar el evento (por ejemplo, en el método OnDataReceived). Sin la parte implementadora, la declaración definitoria por sí sola no compilaría, ya que el evento no tiene respaldo por sí mismo: C# requiere que la parte implementadora complete la funcionalidad del evento.

Comparación con versiones anteriores de C#

Antes de C# 14, no podías declarar constructores ni eventos como parciales. En C# 13 y versiones anteriores, la palabra clave partial solo era válida en clases, métodos y (desde C# 13) propiedades/indexadores. Si intentabas marcar un constructor o un evento como parcial, el compilador producía un error. Esta limitación significaba que no había forma de dividir la implementación de un constructor o un evento entre distintos archivos.

Los desarrolladores solían recurrir a soluciones alternativas para lograr una flexibilidad similar. Por ejemplo, considera un escenario en el que el código autogenerado necesita llamar a alguna lógica personalizada durante la construcción del objeto. Sin constructores parciales, un patrón común era usar un método parcial dentro del constructor. La parte autogenerada de la clase podía llamar a este método, mientras que la implementación del método estaba en otro archivo. Por ejemplo, en código C# antiguo podrías ver:

// 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();
}

En el segundo archivo, el desarrollador podía implementar el método parcial OnConstructed para ejecutar código adicional tras InitializeComponents(). Este patrón permitía inyectar código personalizado en el momento de la construcción, pero era indirecto y un poco torpe. De forma similar, para los eventos no había una solución alternativa sencilla: había que heredar y sobrescribir la funcionalidad o modificar el código generado, ya que no se podía dividir la lógica add/remove de un evento entre archivos.

Con los constructores y eventos parciales de C# 14, estas soluciones alternativas ya no son necesarias. Puedes declarar directamente un constructor o evento como parcial y proporcionar la implementación por separado, lo que hace el código más directo. El nuevo enfoque es más claro y elimina la necesidad de métodos parciales ficticios u otros trucos.

Casos de uso y beneficios

Los principales beneficiarios de los constructores y eventos parciales son los escenarios de generación de código y herramientas. Muchos frameworks y herramientas generan código C# (por ejemplo, diseñadores de UI, ORMs o herramientas de andamiaje de interfaces) y a menudo marcan las clases como parciales para que los desarrolladores puedan extender las clases generadas sin editar el código autogenerado. Con C# 14:

En todos estos casos, los constructores y eventos parciales hacen que el código sea más fácil de mantener. El código autogenerado puede declarar lo que debe existir, y el código escrito a mano puede limitarse a lo que el desarrollador realmente quiere implementar. Tampoco hay coste de rendimiento en tiempo de ejecución por usar miembros parciales: la separación existe únicamente en tiempo de compilación. El programa resultante se comporta como si hubieras escrito el constructor o el evento completos en un solo lugar.

Al permitir que los constructores de instancia y los eventos sean partial, C# 14 redondea el conjunto de características de clases parciales para cubrir casi todos los tipos de miembros. Esta mejora aumenta la capacidad del lenguaje para soportar una separación limpia entre código autogenerado y código personalizado. Los desarrolladores obtienen flexibilidad para organizar y extender el comportamiento de las clases sin trucos, y los generadores de código fuente ganan una nueva herramienta poderosa para inyectar funcionalidad de forma modular. Tanto si trabajas en frameworks grandes como si simplemente divides el código por claridad, los constructores y eventos parciales ofrecen un enfoque más expresivo y conveniente en C# 14.

Fuentes

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Volver