Si estamos escribiendo clases utilizando un generador de código, muchas veces se nos presenta la necesidad de de modificar algunos aspectos de las clases generadas. Si bien es posible modificar el código generado, dicha práctica no es recomendable, ya que cualquier cambio que realicemos sería reemplazado en el momento en que regeneremos el código.
Es ahí donde entra el uso del keyword partial. Cuando definimos una clase como partial (e.g. partial class Test), estamos indicando al compilador de C#, que la definición de la misma se encuentra en múltiples archivos.
Esto nos permite generar el código de nuestra clase en un archivo (e.g. Test.g.cs) y permitir al usuario agregar métodos, propiedades o campos en otro archivo (e.g. Test.cs). De esta manera, cuando el generador cambie, o cuando cambiemos algún parámetro de generación, la parte que fue personalizada por el usuario de nuestro generador permanece incambiada.
Veamos un ejemplo sencillo. Generaremos clases con una propiedad con el mismo nombre de la clase y el sufijo ‘Member’ usando T4 (Text Template Transformation Toolkit) disponible en Visual Studio 2008
[Test.g.tt:]
<#@ template language="C#" #>
<#
string[] names = { "Class1", "Class2" };
foreach(string name in names) {
#>
public partial class <#=name#>
{
public int <#=name#>Member { get;set; }
}
<#
}
#>
Si todo fue bien, debajo de Test.g.tt deberíamos tener un archivo llamado Test.g.cs.
[Test.g.cs:]
public partial class Class1
{
public int Class1Member { get;set; }
}
public partial class Class2
{
public int Class2Member { get;set; }
}
Ahora supongamos que queremos que la clase Class1 tenga un miembro llamado NewMember. Podemos crear un archivo llamado Class1.cs.
[Class1.cs:]
partial class Class1
{
public string NewMember { get;set; }
}
Con esto, Class1 tiene ahora dos miembros y Class2 tiene uno solo. Si cambiamos Test.g.tt, el archivo Test.g.cs será reemplazado, pero nuestro código existente en Class1.cs no se verá afectado.
domingo, 15 de mayo de 2011
Partial Methods en C#
En el post anterior, vimos como habilitar la modificación de la estructura de una clase generada (o interfaz, o estructura) utilizando el keyword partial. De esta manera, podemos agregar nuevos métodos, campos, propiedades y eventos a una clase definida en otros archivos. Siguiendo con el tema, voy a presentar otro uso, que nos permite definir métodos opcionales.
Los partial methods funcionan en forma similar a los events, o a los delegates, pero sin tener costo en tiempo de ejecución en caso de no recibir una implementación, ya que el compilador elimina todas las referencias existentes al método en caso de no recibir una implementación.
Un partial method consta de dos partes: la definición, donde se indica el nombre y los parámetros que recibe el método (siempre retorna void); y la implementación, donde se repiten los mismos datos, pero se incluye un cuerpo que implementa el método.
Veamos un ejemplo. Supongamos que tenemos un generador que produce el siguiente código:
partial class Person
{
string name;
string surname;
public string Name
{
get { return name; }
set { name = value; }
}
public string Surname
{
get { return surname; }
set { surname = value; }
}
}
Con esta clase así generada, es difícil permitir a nuestros usuarios saber cuando cambió el nombre o el apellido de nuestra persona. Una posibilidad que tenemos es la de cambiar el generador para que agregue un evento por cada propiedad:
partial class Person
{
string name;
string surname;
public event Action<string> NameChanged = delegate {};
public event Action<string> SurnameChanged = delegate {};
public string Name
{
get { return name; }
set { name = value; NameChanged(name);}
}
public string Surname
{
get { return surname; }
set { surname = value; SurnameChanged(name); }
}
}
Esta versión es correcta, pero en el caso en que no requiramos monitorear todas las propiedades, hemos agregado eventos en forma innecesaria. Además, es una solución demasiado flexible, ya que puede haber múltiples suscriptores al evento, lo cual puede ser un problema.
La alternativa con partial methods sería esta:
partial class Person
{
string name;
string surname;
partial void NameChanged(string name);
partial void SurnameChanged(string surname);
public string Name
{
get { return name; }
set { name = value; NameChanged(name); }
}
public string Surname
{
get { return surname; }
set { surname = value; SurnameChanged(name); }
}
}
Si queremos saber cuando cambió la propiedad Name, podemos definir en otro archivo:
partial class Person
{
partial void NameChanged(string name)
{
Console.WriteLine("Name changed: '{0}'", name);
}
}
En este caso, la implementación de Person, tiene dos propiedades, Name y Surname, y únicamente un método, NameChanged. Cómo no definimos una implementación para SurnameChanged, el compilador omite todas las llamadas al método y lo elimina de la clase.
La generación de código es algo muy útil, que nos permite automatizar varias tareas mundanas, lo que mejora nuestra productividad. El uso de partial classes y partial methods nos permite mejorar las posibilidades de reúso de nuestros generadores.
Los partial methods funcionan en forma similar a los events, o a los delegates, pero sin tener costo en tiempo de ejecución en caso de no recibir una implementación, ya que el compilador elimina todas las referencias existentes al método en caso de no recibir una implementación.
Un partial method consta de dos partes: la definición, donde se indica el nombre y los parámetros que recibe el método (siempre retorna void); y la implementación, donde se repiten los mismos datos, pero se incluye un cuerpo que implementa el método.
Veamos un ejemplo. Supongamos que tenemos un generador que produce el siguiente código:
partial class Person
{
string name;
string surname;
public string Name
{
get { return name; }
set { name = value; }
}
public string Surname
{
get { return surname; }
set { surname = value; }
}
}
Con esta clase así generada, es difícil permitir a nuestros usuarios saber cuando cambió el nombre o el apellido de nuestra persona. Una posibilidad que tenemos es la de cambiar el generador para que agregue un evento por cada propiedad:
partial class Person
{
string name;
string surname;
public event Action<string> NameChanged = delegate {};
public event Action<string> SurnameChanged = delegate {};
public string Name
{
get { return name; }
set { name = value; NameChanged(name);}
}
public string Surname
{
get { return surname; }
set { surname = value; SurnameChanged(name); }
}
}
Esta versión es correcta, pero en el caso en que no requiramos monitorear todas las propiedades, hemos agregado eventos en forma innecesaria. Además, es una solución demasiado flexible, ya que puede haber múltiples suscriptores al evento, lo cual puede ser un problema.
La alternativa con partial methods sería esta:
partial class Person
{
string name;
string surname;
partial void NameChanged(string name);
partial void SurnameChanged(string surname);
public string Name
{
get { return name; }
set { name = value; NameChanged(name); }
}
public string Surname
{
get { return surname; }
set { surname = value; SurnameChanged(name); }
}
}
Si queremos saber cuando cambió la propiedad Name, podemos definir en otro archivo:
partial class Person
{
partial void NameChanged(string name)
{
Console.WriteLine("Name changed: '{0}'", name);
}
}
En este caso, la implementación de Person, tiene dos propiedades, Name y Surname, y únicamente un método, NameChanged. Cómo no definimos una implementación para SurnameChanged, el compilador omite todas las llamadas al método y lo elimina de la clase.
La generación de código es algo muy útil, que nos permite automatizar varias tareas mundanas, lo que mejora nuestra productividad. El uso de partial classes y partial methods nos permite mejorar las posibilidades de reúso de nuestros generadores.
Difference between Abstract Class & Interfase
Las clases abstractas pueden tener o no implementaciones de los métodos miembros de la clase, y las interfaces no tienen ninguna implementación solo las declaraciones de los miembros que deben constituir la clase que implemente dicha interfaz.
Los miembros de la interfaz deben ser públicos y sin implementación.
interface iMiInterfaz
{
int Sumar(int Num1, int Num2);
int Multiplicar(int Num1, int Num2);
}
Las clases abstractas pueden tener miembros abstractos y no abstractos (es decir sobrecargables en las clases que hereden de ella). pero en las interfaces todos los miembros son implicitamente abstractos, y todos los miembros de la interfaz deben ser reimplementados en la clase que derive de esta.
abstract class MiClaseAbstracta
{
public int Suma(int Num1, int Num2) { return Num1 + Num2; }
public abstract int Multiplicacion(int Num1, int Num2);
}
Los miembros de la interfaz deben ser públicos y sin implementación.
interface iMiInterfaz
{
int Sumar(int Num1, int Num2);
int Multiplicar(int Num1, int Num2);
}
Las clases abstractas pueden tener miembros abstractos y no abstractos (es decir sobrecargables en las clases que hereden de ella). pero en las interfaces todos los miembros son implicitamente abstractos, y todos los miembros de la interfaz deben ser reimplementados en la clase que derive de esta.
abstract class MiClaseAbstracta
{
public int Suma(int Num1, int Num2) { return Num1 + Num2; }
public abstract int Multiplicacion(int Num1, int Num2);
}
jueves, 12 de mayo de 2011
Suscribirse a:
Entradas (Atom)