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 parcial . En la práctica, cada clase parcial se define normalmente en un archivo independiente, lo que facilita la administración y expansión de la clase a lo largo del tiempo.
En el ejemplo siguiente Employee
se muestra cómo se puede dividir la clase entre dos archivos: Employee_Part1.cs y Employee_Part2.cs.
// This is in Employee_Part1.cs
public partial class Employee
{
public void DoWork()
{
}
}
// This is in Employee_Part2.cs
public partial class Employee
{
public void GoToLunch()
{
}
}
//Main program demonstrating the Employee class usage
public class Program
{
public static void Main()
{
Employee emp = new Employee();
emp.DoWork();
emp.GoToLunch();
}
}
// Expected Output:
// Employee is working.
// Employee is at lunch.
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. Sin embargo, si ambas declaraciones de un miembro parcial incluyen comentarios, solo se incluyen los comentarios del miembro de implementación.
- 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 claveclass
,struct
ointerface
. - 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 de una definición de tipo parcial son opcionales, pero si están presentes en una definición de tipo parcial, deben especificarse en otra definición parcial del 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 Coords
clase se declaran en una definición de clase parcial (Coords_Part1.cs
) y el PrintCoords
método se declara en otra definición de clase parcial (Coords_Part2.cs
). Esta separación muestra cómo las clases parciales se pueden dividir entre varios archivos para facilitar el mantenimiento.
// This is in Coords_Part1.cs
public partial class Coords
{
private int x;
private int y;
public Coords(int x, int y)
{
this.x = x;
this.y = y;
}
}
// This is in Coords_Part2.cs
public partial class Coords
{
public void PrintCoords()
{
Console.WriteLine("Coords: {0},{1}", x, y);
}
}
// Main program demonstrating the Coords class usage
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() { }
}
Miembros parciales
Una clase o estructura parcial puede contener un miembro parcial. Una parte de la clase contiene la firma del miembro. 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, incluidas las propiedades y los indizadores, debe proporcionar una implementación. Esa implementación la podría proporcionar un generador de código fuente. Las propiedades parciales no se pueden implementar mediante propiedades implementadas automáticamente. El compilador no puede distinguir entre una propiedad implementada automáticamente y la declaración declaratoria de una propiedad parcial.
Los métodos parciales permiten que el implementador de una parte de una clase declare un miembro. El implementador de otra parte de la clase puede definir ese miembro. 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 miembros. El desarrollador humano puede agregar la declaración de miembro (a menudo con atributos leídos por el generador de código fuente). El desarrollador puede escribir código que llame a estos miembros. 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 miembros parciales que pueden no implementarse.
// Definition in file1.cs
partial void OnNameChanged();
// Implementation in file2.cs
partial void OnNameChanged()
{
// method body
}
- Las declaraciones de miembro parcial deben comenzar con la palabra clave contextual partial.
- Las signaturas de miembros parciales de los dos elementos del tipo parcial deben coincidir.
- Los miembros parciales pueden tener modificadores static y unsafe.
- El miembro parcial puede ser genérico. 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.