Clases y métodos parciales (Guía de programación de C#)

Es posible dividir la definición de una clase, un struct, una interfaz o un método en dos o más archivos de código fuente. Cada archivo de código fuente contiene una sección de la definición de tipo o método, y todos los elementos se combinan cuando se compila la aplicación.

Clases parciales

Es recomendable dividir una definición de clase en varias situaciones:

  • El hecho de declarar una clase entre archivos independientes permite que varios programadores trabajen en ella al mismo tiempo.
  • Puedes agregar código a la clase sin tener que volver a crear el archivo de origen que incluye el origen generado automáticamente. Visual Studio usa este enfoque al crear formularios Windows Forms, código de contenedor de servicio Web, etc. Puede crear código que use estas clases sin necesidad de modificar el archivo creado por Visual Studio.
  • Los generadores de origen pueden ofrecer función adicional en una clase.

Para dividir una definición de clase, use el modificador de palabra clave partial, como se muestra aquí:

public partial class Employee
{
    public void DoWork()
    {
    }
}

public partial class Employee
{
    public void GoToLunch()
    {
    }
}

La palabra clave partial indica que se pueden definir en el espacio de nombres otros elementos de la clase, la estructura o la interfaz. Todos los elementos deben usar la palabra clave partial. Todos los elementos deben estar disponibles en tiempo de compilación para formar el tipo final. Todos los elementos deben tener la misma accesibilidad, como public, private, etc.

Si algún elemento se declara abstracto, todo el tipo se considera abstracto. Si algún elemento se declara sellado, todo el tipo se considera sellado. Si algún elemento declara un tipo base, todo el tipo hereda esa clase.

Todos los elementos que especifiquen una clase base deben coincidir, pero los elementos que omitan una clase base heredan igualmente el tipo base. Los elementos pueden especificar diferentes interfaces base, y el tipo final implementa todas las interfaces enumeradas por todas las declaraciones parciales. Todas las clases, structs o miembros de interfaz declarados en una definición parcial están disponibles para todos los demás elementos. El tipo final es la combinación de todos los elementos en tiempo de compilación.

Nota

El modificador partial no está disponible en declaraciones de delegado o enumeración.

En el ejemplo siguiente se muestra que los tipos anidados pueden ser parciales, incluso si el tipo en el que están anidados no es parcial.

class Container
{
    partial class Nested
    {
        void Test() { }
    }

    partial class Nested
    {
        void Test2() { }
    }
}

En tiempo de compilación, se combinan los atributos de definiciones de tipo parcial. Por ejemplo, consideremos las siguientes declaraciones:

[SerializableAttribute]
partial class Moon { }

[ObsoleteAttribute]
partial class Moon { }

Son equivalentes a las declaraciones siguientes:

[SerializableAttribute]
[ObsoleteAttribute]
class Moon { }

A continuación se indican los elementos que se combinan de todas las definiciones de tipo parcial:

  • comentarios XML
  • interfaces
  • atributos de parámetro de tipo genérico
  • class (atributos)
  • miembros

Por ejemplo, consideremos las siguientes declaraciones:

partial class Earth : Planet, IRotate { }
partial class Earth : IRevolve { }

Son equivalentes a las declaraciones siguientes:

class Earth : Planet, IRotate, IRevolve { }

Restricciones

Debes seguir varias reglas al trabajar con definiciones de clase parcial:

  • Todas las definiciones de tipo parcial que van a formar parte del mismo tipo deben modificarse con partial. Por ejemplo, las declaraciones de clase siguientes generan un error:
    public partial class A { }
    //public class A { }  // Error, must also be marked partial
    
  • El modificador partial solo puede aparecer inmediatamente antes de las palabras clave class, struct o interface.
  • Se permiten tipos parciales anidados en definiciones de tipo parcial, como se muestra en el ejemplo siguiente:
    partial class ClassWithNestedClass
    {
        partial class NestedClass { }
    }
    
    partial class ClassWithNestedClass
    {
        partial class NestedClass { }
    }
    
  • Todas las definiciones de tipo parcial que van a formar parte del mismo tipo deben definirse en el mismo ensamblado y en el mismo módulo (archivo .exe o .dll). Las definiciones parciales no pueden abarcar varios módulos.
  • El nombre de clase y los parámetros de tipo genérico deben coincidir en todas las definiciones de tipo parcial. Los tipos genéricos pueden ser parciales. Cada declaración parcial debe usar los mismos nombres de parámetro en el mismo orden.
  • Las siguientes palabras clave son opcionales en una definición de tipo parcial, pero si están presentes la definición, no pueden entrar en conflicto con las palabras clave especificadas en otra definición parcial para el mismo tipo:

Para obtener más información, vea Restricciones de tipos de parámetros.

Ejemplos

En el ejemplo siguiente, los campos y el constructor de la clase, Coords, se declaran en una definición de clase parcial y el miembro PrintCoords se declara en otra definición de clase parcial.

public partial class Coords
{
    private int x;
    private int y;

    public Coords(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
}

public partial class Coords
{
    public void PrintCoords()
    {
        Console.WriteLine("Coords: {0},{1}", x, y);
    }
}

class TestCoords
{
    static void Main()
    {
        Coords myCoords = new Coords(10, 15);
        myCoords.PrintCoords();

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
// Output: Coords: 10,15

En el ejemplo siguiente se muestra que también se pueden desarrollar structs e interfaces parciales.

partial interface ITest
{
    void Interface_Test();
}

partial interface ITest
{
    void Interface_Test2();
}

partial struct S1
{
    void Struct_Test() { }
}

partial struct S1
{
    void Struct_Test2() { }
}

Métodos Partial

Una clase o struct parcial puede contener un método parcial. Un elemento de la clase contiene la firma del método. Una implementación se puede definir en el mismo elemento o en otro.

No es necesaria una implementación para un método parcial cuando la firma cumple las reglas siguientes:

  • La declaración no incluye ningún modificador de acceso. El método tiene acceso a private de manera predeterminada.
  • El tipo devuelto es void.
  • Ninguno de los parámetros tiene el modificador out.
  • La declaración de método no puede incluir ninguno de los modificadores siguientes:

Si no se proporciona la implementación, el método y todas las llamadas al método se quitan en tiempo de compilación.

Cualquier método que no cumpla todas estas restricciones (por ejemplo, public virtual partial void) debe proporcionar una implementación. Esa implementación la podría proporcionar un generador de código fuente.

Los métodos parciales permiten que el implementador de una parte de una clase declare un método. El implementador de otra parte de la clase puede definir ese método. Hay dos escenarios en los que esta separación es útil: plantillas que generan código reutilizable y generadores de código fuente.

  • Código de plantilla: la plantilla reserva un nombre de método y una firma para que el código generado pueda llamar al método. Estos métodos siguen las restricciones que permiten a un desarrollador decidir si implementar el método. Si el método no se implementa, el compilador quita la firma del método y todas las llamadas al método. Las llamadas al método, incluidos los resultados que se producirían por la evaluación de los argumentos de las llamadas, no tienen efecto en tiempo de ejecución. Por lo tanto, el código de la clase parcial puede usar libremente un método parcial, incluso si no se proporciona la implementación. No se produce ningún error en tiempo de compilación o en tiempo de ejecución si se llama al método pero no se implementa.
  • Generadores de código fuente: los generadores de código fuente proporcionan una implementación para los métodos. El desarrollador humano puede agregar la declaración de método (a menudo con atributos leídos por el generador de código fuente). El desarrollador puede escribir código que llame a estos métodos. El generador de código fuente se ejecuta durante la compilación y proporciona la implementación. En este escenario, no se suelen seguir las restricciones de los métodos parciales que pueden no implementarse.
// Definition in file1.cs
partial void OnNameChanged();

// Implementation in file2.cs
partial void OnNameChanged()
{
  // method body
}
  • Las declaraciones de método parcial deben comenzar con la palabra clave contextual partial.
  • Las signaturas de métodos parciales de los dos elementos del tipo parcial deben coincidir.
  • Los métodos parciales pueden tener modificadores static y unsafe.
  • Los métodos parciales pueden ser genéricos. Las restricciones deben ser las mismas en la declaración de método de la definición e implementación. Los nombres del parámetro y del parámetro de tipo no tienen que ser iguales en la declaración de implementación y en la declaración de definición.
  • Puedes crear un delegado para un método parcial definido e implementado, pero no para un método parcial que no tiene una implementación.

Especificación del lenguaje C#

Para obtener más información, vea la sección Tipos parciales y Métodos parciales de la Especificación del lenguaje C#. La especificación del lenguaje es la fuente definitiva de la sintaxis y el uso de C#. Las características adicionales para los métodos parciales se definen en la especificación de características.

Consulte también