Compartir a través de


Extender métodos 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 lenguaje (LDM) correspondientes.

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 especificaciones.

Problema del campeón: https://github.com/dotnet/csharplang/issues/3301

Resumen

Esta propuesta pretende eliminar todas las restricciones alrededor de métodos de firma partial en C#. El objetivo es ampliar el conjunto de escenarios en los que estos métodos pueden funcionar con los generadores de código fuente, además de ser una forma de declaración más general para los métodos de C#.

Consulte también la especificación original de los métodos parciales (sección 15.6.9).

Motivación

C# tiene un soporte limitado para los desarrolladores que dividen los métodos en declaraciones y definiciones / implementaciones.

partial class C
{
    // The declaration of C.M
    partial void M(string message);
}

partial class C
{
    // The definition of C.M
    partial void M(string message) => Console.WriteLine(message);
}

Un comportamiento de los métodos partial es que cuando la definición está ausente entonces el lenguaje simplemente borrará cualquier llamada al método partial. Esencialmente se comporta como una llamada a un método [Conditional] donde la condición fue evaluada a false.

partial class D
{
    partial void M(string message);

    void Example()
    {
        M(GetIt()); // Call to M and GetIt erased at compile time
    }

    string GetIt() => "Hello World";
}

La motivación original para esta característica fue la generación de código fuente en forma de código generado por el diseñador. Los usuarios estaban constantemente editando el código generado porque querían enganchar algún aspecto del código generado. En particular, las partes del proceso de inicio de Windows Forms, después de inicializar los componentes.

Editar el código generado era propenso a errores porque cualquier acción que causara que el diseñador regenerara el código causaría que la edición del usuario fuera borrada. La característica del método partial alivió esta tensión porque permitió a los diseñadores emitir enlaces en forma de métodos partial.

Los diseñadores podrían emitir enlaces como partial void OnComponentInit() y los desarrolladores podrían definir declaraciones para ellos o no definirlos. En cualquier caso, el código generado se compilaría y los desarrolladores que estuvieran interesados en el proceso podrían participar según fuera necesario.

Esto significa que los métodos parciales tienen varias restricciones:

  1. Debe tener un tipo de valor devuelto void.
  2. No puede tener parámetros out.
  3. No se puede tener ninguna accesibilidad (implícitamente private).

Estas restricciones existen porque el lenguaje debe ser capaz de emitir código cuando el sitio de llamada es borrado. Dado que se pueden borrar, private es la única accesibilidad posible porque el miembro no puede exponerse en los metadatos del ensamblado. Estas restricciones también sirven para limitar el conjunto de escenarios en los que se pueden aplicar los métodos partial.

La propuesta aquí es quitar todas las restricciones existentes sobre los métodos partial. Básicamente, deje que dispongan de parámetros out, tipos de devolución no vacíos o cualquier tipo de accesibilidad. Tales declaraciones partial tendrían entonces el requisito añadido de que debe existir una definición. Eso significa que el lenguaje no tiene que considerar el impacto de borrar los sitios de llamada.

Esto expandiría el conjunto de escenarios de generador en los que los métodos "partial" podrían participar y, por tanto, vincularse perfectamente con nuestra funcionalidad de generadores de código. Por ejemplo, se podría definir una expresión regular con el siguiente patrón:

[RegexGenerated("(dog|cat|fish)")]
partial bool IsPetMatch(string input);

Esto proporciona al desarrollador una forma declarativa sencilla de optar por usar generadores, además de ofrecer a los generadores un conjunto muy fácil de declaraciones para revisar en el código fuente y dirigir su salida generada.

Compárese con la dificultad que tendría un generador para conectar el siguiente fragmento de código.

var regex = new RegularExpression("(dog|cat|fish)");
if (regex.IsMatch(someInput))
{

}

Dado que el compilador no permite que los generadores modifiquen el enlace de este patrón, sería prácticamente imposible para ellos implementarlo. Necesitarían recurrir al reflejo en la implementación de IsMatch o pedir a los usuarios que modifiquen sus puntos de invocación a un nuevo método y refactoricen la expresión regular para pasar el literal de cadena como argumento. Es bastante caótico.

Diseño detallado

El lenguaje cambiará para permitir que los métodos partial sean anotados con un modificador de accesibilidad explícito. Esto significa que pueden ser etiquetados como private, public, etc.

Cuando un método partial tenga un modificador de accesibilidad explícito, el lenguaje requerirá que la declaración tenga una definición coincidente, incluso cuando la accesibilidad sea private:

partial class C
{
    // Okay because no definition is required here
    partial void M1();

    // Okay because M2 has a definition
    private partial void M2();

    // Error: partial method M3 must have a definition
    private partial void M3();
}

partial class C
{
    private partial void M2() { }
}

Además, el lenguaje eliminará todas las restricciones sobre lo que puede aparecer en un método partial que tiene una accesibilidad explícita. Tales declaraciones pueden contener tipos de retorno no vacíos, parámetros out, modificadores extern, etc. Estas firmas tendrán toda la expresividad del lenguaje C#.

partial class D
{
    // Okay
    internal partial bool TryParse(string s, out int i); 
}

partial class D
{
    internal partial bool TryParse(string s, out int i) { ... }
}

Esto permite explícitamente que los métodos partial participen en implementaciones overrides y interface:

interface IStudent
{
    string GetName();
}

partial class C : IStudent
{
    public virtual partial string GetName(); 
}

partial class C
{
    public virtual partial string GetName() => "Jarde";
}

El compilador cambiará el error que emite cuando un método partial contiene un elemento ilegal para decir esencialmente:

No se puede utilizar ref en un método partial que carece de accesibilidad explícita.

Esto ayudará a los desarrolladores en la dirección correcta cuando se utiliza esta característica.

Restricciones:

  • Las declaraciones partial con accesibilidad explícita deben tener una definición
  • Las declaraciones partial y las firmas de definición deben coincidir en todos los modificadores de método y parámetro. Los únicos aspectos que pueden diferir son los nombres de los parámetros y las listas de atributos (esto no es nuevo, sino un requisito ya existente de los métodos partial).

Preguntas

parcial para todos los miembros

Dado que estamos expandiendo partial para ser más amigable con los generadores de código fuente, ¿también deberíamos expandirlo para que funcione con todos los miembros de la clase? Por ejemplo, deberíamos poder declarar constructores, operadores, etc. partial.

Resolución La idea es sólida, pero en este punto del calendario de C# 9 estamos intentando evitar el crecimiento innecesario de características. Querer solucionar el problema inmediato de expandir la funcionalidad para que funcione con generadores de código modernos.

La extensión de partial para admitir otros miembros se considerará para la versión de C# 10. Parece probable que consideremos esta ampliación. Sigue siendo una propuesta activa, pero aún no se ha puesto en práctica.

Uso de abstracto en lugar de parcial

El quid de esta propuesta es esencialmente asegurar que una declaración tiene una definición / implementación correspondiente. Dado que deberíamos utilizar abstract puesto que ya es una palabra clave del lenguaje que obliga al desarrollador a pensar en tener una implementación?

Resolución Hubo un debate constructivo sobre esto, pero finalmente se decidió en contra. Sí, los requisitos son familiares, pero los conceptos son significativamente diferentes. Podría llevar fácilmente al desarrollador a creer que estaba creando ranuras virtuales cuando no lo estaba haciendo.