Compartir a través de


Eventos y constructores parciales

Nota:

Este artículo es una especificación de características. La especificación actúa como documento de diseño de la característica. Incluye cambios de especificación propuestos, junto con la información necesaria durante el diseño y el desarrollo de la característica. Estos artículos se publican hasta que se finalizan los cambios de especificación propuestos y se incorporan en la especificación ECMA actual.

Puede haber algunas discrepancias entre la especificación de características y la implementación completada. Esas diferencias se recogen en las notas de la reunión de diseño de idioma (LDM) pertinentes.

Puede obtener más información sobre el proceso de adopción de especificaciones de características en el estándar del lenguaje C# en el artículo sobre las especificaciones.

Problema planteado por el experto: https://github.com/dotnet/csharplang/issues/9058

Resumen

Permitir que el partial modificador en eventos y constructores separe las partes de declaración e implementación, de forma similar a métodos parciales y propiedades o indexadores parciales.

partial class C
{
    partial C(int x, string y);
    partial event Action<int, string> MyEvent;
}

partial class C
{
    partial C(int x, string y) { }
    partial event Action<int, string> MyEvent
    {
        add { }
        remove { }
    }
}

Motivación

C# ya admite métodos, propiedades e indizadores parciales. Faltan eventos parciales y constructores.

Los eventos parciales serían útiles para las bibliotecas de eventos débiles, donde el usuario podría escribir definiciones:

partial class C
{
    [WeakEvent]
    partial event Action<int, string> MyEvent;

    void M()
    {
        RaiseMyEvent(0, "a");
    }
}

Y un generador de origen proporcionado por la biblioteca proporcionaría implementaciones:

partial class C
{
    private readonly WeakEvent _myEvent;

    partial event Action<int, string> MyEvent
    {
        add { _myEvent.Add(value); }
        remove { _myEvent.Remove(value); }
    }

    protected void RaiseMyEvent(int x, string y)
    {
        _myEvent.Invoke(x, y);
    }
}

Los eventos parciales y los constructores parciales también serían útiles para generar código de interoperabilidad como en Xamarin , donde el usuario podría escribir definiciones parciales de constructores y eventos:

partial class AVAudioCompressedBuffer : AVAudioBuffer
{
    [Export("initWithFormat:packetCapacity:")]
    public partial AVAudioCompressedBuffer(AVAudioFormat format, uint packetCapacity);

    [Export("create:")]
    public partial event EventHandler Created;
}

Y el generador de origen generaría los enlaces (en Objective-C en este caso):

partial class AVAudioCompressedBuffer : AVAudioBuffer
{
    [BindingImpl(BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
    public partial AVAudioCompressedBuffer(AVAudioFormat format, uint packetCapacity) : base(NSObjectFlag.Empty)
    {
        // Call Objective-C runtime:
        InitializeHandle(
            global::ObjCRuntime.NativeHandle_objc_msgSendSuper_NativeHandle_UInt32(
                this.SuperHandle,
                Selector.GetHandle("initWithFormat:packetCapacity:"),
                format.GetNonNullHandle(nameof(format)),
                packetCapacity),
            "initWithFormat:packetCapacity:");
    }

    public partial event EventHandler Created
    {
        add { /* ... */ }
        remove { /* ... */ }
    }
}

Diseño detallado

General

La sintaxis de declaración de eventos (§15.8.1) se extiende para permitir el partial modificador:

 event_declaration
-    : attributes? event_modifier* 'event' type variable_declarators ';'
+    : attributes? event_modifier* 'partial'? 'event' type variable_declarators ';'
-    | attributes? event_modifier* 'event' type member_name
+    | attributes? event_modifier* 'partial'? 'event' type member_name
         '{' event_accessor_declarations '}'
     ;

La sintaxis de declaración del constructor de instancia (§15.11.1) se extiende para permitir el partial modificador:

 constructor_declaration
-    : attributes? constructor_modifier* constructor_declarator constructor_body
+    : attributes? constructor_modifier* 'partial'? constructor_declarator constructor_body
     ;

Tenga en cuenta que hay una propuesta para permitir el partial modificador en cualquier lugar entre modificadores, en lugar de solo como último (también para declaraciones de método, propiedad y tipo).

Se dice que una declaración de evento con el partial modificador es una declaración de evento parcial y está asociada a uno o varios eventos parciales con los nombres especificados (tenga en cuenta que una declaración de evento sin descriptores de acceso puede definir varios eventos).

Se dice que una declaración de constructor con el partial modificador es una declaración de constructor parcial y está asociada a un constructor parcial con la firma especificada.

Se dice que una declaración de evento parcial es una declaración de implementación cuando especifica o event_accessor_declarations tiene el extern modificador . De lo contrario, es una declaración de definición.

Se dice que una declaración de constructor parcial es una declaración de definición cuando tiene un cuerpo de punto y coma y carece del extern modificador. En caso contrario, se trata de una declaración de implementación.

partial class C
{
    // defining declarations
    partial C();
    partial C(int x);
    partial event Action E, F;

    // implementing declarations
    partial C() { }
    partial C(int x) { }
    partial event Action E { add { } remove { } }
    partial event Action F { add { } remove { } }
}

Solo la declaración de definición de un miembro parcial participa en la búsqueda y se considera en sitios de uso y para emitir los metadatos. (Excepto para los comentarios de documentación como se detalla a continuación). La firma de declaración de implementación se usa para el análisis que acepta valores NULL de los cuerpos asociados.

Un evento o constructor parcial:

  • Solo se puede declarar como miembro de un tipo parcial.
  • Debe tener una declaración de definición y otra de implementación.
  • No se permite tener el abstract modificador .
  • No se puede implementar explícitamente un miembro de interfaz.

Un evento parcial no es similar a un campo (§15.8.2), es decir:

  • No tiene ningún almacenamiento de respaldo ni descriptores de acceso generados por el compilador.
  • Solo se puede usar en += operaciones y -= , no como un valor.

Una declaración de constructor parcial que define no puede tener un inicializador de constructor (: this() o : base(); §15.11.2).

Interrupción de análisis

Permitir el partial modificador en más contextos es un cambio importante:

class C
{
    partial F() => new partial(); // previously a method, now a constructor
    @partial F() => new partial(); // workaround to keep it a method
}

class partial { }

Para simplificar el analizador de lenguaje, el partial modificador se acepta en cualquier declaración similar a método (es decir, funciones locales y métodos de script de nivel superior), aunque no se especifiquen los cambios gramaticales explícitamente anteriores.

Atributos

Los atributos del evento o constructor resultantes son los atributos combinados de las declaraciones parciales en las posiciones correspondientes. Los atributos combinados se concatenan en un orden no especificado y no se quitan los duplicados.

El methodattribute_target (§22.3) se omite en declaraciones de eventos parciales. Los atributos de descriptor de acceso solo se usan desde declaraciones de descriptor de acceso (que solo pueden estar presentes en la declaración de implementación). Tenga en cuenta que param y returnattribute_targets ya se omiten en todas las declaraciones de eventos.

El compilador omite los atributos de información del autor de la llamada en la declaración de implementación según lo especificado por la propuesta de propiedades parciales de la sección Atributos de información del autor de la llamada (tenga en cuenta que se aplica a todos los miembros parciales que incluyen eventos y constructores parciales).

Firmas

Ambas declaraciones de un miembro parcial deben tener firmas coincidentes similares a las propiedades parciales:

  1. Las diferencias de tipo y tipo ref entre declaraciones parciales que son significativas para el tiempo de ejecución dan lugar a un error en tiempo de compilación.
  2. Las diferencias en los nombres de elementos de tupla entre declaraciones parciales producen un error en tiempo de compilación.
  3. Las declaraciones deben tener los mismos modificadores, aunque los modificadores pueden aparecer en un orden diferente.
    • Excepción: esto no se aplica al extern modificador que solo puede aparecer en la declaración de implementación.
  4. Todas las demás diferencias sintácticas en las firmas de declaraciones parciales producen una advertencia en tiempo de compilación, con las siguientes excepciones:
    • No es necesario que las listas de atributos coincidan como se ha descrito anteriormente.
    • Las diferencias de contexto que aceptan valores NULL (por ejemplo, oblicuas frente a anotadas) no provocan advertencias.
    • Los valores de parámetro predeterminados no necesitan coincidir, pero se notifica una advertencia cuando la declaración del constructor de implementación tiene valores de parámetro predeterminados (ya que solo la declaración de definición participa en la búsqueda).
  5. Se produce una advertencia cuando los nombres de parámetro difieren en la definición e implementación de declaraciones de constructor.
  6. Las diferencias de nulabilidad que no implican nulabilidad omitosa dan lugar a advertencias.

Comentarios de documentación

Se permite incluir comentarios de documento en la declaración de definición e implementación. Tenga en cuenta que los comentarios del documento no se admiten en los descriptores de acceso de eventos.

Cuando los comentarios de documento están presentes solo en una de las declaraciones de un miembro parcial, esos comentarios de documento se usan normalmente (que se muestran a través de las API de Roslyn, emitidas en el archivo XML de documentación).

Cuando los comentarios del documento están presentes en ambas declaraciones de un miembro parcial, se quitan todos los comentarios del documento sobre la declaración de definición y solo se usan los comentarios del documento sobre la declaración de implementación.

Cuando los nombres de parámetro difieren entre las declaraciones de un miembro parcial, paramref los elementos usan los nombres de parámetro de la declaración asociada con el comentario de documentación en el código fuente. Por ejemplo, un paramref en un comentario de documento colocado en una declaración de implementación hace referencia a los símbolos de parámetro de la declaración de implementación mediante sus nombres de parámetro. Esto puede resultar confuso, ya que la firma de metadatos usará nombres de parámetro de la declaración de definición. Se recomienda asegurarse de que los nombres de parámetro coincidan entre las declaraciones de un miembro parcial para evitar esta confusión.

Preguntas abiertas

Tipos de miembros

¿Queremos eventos parciales, constructores, operadores, campos? Propondremos los dos primeros tipos de miembro, pero se podría considerar cualquier otro subconjunto.

También se pueden considerar constructores primarios parciales, por ejemplo, permitir que el usuario tenga la misma lista de parámetros en varias declaraciones de tipos parciales.

Ubicaciones de atributos

¿Se debe reconocer el especificador de destino de [method:] atributo para eventos parciales (o simplemente las declaraciones de definición)? A continuación, los atributos de descriptor de acceso resultantes serían la concatenación de methodatributos -targeting de los elementos de declaración (o simplemente de definición) más los atributos de autoestinación y method-targeting de los descriptores de acceso de la declaración de implementación. La combinación de atributos de diferentes tipos de declaración sería sin precedentes y, de hecho, la implementación actual de la coincidencia de atributos en Roslyn no admite eso.

También podemos considerar la posibilidad de reconocer [param:] y [return:], no solo en eventos parciales, sino en todos los eventos de tipo campo y extern. Para obtener más información, vea https://github.com/dotnet/roslyn/issues/77254.