La clase como elemento fundamental de la programacin orientada a objetos requiere toda la atencin posible por parte del programador. El correcto manejo de sus elementos y el buen aprovechamiento de los mismos es lo que permitir sacar el mximo provecho de esta metodologa de programacin, y sobretodo hacer ms fcil y efectiva la tarea de programar una aplicacin de software. La teora general de la programacin orientada a objetos define unos elementos bsicos que conforman una clase, pero cada uno de los lenguajes de programacin ha realizado sus propios aportes a estos elementos, especialmente ampliando su funcionalidad o representndolos mediante elementos propios del lenguaje, con el objetivo de volverlos ms potentes y fciles de manejar. El lenguaje C# ha dotado a las clases de una serie de elementos que en apariencia amplan el conjunto de elementos definidos en la teora general, pero ms que eso, en realidad lo que se busca es poner a disposicin del programador toda una gama de recursos que le permitan construir componentes de software que cumplan todos los requerimientos de la programacin orientada a objetos y permitan expresar los elementos generales en la forma ms efectiva y eficiente posible. Estos son los elementos bsicos que constituyen una clase en C#:
Constructores Un constructor es un mtodo de una clase que se ejecuta automticamente cada vez que se crea una instancia de la misma. Aunque no se especifique, como ha sucedido en todas las clases que hasta ahora hemos implementado, el compilador de C# siempre establece internamente un mtodo constructor que no hace absolutamente nada. Adems, siempre que vamos a crear un objeto definido por una clase, hacemos un llamado a su constructor en el momento que creamos una nueva instancia con el operador new. Por ejemplo, retomando nuestra clase de los nmeros complejos, definida en el anterior captulo, en las siguientes dos lneas de cdigo, la primera lnea define un objeto de tipo Complejo y la segunda se encarga de llamar al constructor de esa clase.
CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 PROGRAMACION CON C# www.pedrov.phpnet.us 86 86 86 86 Complejo z; z = new Complejo();
El trabajo del constructor es iniciar el objeto que se ha definido mediante la clase. Dentro del constructor pueden implementarse aquellas acciones que se necesita ejecutar inicialmente y en forma automtica para dar una determinada configuracin a un objeto, o incluso para hacerlo funcional. Un mtodo constructor lleva el mismo nombre de la clase que lo contiene y se debe declarar con nivel de accesibilidad pblica (public), aunque tambin es admitido el nivel interno (internal). El nivel de accesibilidad pblica permite que el constructor pueda ser ejecutado en cualquier instancia, ya sea dentro del proyecto que implement la clase o por fuera de l, en cambio si el mtodo es internal, significa que solo podr ejecutarse dentro del proyecto que contiene a la clase. En general, la sintaxis para implementar un constructor es la siguiente:
[public | internal] NombreClase() { // Implementar el constructor o dejar esto vaci }
Un constructor es el primer elemento de la clase sobre el cual puede aplicarse el polimorfismo, aqu identificado como sobrecarga. Una clase puede implementar varios constructores, los cuales deben diferenciarse ya sea en el tipo o la cantidad de parmetros que manejan. En el siguiente fragmento de cdigo, la clase Complejo implementa tres constructores,
public class Complejo { public Complejo() { }
public Complejo(string NumeroComplejo) { // Cdigo para procesar el parmetro }
public Complejo(double Real, double Imaginario) { // Cdigo para procesar los parmetros } }
En el momento de la ejecucin, de acuerdo a los parmetros que reciba el constructor, el sistema decidir a cual de estos constructores llamar. Si la declaracin de un objeto Complejo se hiciera mediante,
Complejo z = new Complejo("5 + 3i")
se pondr en marcha el segundo constructor, ya que este es el nico que recibe como parmetro un valor tipo cadena de texto.
Destructor Un destructor es un mtodo que se ejecuta automticamente justo antes de que un objeto sea destruido. A diferencias del constructor, un mtodo destructor no puede sobrecargarse ni tampoco heredarse. Adems, no puede invocarse explcitamente. El lenguaje C# cuenta con una herramienta llamada recolector de basura que se encarga de destruir aquellos objetos que ya no se estn utilizando y an sigan CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 ELEMENTOS DE UN CLASE C#
pedrov.cs@hotmail.com 87 87 87 87 ocupando espacio en memoria. Como ya se haba mencionada anteriormente, el operador new, aplicado a la creacin de un objeto, se encarga de asignar la memoria necesaria que este necesita. En el momento que el objeto deja de ser referenciado, el recolector de basura se encarga de liberar la memoria que ocupaba. Un objeto se deja de referenciar cuando la ejecucin sale del mbito que lo defini. En los ejemplos que hemos analizado hasta el momento, la mayora de objetos deja de ser referenciado cuando se finaliza la ejecucin del mtodo Main. La sintaxis para implementar el destructor es la siguiente:
~NombreClase() { // Acciones antes de destruir un objeto }
En la prctica un mtodo destructor es utilizado para realizar tareas que se necesitan antes de destruir el objeto, como puede ser: guardar valores de datos, limpiar la memoria manualmente o fijar alguna configuracin especial.
Ejemplo 4.1 Un constructor y un destructor
En el siguiente ejemplo vamos a programar un constructor y un destructor para determinar el instante en que se ejecutan cada uno de ellos. Supongamos que tenemos una clase Estudiante que permite procesar algunos datos de estudiantes, y presenta dos sobrecargas para su constructor. La primera sobrecarga no hace prcticamente nada y la segunda exige el cdigo del estudiante.
/* Archivo: Ejemplo41.cs */
using System; using System.Windows.Forms;
public class Estudiante { string cdigo;
// Constructor por defecto public Estudiante() { }
// Constructor con cdigo del estudiante public Estudiante(string CodigoEstudiante) { cdigo = CodigoEstudiante; MessageBox.Show("Cdigo: " + cdigo, "Construyendo..."); }
// Propiedad public string Codigo { set { codigo = value; } get { return codigo; } }
public class Programa { static void Main() { Estudiante alumno = new Estudiante();
//Estudiante escolar = new Estudiante("01"); } }
Compile el archivo con la instruccin,
> csc ejemplo41.cs
Al ejecutar el programa se crea un objeto de tipo Estudiante pero, dado que la ejecucin llega al final del mtodo Main, inmediatamente se inicia el proceso de destruccin del objeto. Observe que, sin necesidad de hacer nada, la caja de mensajes del destructor permanece un instante y luego desaparece. En realidad, al llegar al final de la ejecucin e iniciar el proceso de destruccin, tambin se activa la recogida de basura y por lo tanto esta se encarga de destruir cualquier dato que exista en memoria, incluida la caja de mensajes. Desactive la primera lnea del programa y active la segunda. Vuelva a compilar el programa y analice la ejecucin. Observar que aparece la caja de mensajes del constructor y esta se mantiene hasta hacer clic en el botn aceptar. La caja de mensajes se mantiene por que el programa est en plena ejecucin. Pero no ocurre lo mismo cuando pasa al proceso de destruccin.
Mtodos Hasta este punto ya hemos trabajado con muchos mtodos. Sabemos que un mtodo es lo que otros lenguajes de programacin, sobre todo estructurados, se denominan procedimientos o funciones. Adems, se sabe que el principal mtodo que dirige la ejecucin de un programa C# es el mtodo Main, que es el punto por donde se inicia la ejecucin y la carga en memoria por parte del sistema operativo. Los mtodos le permiten al programador realizar acciones sobre los atributos internos de un objeto, o incluso actuar sobre elementos externos que se relacionan con dicho objeto. Aunque, un mtodo existe en la medida que exista para un programador que hace uso de una determinada clase, es decir un mtodo publico, en la prctica se puede hablar tambin de mtodos privados, queriendo significar que son acciones internas que se realizan con los elementos de un objeto. En general, un mtodo se define con una instruccin que tiene la siguiente sintaxis:
public tipo NombreMtodo(Argumentos) { // Implementacin del mtodo return valor }
Todo mtodo que debe devolver un valor, el cual debe ser del mismo tipo que el mtodo. Los mtodos que ejecutan acciones que no requieren la devolucin de un valor, se deben definir como void. C# para permitirle al programador compartir mtodos genricos que se ejecutan sin necesidad de hacer referencia a ningn objeto en particular, permite la definicin de mtodos estticos. Esto mtodos, que se definen con el modificador static, se CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 ELEMENTOS DE UN CLASE C#
pedrov.cs@hotmail.com 89 89 89 89 comportan como las funciones o procedimientos de acceso publico que se utilizan en la programacin estructurada, o en C++. Esta es la forma que implementa C# para permitirle al programador contar con funciones que son accesibles en el mismo nivel donde acta la clase en la cual se han incorporado.
Propiedades Las propiedades tienen un aspecto similar a un mtodo, pero no admiten argumentos. Se utilizan para establecer o asignar valores a los atributos de un objeto. Aunque, tambin pueden utilizarse para procesar valores internos del objeto y retornar un valor sin la necesidad de que exista un atributo directamente relacionado. En general, una propiedad se implementa con la siguiente sintaxis:
public tipo NombrePropiedad { get { // Devuelve con return un valor } set { // Asigna el valor de value a un atributo } }
La seccin get se encarga de retornar un valor, ya sea de un atributo o como resultado de un proceso de datos. La seccin set, en cambio, si debe actuar directamente sobre un atributo, ya que de otra forma no tendra sentido, y su objetivo es asignar un valor. En ambas secciones puede incluirse todo un conjunto de instrucciones, antes de retornar o asignar un valor. En el caso que no se requieren procesar parmetros, pueden presentarse por parte del programador dudas sobre lo que debe utilizarse: un mtodo o una propiedad. No existen reglas definidas sobre cual de los dos utilizar, y bajo que condiciones. Todo depende del diseo o claridad que se desee darle al cdigo de programacin. Por ejemplo, si se tiene una clase Persona, y se desea procesar a travs de ella la edad de una persona, con base en su fecha de nacimiento, que se encuentra incluida en algn atributo, bien podra implementarse un mtodo o una propiedad, y todo sera correcto.
Propiedades de solo lectura Una propiedad de solo lectura es aquella que nicamente permite leer un dato contenido en algn atributo de un objeto, o como resultado de un proceso interno. Este tipo de propiedades no debe permitir ingresar un dato al objeto. Es decir no debe incluir la seccin set. La implementacin de una propiedad de solo lectura se logra incluyendo en el cuerpo de la propiedad nicamente una seccin get, con lo cual se limita su funcionalidad solo a retornar un valor hacia el cliente de la clase. La siguiente es la sintaxis para implementar una propiedad de solo lectura:
public tipo NombrePropiedad { get { // Devuelve con return un valor } } CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 PROGRAMACION CON C# www.pedrov.phpnet.us 90 90 90 90
Propiedades de solo escritura Las propiedades de solo escritura solo permiten asignar valores a un atributo del objeto que las implementa. Esto significa que en la implementacin solo debe incluirse la seccin set. La siguiente es la sintaxis para implementar una propiedad de solo escritura:
public tipo NombrePropiedad { set { // Asigna el valor de value a un atributo } }
El uso adecuado de esta clasificacin de las propiedades es lo que permite establecer un buen nivel de encapsulamiento en las clases que se diseen.
Sobrecarga de mtodos Sobrecargar un mtodo significa implementar varios mtodos de una clase con el mismo nombre pero con diferentes parmetros, ya sea en cantidad o en tipo. C#, tambin acepta diferencias en los valores devueltos por un mtodo sobrecargado, pero siempre y cuando esta no sea la nica diferencia. Un mtodo podra tener una versin que devuelva un entero (int) y contar con tres parmetros, todos de tipo cadena (string). Si otra versin del mtodo devuelve un double, y tiene tres parmetros, no pueden ser todos del tipo string, al menos uno de ellos debe ser de otro tipo, de lo contrario el compilador devolver un error. El siguiente es un ejemplo de una sobrecarga correcta:
public int Matricular(string codigo, string curso) { // Registrar datos de un estudiante }
public bool Matricular(string codigo) { // Registrar datos de un estudiante }
Supongamos que una clase implementa los mtodos,
public int Matricular(string codigo, string curso) { // Registrar datos de un estudiante } public bool Matricular(string codigo, string curso) { // Registrar datos de un estudiante }
En este caso se generar un error en el momento de la compilacin, dado que C# tratar de identificar cada mtodo a travs de la cantidad de argumentos y sus tipos. Como no encuentra diferencias que permitan una clara identificacin, no se puede terminar el proceso de compilacin.
CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 ELEMENTOS DE UN CLASE C#
pedrov.cs@hotmail.com 91 91 91 91
Ejemplo 4.2 Autmata que procesa direcciones web
En este ejemplo vamos a construir una clase con dos mtodos para identificar si una cadena de texto corresponde a una direccin web bien escrita, a un correo electrnico o no corresponde a ninguno de los dos formatos. Los programas que se encargan de realizar este tipo de anlisis suelen denominarse autmatas. Una de las tareas ms complejas a las que se puede enfrentar un programador es la de procesar cadenas de texto para determinar si estn escritas de acuerdo a un formato o sintaxis preestablecidos. En la mayora de los casos esta tarea requiere la revisin, en forma repetitiva, carcter por carcter, en busca de los patrones que han sido estudiados y establecidos con anticipacin. Una solucin, dada por las Ciencias de la Computacin, para facilitar el estudio y anlisis de las condiciones que se deben establecer en el procesamiento de textos, es la teora de los autmatas y los lenguajes regulares, y ms especficamente las llamadas expresiones regulares, un campo de permanente investigacin que ha permitido alcanzar los logros que hoy en da tienen a la Computacin y a la Informtica en el sitial en que se encuentran. Las expresiones regulares, como tal, son un lenguaje que permite simbolizar conjuntos de cadenas de texto formadas por concatenacin de otras cadenas. La definicin exacta de expresin regular se ha establecido a travs de un intrincado conjunto de axiomas de tipo matemtico, que por ahora no entraremos a detallar. Tan solo vamos a dar una nocin breve del concepto de expresin regular en la misma forma como lo hace la ayuda incluida en el kit de desarrollo de .NET, donde se la asemeja con los patrones que solemos utilizar para realizar bsquedas de archivos en el disco duro de nuestro computador. Por ejemplo, si queremos que el sistema nos muestre todos los archivos fuentes de C# podemos hacerlo a travs del patrn *.cs. Esta es una forma de decirle al sistema operativo que muestre solo los archivos cuyo nombre termine en los caracteres .cs. Podramos decir que para el sistema operativo la cadena *.cs es una expresin regular. A partir de aqu, y en lo que resta de esta prctica, para aquellos lectores que no estn familiarizados con las expresiones regulares, se les sugiere olvidar todo lo que saben sobre el significado de algunos caracteres especiales, tales como * y +, y manejar nicamente el significado que aqu se describa. Para comenzar, y a manera de ejemplo explicativo, veamos algunas expresiones regulares bsicas que se manejan en la teora general de la computacin. Supongamos que tenemos un carcter a, entonces se representa:
Expresin regular Conjunto representado Representa a la cadena vaca a Representa a la cadena formada por a a+ Todas las cadenas formadas por la concatenacin de a, tales como a, aa, aaa, aaa, etc. a* Todas las cadenas formadas por la concatenacin de a, incluyendo a la cadena vaca. Es decir, a* = (a+) } {
Entonces, la expresin regular 01*, representa a todas las cadenas que empiezan por el carcter 0 (cero) seguido de ninguno o cualquier cantidad de unos. Aqu, estn representadas cadenas como 0, 01, 011, 0111, etc. La expresin regular (ab)+c, representa todas las cadenas que repiten la cadena ab, una o ms veces, y terminan en CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 PROGRAMACION CON C# www.pedrov.phpnet.us 92 92 92 92 el carcter c, tales como abc, ababc, abababc, etc. En este ltimo ejemplo no se incluye la cadena abcab, ni tampoco la cadena c. El framework de .NET pone a disposicin del programador el espacio de nombres System.Text.RegularExpressions, conformado por un conjunto de clases que se encargan de trabajar con expresiones regulares. Vamos a utilizar esta teora y las clases que .NET pone disposicin del programador para implementar un mtodo que permita analizar cadenas de texto y determinar si una cadena de texto corresponde a una direccin web, a un correo electrnico o a ninguno de ellos. .NET dispone de una mayor cantidad de elementos, o caracteres especiales, que aquellos manejados en la teora general computacin para la construccin de las expresiones regulares. Vamos a describir brevemente algunos de ellos, para utilizarlos en nuestro ejemplo:
Caracteres especiales Descripcin [ ] Permiten determinar una lista de caracteres, de los cuales se escoger uno. Por ejemplo, [0123] pone a disposicin cualquiera de estos dgitos para hacerlo coincidir con la cadena analizada. ( ) Permiten establecer alguna subcadena que se har coincidir con la cadena analizada. Por ejemplo, (01)* representa a todas las cadenas que son una repeticin de la subcadena 01, tales como 01, 0101, 010101. \A Establece que la coincidencia debe cumplirse desde el principio de la cadena \Z Establece que la coincidencia debe establecerse hasta el final de la cadena \w Representa a cualquier carcter que sea un carcter alfanumrico o el carcter de subrayado. Tambin se representa como [a-zA- Z0-9] {N} Establece que el elemento que le antecede debe repetirse exactamente N veces. Por ejemplo, [w]{3} representa a la cadena www.
Para no complicar mucho las cosas vamos a crear una expresin regular que permita identificar las direcciones web que tienen el formato,
www.nombredominio.tipodominio
donde nombredominio es un nombre formado por una cadena de caracteres alfanumericos y tipodominio corresponde a alguno de los posibles tipos de dominio que pueden existir, tales como com, net, info u org. Para nuestro caso, toda direccin web debe empezar por la repeticin del carcter w, tres veces. Esto podemos expresarlo como,
[w]{3}
A continuacin viene un punto. Este smbolo corresponde a un carcter especial de las expresiones regular de .NET, por lo cual debemos escribirlo en la forma
(\.)
El nombre del dominio, como ya se dijo, es una cadena de caracteres alfanumricos, donde no cabe la posibilidad de que sea una cadena vaca. Vamos a suponer que solo se aceptan caracteres en minsculas, por lo cual su representacin puede hacerse como,
[a-z0-9]+
CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 ELEMENTOS DE UN CLASE C#
pedrov.cs@hotmail.com 93 93 93 93 El tipo de dominio puede corresponder a una de las siguientes posibilidades: com, net, info u org. En este caso existe una disyuncin de la cual se debe escoger solo una opcin y se expresa como,
(com|net|info|org)
Finalmente es necesario que la cadena a analizar coincida exactamente desde su inicio hasta su final, por lo cual es necesario incluir los limites \A y \Z al principio y al final de la expresin regular, respectivamente. En definitiva la expresin regular que nos permitir validar una direccin web es la siguiente:
El smbolo @ al inicio de la asignacin le informa al compilador de C# que no identifique en la cadena de texto las secuencias estndar de escape. De la misma forma podemos crear una expresin regular que identifique a todas las direcciones de correo electrnico que cumplan con el formato,
nombreusuario@nombredominio.tipodominio
La siguiente es la expresin regular que identifica a este tipo de cadenas:
expresion = @"\A(\w+\.?\w*\@\w+\.)(com)\Z";
En esta expresin se ha incluido la secuencia \.? que admite la existencia de un punto, o ninguno, en el nombre del usuario. La clase que se encarga del procesamiento de las expresiones regulares se llama Regex y se encuentra en el espacio de nombres System.Text.RegularExpressions. Con esta clase se pueden definir objetos que reciben una expresin regular y se encargan de procesar una cadena de texto para identificar todas las subcadenas que hacen parte de ella. La clase Regex implementa el mtodo IsMatch que recibe como argumento la cadena que se va a analizar y retorna un valor booleano, true o false, de acuerdo a si se encontr cadenas que se identifiquen con la expresin regular o no. La siguiente lnea define un objeto tipo Regex que procesa una expresin regular como las definidas en este anlisis:
Regex automata = new Regex(expresion);
La clase Regex exige que la expresin regular sea asignada en el momento de la creacin del objeto. Con base en el anterior anlisis vamos a programar un ensamblado, tipo librera dinmica, que se encargar de recibir una cadena de texto y validarla de acuerdo a lo antes requerido. Este ensamblado estar constituido bsicamente por una clase, llamada AutomataWEB, conformada por dos mtodos estticos, EsWeb y EsCorreo, que servirn para validar las direcciones web y las direcciones de correo electrnico, respectivamente. /* Archivo: AutomataWEB.cs */
using System; using System.Text.RegularExpressions;
public class AutomataWEB { CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 PROGRAMACION CON C# www.pedrov.phpnet.us 94 94 94 94
public AutomataWEB() { } // Mtodo para identificar direcciones web public static bool EsWeb(string cadena) { string expresion; expresion = @"\A[w]{3}(\.)[a-z0-9]+(\.)(com|net|info|org)\Z"; Regex automata = new Regex(expresion); return automata.IsMatch(cadena); } // Mtodo para identificar direcciones de correo electrnico public static bool EsCorreo(string cadena) { string expresion; expresion = @"\A(\w+\.?\w*\@\w+\.)(com)\Z"; Regex automata = new Regex(expresion); return automata.IsMatch(cadena); } }
Compile este archivo con la instruccin,
> csc /t:library AutomataWEB.cs
Los mtodos estticos no pueden hacer parte de ningn objeto definido a partir de la clase que lo contiene. En consecuencia para referirse a ellos se debe hacer mediante el nombre de la clase. En este caso, para validar una direccin web, se debe hacer siguiendo la sintaxis,
AutomataWEB.EsWeb(cadena)
Ahora vamos a crear un programa que se encargar de recibir una cadena de texto y realizar la correspondiente verificacin con ayuda del autmata que hemos creado.
/* Archivo: Ejemplo42.cs */
using System;
public class ValidacionWEB { public static void Main() { string cadena;
Console.Write("Escriba una cadena de texto: "); cadena = Console.ReadLine(); cadena = cadena.ToLower(); // convierte a minsculas
if (AutomataWEB.EsWeb(cadena)) Console.Write("La cadena es una direccin web."); else if (AutomataWEB.EsCorreo(cadena)) Console.Write("La cadena es una direccin de email."); else Console.Write("La cadena no es una direccin vlida.");
} }
CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 ELEMENTOS DE UN CLASE C#
pedrov.cs@hotmail.com 95 95 95 95 Compile el programa con la instruccin,
> csc /r:AutomataWEB.dll ejemplo42.cs
Ejecute el programa resultante y pruebe su funcionamiento ingresando cadenas que correspondan a direcciones web, correos electrnicos y otras que no cumplan con ninguno de los dos formatos. El estudio de las expresiones regulares requiere un anlisis ms profundo y detallado, por lo cual este ejemplo se constituye solo en una breve introduccin a las potencialidades que ofrece este tema en las aplicaciones de software modernas. Una descripcin ms detallada se realizar en un captulo posterior dedicado exclusivamente a este tema. Como ejercicio, el lector debera modificar los mtodos programados aqu para validar cualquier direccin web, incluyendo todos los tipos de dominio y las abreviaturas de los pases a los que pertenecen, y de esta forma ir ganando terreno en el manejo de las expresiones regulares con .NET.
Ejemplo 4.3 Complejos de la forma a + bi
En este ejemplo vamos a continuar en la construccin de la clase Complejo que se ha tomado como modelo de las descripciones de este captulo. Sabemos que, todo complejo tiene la forma a + bi, y como tal tiene su validez dentro de las matemticas. Siempre que se necesite trabajar con un complejo, las cosas seran ms fciles si tanto la asignacin como el valor retornado por una variable de este tipo se hacen en trminos de la forma matemtica de un nmero complejo. Sera ideal poder realizar una asignacin a una variable compleja en la forma,
z = 2 + 3i;
Pero en vista de que el compilador de C# no reconoce la sintaxis a + bi como un valor numrico vlido, por ahora no es posible en el nivel de programacin lograr esta pretensin. Sin embargo, si podemos hacer una aproximacin de este tipo de asignacin mediante asignaciones en forma de cadena de texto, tal como
z.Valor = "2 + 3i";
de tal manera que, para el usuario de un programa que utilice esta clase, el manejo de los nmeros complejos sea totalmente transparente. En este punto, nos enfrentamos a una situacin bastante particular. Debemos programar una validacin que se encargue de reconocer un nmero complejo en una cadena de texto, interpretndola adecuadamente, para determinar las partes real e imaginaria del mismo. Para ello, es necesario, antes que nada, garantizar que la cadena de texto realmente incida con el formato de un nmero complejo vlido. Existen diversas formas para expresar un nmero complejo. Se sabe que un mismo nmero complejo se puede escribir en formas equivalentes, tales como 2 + 3i, 2 + i3, 3i + 2, i3 + 2, y cualquiera de ellas es vlida. Adems existen complejos cuya forma, debido a las propiedades matemticas, puede ser equivalente a otra. As, por ejemplo, tenemos que 5 + -2i es lo mismo que 5 2i, o que 4 + 1i es igual a 4 + i, o tambin que 0 + 4i es igual a 4i. Incluso, cualquier nmero real puede considerarse como un complejo de parte imaginaria igual a cero. La clase Complejo debe poseer la capacidad suficiente de recibir un nmero complejo en cualquiera de sus formatos vlidos y procesarlo adecuadamente. En general, y para facilitar la comprensin de las descripciones algoritmicas vamos a dividir en cuatro grupos los formatos en que puede estar escrito cualquier nmero complejo: CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 PROGRAMACION CON C# www.pedrov.phpnet.us 96 96 96 96
Grupo Formato 1 a, a + i, a + bi 2 i, i + a, bi, bi + a 3 ib, a + ib 4 ib + a
En estas descripciones sintcticas no se han incluido los signos, positivo o negativo, que pueden adoptar la parte real y la parte imaginaria de un complejo. Ms adelante, en cada caso particular entraremos a considerar este aspecto. Tampoco, el agrupamiento realizado obedece a ninguna regla especfica relacionada con estos nmeros, sino nicamente se busca agruparlos de acuerdo a algunas caractersticas de forma que pueden convenientemente facilitar la generalizacin de cada uno de ellos. El primer grupo es el formato ms usual entre los matemticos, la parte real va en primera instancia y la parte imaginara despus. El tercer formato suele encontrrselo con mucha frecuencia en libros de ingeniera. El segundo y cuarto formatos son un equivalente de los anteriores. Como ya se vio en el ejemplo anterior, .NET dispone de un recurso muy poderoso para identificar cadenas de texto que guardan una relacin de semejanza entre si. Estas son las clases del espacio de nombres System.Text.RegularExpressions, que permiten procesar expresiones regulares. Vamos a utilizar este recurso para procesar cadenas de texto a travs de expresiones regulares que representen a los grupos de la tabla anterior, para identificar si corresponden o no a nmeros complejos bien construidos. Abra en un editor de texto el archivo que contiene a la clase Complejo que se viene construyendo en los ejemplos anteriores. Una ventaja de .NET es que permite modificar un ensamblado, y siempre y cuando no se modifiquen los identificadores de sus miembros existentes, mantienen la compatibilidad con las aplicaciones que utilizaban la versin antigua del componente. Lo primero que se debe hacer es incluir dos nuevas lneas de cdigo con los espacios de nombres que se necesitan para los nuevos procesamientos que vamos a incluir en la clase.
using System.Globalization; using System.Text.RegularExpressions;
En vista de que los procesos que se van a realizar requieren conversiones de tipos numricos a cadenas de texto, y viceversa, es posible que algunos elementos de estos se vuelvan incompatibles con los tipos. Por ejemplo, si se tiene la cadena de texto 3.52, donde el punto corresponde al separador decimal del nmero representado, al convertirla a tipo double se puede presentar una inconsistencia de interpretacin si en la configuracin del sistema operativo se identifica al separador decimal con una coma, (,) La primera directiva posibilita acceder a las clases que nos van a permitir determinar el formato que se est utilizando en la mquina actual para mostrar nmeros con parte entera y decimal. Para evitar inconsistencias se adecuaran los nmeros al formato que maneje la mquina donde se est trabajando. En la siguiente lnea de cdigo, la propiedad CurrencyDecimalSeparator devuelve una cadena tipo string con el separador decimal que utiliza el sistema operativo en la mquina actual:
El punto central de este ejemplo, es construir un mtodo que le permita a las variables instanciadas de la clase Complejo recibir una cadena de texto y validarla para determinar si corresponde a un nmero complejo. Para ello vamos a determinar los patrones que pueden determinarlos. CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 ELEMENTOS DE UN CLASE C#
pedrov.cs@hotmail.com 97 97 97 97 Sabemos que, un nmero es una cadena de dgitos decimales en la cual puede o no aparecer un separador decimal. Si el separador decimal fuera un punto, que en el lenguaje de expresiones regulares de .NET se representa como (\.), un nmero tendra el formato,
numero = @"(\d+(\.)?\d*)";
El cuantificador ? especifica que el elemento que le antecede puede aparecer una, o ninguna vez, en la cadena analizada. Como en este caso vamos a utilizar como vlido el separador decimal definido por el sistema, sd, entonces introducimos una concatenacin con este, as:
numero = @"(\d+(" + sd + @")?\d*)";
Todo nmero complejo, exceptuando aquellos que no posean parte imaginaria nula, incluyen un literal que representa a la raz cuadrada de -1. Este generalmente se simboliza con la letra minscula i. Definimos este smbolo en la siguiente forma:
i = @"(i)";
El signo que puede anteceder a un nmero puede ser positivo, (+), o negativo, (-). Sabemos que para expresar opcin en la escogencia de uno u otro smbolo se utilizan los corchetes. Por lo tanto el signo de un nmero, en trminos de expresin regular de .NET, quedara expresado por,
signo = @"([+-])";
Con los anteriores elementos podemos expresar la parte real e imaginaria de un complejo en la siguiente forma:
real = signo + numero; imaginario = signo + numero + i;
Ahora veamos el primer grupo de complejos que es posible encontrar:
Grupo Formato 1 a, a + i, a + bi
En el caso ms general, un complejo puede estar formado por una parte real y una parte imaginaria, a + bi, que queda incluida en una expresin regular como la siguiente:
expresion1 = real + imaginario;
Pero, para incluir en la expresin regular los otros dos casos (complejos con parte imaginaria 0 o 1) es necesario tratar en forma independiente esta parte. En el primer caso, la parte imaginaria no existe, por lo tanto se debe dejar como opcional esta parte, incluyendo a su signo. As:
imaginario = "(" + signo + numero + i + ")?";
A su vez, para el segundo caso, donde la parte imaginaria solo la forma el literal i, se puede obtener dejando como opcional el nmero que la acompaa.
imaginario = "(" + signo + "(" + numero + ")?" + i + ")?";
CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 PROGRAMACION CON C# www.pedrov.phpnet.us 98 98 98 98 Con estas modificaciones, e indicndole al motor de procesamiento de expresiones regulares que debe validar la coincidencia en toda la cadena, desde el principio (\A) hasta el final, (\Z), la expresin regular para el grupo 1 de posibles complejos que pueden pasarse a la clase queda as:
string expresion1 = @"\A" + real + imaginario + @"\Z";
Para este primer grupo de complejos, es necesario definir un objeto del tipo Regex quien se encargar de procesar las cadenas entrantes para determinar si constituyen un nmero complejo. La siguiente lnea define el objeto complejo1 con la expresin regular antes analizada:
Regex complejo1 = new Regex(expresion1);
La comprobacin de la cadena entrante, puede realizarse mediante el mtodo booleano IsMatch del objeto complejo1. As,
if (complejo1.IsMatch(cadena)) return true;
De la misma forma como se realiz la expresin regular para el primer grupo de complejos, puede definirse las expresiones regulares para los otros casos de posibles formatos de nmeros complejos. En definitiva, con estos elementos construimos el mtodo booleano EsComplejo, en la siguiente forma:
// Mtodo para vlidar un nmero complejo private bool EsComplejo(string cadena) { cadena = QuitarEspacios(cadena); if (cadena.Length == 0) return false; string sd; sd=NumberFormatInfo.CurrentInfo.CurrencyDecimalSeparator; cadena = cadena.Replace('.', Char.Parse(sd));
// Elementos bsicos de un complejo string numero = @"(\d+(" + sd + @")?\d*)"; string i = @"(i)"; string signo = @"([+-])";
// Validacin para a, a + i, a + bi string real = signo + "?" + numero; string imaginario = "("+signo+"("+numero+")?"+i+")?"; string expresion1 = @"\A" + real + imaginario + @"\Z"; Regex complejo1 = new Regex(expresion1); if (complejo1.IsMatch(cadena)) return true;
// Validacin para i, i + a, bi, bi + a imaginario = signo + "?" + numero + "?" + i; real = "(" + signo + numero + ")?"; string expresion2 = @"\A" + imaginario + real + @"\Z"; Regex complejo2 = new Regex(expresion2); if (complejo2.IsMatch(cadena)) return true;
// Validacin para ib, ib + a imaginario = signo + "?" + i + numero; real = "(" + signo + numero + ")?"; string expresion3 = @"\A" + imaginario + real + @"\Z"; Regex complejo3 = new Regex(expresion3); if (complejo3.IsMatch(cadena)) return true;
// Validacin para a + ib real = signo + "?" + numero; imaginario = signo + i + numero; string expresion4 = @"\A" + real + imaginario + @"\Z"; CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 ELEMENTOS DE UN CLASE C#
Se ha incluido una llamada a un mtodo denominado QuitarEspacios, que se encarga de eliminar todos los espacios en blanco que puedan existir en la cadena de texto. Aunque la inclusin de los espacios pudo haberse considerado en las expresiones regulares de cada uno de los casos, esto hara un tanto ms compleja su estructuracin, por lo que se ha optado por el camino ms fcil, quitarlos! Este mtodo recibe una cadena de texto, y a travs de una expresin regular apropiada, busca uno o ms espacios y los reemplaza con una cadena vaca.
Una vez que se ha determinado la validez de una cadena de texto como nmero complejo, es necesario separar sus partes real e imaginaria para asignarlas a sus respectivos atributos. El mtodo PartesComplejo se basa de un razonamiento muy simple: se busca la parte imaginaria del complejo, se lee su valor y luego se elimina, dejando de esta forma nicamente la parte real.
// Mtodo para separar la parte real y la parte imaginaria private void PartesComplejo(string cadena) { string sd; sd=NumberFormatInfo.CurrentInfo.CurrencyDecimalSeparator; cadena = cadena.Replace('.', Char.Parse(sd)); string parteReal = ""; string parteImag = "";
string signo = @"([+-])"; string numero = @"(\d+(" + sd + @")?\d*)"; string i = @"(i)";
string imaginaria = signo + "?" + numero + "?" + i + numero + "?"; Regex imaginario1 = new Regex(imaginaria); if (imaginario1.IsMatch(cadena)) { // Cargar en mc las cadenas encontrada MatchCollection mc = imaginario1.Matches(cadena); // Recuperar la cadena encontrada foreach(Match m in mc) { parteImag = m.ToString(); } // Analizar algunos casos especiales if (parteImag == "+i" || parteImag == "i") parteImag = "1"; else if (parteImag == "-i") parteImag = "-1"; else parteImag = parteImag.Replace("i", "");
// Eliminar la parte imaginaria parteReal = imaginario1.Replace(cadena, ""); } else { parteReal = cadena; CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 PROGRAMACION CON C# www.pedrov.phpnet.us 100 100 100 100 parteImag = "0"; } // Convierte las cadenas de texto a double // y las asigna a sus atributos respectivos real = Double.Parse(parteReal); imaginario = Double.Parse(parteImag); }
En la expresin regular que se utiliza en este mtodo existen dos particularidades. La primera es que no se busca un sola coincidencia en toda la cadena (aunque, bien podra haberse hecho), por que se supone que la cadena analizada ya est comprobado que corresponde a un nmero complejo y por lo tanto solo existir una, o ninguna, parte imaginaria. La otra particularidad, es que se han establecido todas las formas de parte imaginaria en una sola expresin regular. La razn, en este punto ya se sabe que la parte imaginaria est bien escrita y por lo tanto todo lo que se encuentre ser vlido.
imaginaria = signo + "?" + numero + "?" + i + numero + "?";
Cuando se analiza una cadena a travs de una expresin regular, el motor de anlisis busca todas las subcadenas que hagan parte de esa familia y las va guardando en un objeto de tipo MatchCollection. Para recuperar la coleccin de cadenas objetivo encontradas existe el mtodo Matches que hace parte de los objetos de tipo Regex. En la siguiente lnea se recupera todas las cadenas encontradas y se asignan al objeto mc:
MatchCollection mc = imaginario1.Matches(cadena);
En este caso particular, estamos seguros que si la cadena objetivo existe, es nica, y en el peor de los casos no existe. Con este mtodo, y los anteriores, estamos listos para ampliar y mejorar las capacidades de nuestra clase Complejo. Se pondr a disposicin del usuario de la clase, tres constructores sobrecargados. El primero no pide ningn dato de entrada. El segundo mtodo da la posibilidad de ingresar los valores real e imaginario del complejo y el tercer mtodo permite inicializar la variable con un complejo ingresado en forma de cadena de texto. Estos son los tres constructores:
// Constructores public Complejo() { }
public Complejo(double parteReal, double parteImaginaria) { real = parteReal; imaginario = parteImaginaria; }
public Complejo(string valorComplejo) { if (EsComplejo(valorComplejo)) PartesComplejo(valorComplejo); else { real = 0; imaginario = 0; } } La salida devuelta por un objeto de tipo Complejo debe ser acorde a los valores de su parte real e imaginaria y al formato manejado para representar este tipo de nmeros. El siguiente mtodo privado se encarga de preparar la salida de un complejo en forma de cadena de texto, con el formato a + bi. CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 ELEMENTOS DE UN CLASE C#
Con base en lo anterior se agregar la propiedad Valor, que se encarga de devolver el valor de un complejo o recibir su valor desde el exterior, validando su correcta escritura. Esta es su implementacin:
public string Valor { get { return FormatoSalida(); } set { if (EsComplejo(value)) PartesComplejo(value); else { real = 0; imaginario = 0; } } }
Finalmente, es importante que los objetos tipo Complejo se puedan imprimir sin necesidad de recurrir a ninguna propiedad en especial, en la misma forma como los hacen los valores numricos de otros tipos. Es decir, si el programador tiene
Console.WriteLine(z);
debe mostrarse en pantalla el valor del complejo, que hace parte del argumento del mtodo WriteLine, en el formato adecuado. Esto mejora el nivel de abstraccin de la clase Complejo y le asegura a sus objetos un comportamiento ms cercano a los valores numricos, facilitando su manejo por parte de cualquier programador. Para lograr esto es necesario sobrescribir el mtodo ToString que hace parte de toda clase definida en .NET. La clase Complejo al igual que todas las clases de .NET, en realidad son heredadas de una clase genrica que forma parte de la raz del framework, llamada Object. Aunque esta herencia no se necesita determinar en forma explicita, el compilador de C# lo interpreta as con todas las clases definidas como superclases. Un mtodo que se hereda de Object para todas las clases es ToString el que se ejecuta por defecto cuando se intenta imprimir un objeto cualquiera. En la mayora de los casos cuando se imprime un objeto, sin especificar ninguna propiedad, este mtodo devuelve el nombre completo del objeto. En nuestro caso vamos a sobrescribir el mtodo para obligarlo escribir el valor del nmero complejo. As:
// Sobrecarga del mtodo ToString CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 PROGRAMACION CON C# www.pedrov.phpnet.us 102 102 102 102 public override string ToString() { return FormatoSalida(); }
En definitiva la clase complejo, ya casi lista, queda como sigue:
/* Archivo: Complejo.cs */
using System; using System.Globalization; using System.Text.RegularExpressions;
public class Complejo { // Atributos private double real; private double imaginario;
// Constructores public Complejo() { }
public Complejo(double parteReal, double parteImaginaria) { real = parteReal; imaginario = parteImaginaria; }
public Complejo(string valorComplejo) { if (EsComplejo(valorComplejo)) PartesComplejo(valorComplejo); else { real = 0; imaginario = 0; } }
// Propiedades public double Real { get { return real; } set { real = value; } }
public double Imaginario { get { return imaginario; } set { imaginario = value; } }
public double Modulo { get { return Tamano(); } }
public double Argumento { get { return Angulo(); } } CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 ELEMENTOS DE UN CLASE C#
pedrov.cs@hotmail.com 103 103 103 103
public string Valor { get { return FormatoSalida(); } set { if (EsComplejo(value)) PartesComplejo(value); else { real = 0; imaginario = 0; } } }
// Sobrecarga del mtodo ToString public override string ToString() { return FormatoSalida(); }
// Mtodo para vlidar un nmero complejo private bool EsComplejo(string cadena) { cadena = QuitarEspacios(cadena); if (cadena.Length == 0) return false; string sd = NumberFormatInfo.CurrentInfo.CurrencyDecimalSeparator; cadena = cadena.Replace('.', Char.Parse(sd)); // Elementos bsicos de un complejo string numero = @"(\d+(" + sd + @")?\d*)"; string i = @"(i)"; CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 PROGRAMACION CON C# www.pedrov.phpnet.us 104 104 104 104
string signo = @"([+-])";
// Validacin para a, a + i, a + bi string real = signo + "?" + numero; string imaginario = "(" + signo + "(" + numero + ")?" + i + ")?"; string expresion1 = @"\A" + real + imaginario + @"\Z"; Regex complejo1 = new Regex(expresion1); if (complejo1.IsMatch(cadena)) return true;
// Validacin para i, i + a, bi, bi + a imaginario = signo + "?" + numero + "?" + i; real = "(" + signo + numero + ")?"; string expresion2 = @"\A" + imaginario + real + @"\Z"; Regex complejo2 = new Regex(expresion2); if (complejo2.IsMatch(cadena)) return true;
// Validacin para ib, ib + a imaginario = signo + "?" + i + numero; real = "(" + signo + numero + ")?"; string expresion3 = @"\A" + imaginario + real + @"\Z"; Regex complejo3 = new Regex(expresion3); if (complejo3.IsMatch(cadena)) return true;
// Validacin para a + ib real = signo + "?" + numero; imaginario = signo + i + numero; string expresion4 = @"\A" + real + imaginario + @"\Z"; Regex complejo4 = new Regex(expresion4); return complejo4.IsMatch(cadena); }
// Mtodo para separar la parte real y la parte imaginaria private void PartesComplejo(string cadena) { string sd; sd=NumberFormatInfo.CurrentInfo.CurrencyDecimalSeparator; cadena = QuitarEspacios(cadena); cadena = cadena.Replace('.', Char.Parse(sd)); string parteReal = ""; string parteImag = ""; string signo = @"([+-])"; string numero = @"(\d+(" + sd + @")?\d*)"; string i = @"(i)";
string imaginaria = signo + "?" + numero + "?" + i + numero + "?"; Regex imaginario1 = new Regex(imaginaria); if (imaginario1.IsMatch(cadena)) { // Cargar en mc las cadenas encontrada MatchCollection mc = imaginario1.Matches(cadena); // Recuperar la cadena encontrada foreach(Match m in mc) { parteImag = m.ToString(); } // Analizar algunos casos especiales if (parteImag == "+i" || parteImag == "i") parteImag = "1"; else if (parteImag == "-i") parteImag = "-1"; else CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 ELEMENTOS DE UN CLASE C#
pedrov.cs@hotmail.com 105 105 105 105
parteImag = parteImag.Replace("i", "");
// Eliminar la parte imaginaria parteReal = imaginario1.Replace(cadena, ""); } else { parteReal = cadena; parteImag = "0"; } // Verificar la cadenas de texto vacas if (parteReal.Length == 0) parteReal = "0"; if (parteImag.Length == 0) parteImag = "0"; // Convierte las cadenas de texto a double // y las asigna a sus atributos respectivos real = Double.Parse(parteReal); imaginario = Double.Parse(parteImag); }
Compile este archivo en un ensamblado tipo librera dinmica, con la instruccin,
> csc /t:library Complejo.cs
El siguiente programa hace uso de la clase Complejo y muestra el funcionamiento de los cambios realizados:
/* Archivo: Ejemplo43.cs */ using System;
public class NumerosComplejos { static void Main() { Complejo z = new Complejo(); Console.Write("Ingrese un nmero complejo: "); z.Valor = Console.ReadLine();
// Uso de una sobrecarga del constructor de Complejo Complejo w = new Complejo("-3i + 4"); Console.WriteLine(w); Console.Write("a = {0}; b = {1}\n", w.Real, w.Imaginario); Console.Write("Mdulo: {0}\n", w.Modulo); Console.Write("Argumento: {0}", w.Argumento); } }
Compile este archivo con la instruccin de lnea de comandos,
> csc /r:Complejo.dll ejemplo43.cs
Ejecute el programa resultante y analice el comportamiento de cada lnea que lo compone. Un detalle importante a tener en cuenta es que el ensamblado Complejo.dll, a pesar de haber sufrido cambios, sigue siendo compatible con los programas que utilizaban la versin desarrollada en los anteriores ejemplos. Esta es una caracterstica de los ensamblados de .NET, mientras no se modifique o elimine el nombre de alguno de los miembros que lo componen, cada componente puede seguir editndose y aumentando sus elementos y mantener la compatibilidad hacia versiones anteriores.
Sobrecarga de operadores El concepto de sobrecarga tambin es aplicable a los operadores de C# y consiste en hacer que estos se comporten de acuerdo a los objetos que los utilizan. El ejemplo ms conocido es el operador sobrecargado es +, quin tiene una versin para valores numricos y otra para valores tipo cadena de texto. Cuando el operador se aplica a dos valores que representan cantidades numricas, realiza una suma matemtica, pero cuando se aplica a dos cadenas de texto, produce como resultado una cadena que es la concatenacin de las dos primeras. Las siguientes lneas de cdigo muestran un ejemplo tpico:
int a = 5 + 7; string c = "Hola" + "Mundo";
En la variable entera a se almacena el valor numrico 12, mientras que en la variable tipo cadena c se almacena el valor "HolaMundo". En cada caso el operador + tiene un comportamiento acorde a los tipos de datos sobre los que se aplica. La sobrecarga de operadores le da al lenguaje de programacin la claridad y naturalidad suficientes para hacer de las operaciones con objetos un trabajo fcil de entender y aplicar por parte del programador. Sin embargo, no se debe abusar de este recurso, por que un mal uso del mismo puede volver al lenguaje incomprensible y confuso al momento de aplicar los operadores a algunos objetos. Por ejemplo, perfectamente se podra hacer una sobrecarga para el operador +, de tal manera que al aplicarse a un cierto tipo de datos numricos produjera una multiplicacin, algo que hara perder innecesariamente la lgica del lenguaje de programacin. Todo operador, al momento de sobrecargarse, debe ejecutarse acorde a la funcin que realiza en otros objetos ya establecidos o en el mundo real del programador. No debemos perder de vista que la esencia de un lenguaje de programacin es actuar como intermediario en el proceso de comunicacin entre la mquina y el ser humano, y por lo tanto debe buscar ser lo ms claro posible. CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 ELEMENTOS DE UN CLASE C#
pedrov.cs@hotmail.com 107 107 107 107 Para sobrecargar un operador se utiliza un mtodo esttico que debe hacer parte del tipo o clase que lo va a utilizar. La siguiente es la sintaxis general que se utiliza para sobrecargar un operador unario:
Aunque C# no permite la sobrecarga de todo los operadores que maneja, si lo hace para la mayora de operadores relacionados con las matemticas y los bits. En la siguiente lista se muestran todos los operadores que admiten sobrecarga:
En este ejemplo vamos a sobrecargar los operadores matemticos para la clase Complejo. Hasta ahora no hemos definido la forma de realizar operaciones con complejos. Si un programador deseara obtener una suma de complejos debera recurrir a la definicin matemtica y aplicar el proceso con las partes de los complejos que se vayan a operar. Comencemos con la suma de nmeros complejos. Las matemticas la definen de la siguiente forma: si se tienen dos complejos 1 z y 2 z , la suma de ellos es un nmero complejo cuya parte real es la suma de las partes reales de los dos complejos, y de igual forma la parte imaginaria es igual a la suma de las partes imaginarias. En notacin matemtica:
Si 1 1 1 z a b i = + y 2 2 2 z a b i = + , entonces 1 2 1 2 1 2 ( ) ( ) z z a a b b i + = + + +
Una posible solucin al problema de programar la suma de complejos, podra ser la definicin de un mtodo esttico que se encargue de recibir como parmetros dos complejos, realizar la suma utilizando la definicin matemtica y devolver el resultado en trminos de una variable compleja. Este mtodo, implementado por la clase Complejo, bien podra ser el siguiente:
public static Complejo Suma(Complejo z1, Complejo z2) { double a = z1.Real + z2.Real; double b = z1.Imaginario + z2.Imaginario; Complejo z = new Complejo(a, b); return z; }
CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 PROGRAMACION CON C# www.pedrov.phpnet.us 108 108 108 108 La solucin es buena, y de hecho funciona muy bien. En las siguientes lneas de cdigo se muestra como debera utilizarse el mtodo Suma:
Complejo z1 = new Complejo("5 + 3i"); Complejo z2 = new Complejo("8 - 2i"); Complejo z = new Complejo(); z = Complejo.Suma(z1, z2);
Esta codificacin de operaciones matemticas, basada en llamadas a mtodos, puede resultar molesta en situaciones donde se van a realizar operaciones de uso muy comn. Un programador que haga uso de los complejos, talvez preferira codificar una suma en forma ms natural, o por lo menos como est acostumbrado a hacerlo con los dems nmeros que maneja el lenguaje de programacin, as
suma = z1 + z2;
Para lograr esto es necesario sobrecargar el operador +, indicndole cual debe ser el proceso a seguir cuando se aplique a nmeros complejos. El siguiente mtodo sobrecarga el operador + para la clase Complejo:
Como puede observarse, el mtodo de sobrecarga hace exactamente lo que debera hacer el programador usuario de la clase. La ventaja es que solo se programa aqu, y en adelante bastar con aplicar una operacin de suma comn y corriente, como si de otro nmero cualquiera se tratar. Antes de sobrecargar las dems operaciones vamos sobrecargar el operador inverso aditivo, o signo negativo, (-), el cual invierte el signo de las partes que conforman un complejo. Matemticamente establece:
Si z a bi = + entonces z a bi =
Entonces la sobrecarga del operador inverso aditivo, o signo negativo, queda como sigue:
En las dos ltimas sobrecargas aparentemente se ha modificado el mismo operador. En realidad no es as. El compilador de C# distingue claramente a cada operador por el nmero de operandos sobre los cuales acta. En el primer caso, al existir un solo operando entiende que se trata del operador inverso aditivo, mientras que el segundo caso queda claro que se trata del operador resta. La multiplicacin de complejos se obtiene realizando una multiplicacin polinomial de los dos operandos. En general esta operacin se simplifica en el siguiente resultado:
Si 1 1 1 z a b i = + y 2 2 2 z a b i = + , entonces 1 2 1 2 1 2 1 2 2 1 ( ) ( ) z z a a b b a b a b i = + +
Con base en esta definicin, la sobrecarga del operador multiplicacin, *, queda as:
Pero la multiplicacin no solo puede darse entre nmeros complejos, tambin puede multiplicarse un numero real por un complejo. Para lograr esto es necesario aplicar dos sobrecargas ms al operador *. Puede multiplicarse un complejo por la izquierda o por la derecha. Ambas situaciones deben quedar bien claras para el compilador de C#. La definicin matemtica de esta multiplicacin establece:
Si c y z a bi = + , entonces c z ca cbi = +
En consecuencia la sobrecarga de * para este caso queda como sigue:
public static Complejo operator *(double c, Complejo z) { Complejo z1 = new Complejo(); z1.Real = c * z.Real; z1.Imaginario = c * z.Imaginario; return z1; }
A su vez, la sobrecarga para la multiplicacin por la derecha se puede implementar con base en la anterior, as:
public static Complejo operator *(Complejo z, double c) { return c * z; }
Existe una operacin propia de los complejos, que no esta definida para ningn otro tipo numrico. Es el conjugado de un complejo. Esta operacin, lo nico que hace es invertir la parte imaginaria del nmero complejo al cual se aplica.
Si z a bi = + , el conjugado de z se define como z a bi = CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 PROGRAMACION CON C# www.pedrov.phpnet.us 110 110 110 110
En C# no existe un operador cuya funcionalidad tenga alguna relacin con el conjugado de un complejo. En vista de esto vamos a sobrecargar el operador ! (negacin lgica). La sobrecarga queda como sigue:
Teniendo en cuenta estos cambios, nuestra clase Complejo queda como sigue:
/* Archivo: Complejo.cs */
using System; using System.Text.RegularExpressions; using System.Globalization;
public class Complejo { // Atributos private double real; private double imaginario;
// Constructores public Complejo() { }
public Complejo(double parteReal, double parteImaginaria) { real = parteReal; imaginario = parteImaginaria; } public Complejo(string valorComplejo) { if (EsComplejo(valorComplejo)) PartesComplejo(valorComplejo); else { real = 0; imaginario = 0; CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 ELEMENTOS DE UN CLASE C#
pedrov.cs@hotmail.com 111 111 111 111
} }
// Propiedades public double Real { get { return real; } set { real = value; } }
public double Imaginario { get { return imaginario; } set { imaginario = value; } }
public double Modulo { get { return Tamano(); } }
public double Argumento { get { return Angulo(); } }
public string Valor { get { return FormatoSalida(); } set { if (EsComplejo(value)) PartesComplejo(value); else { real = 0; imaginario = 0; } } }
// Sobrecarga del mtodo ToString public override string ToString() { return FormatoSalida(); }
// Mtodo para vlidar un nmero complejo private bool EsComplejo(string cadena) { cadena = QuitarEspacios(cadena); if (cadena.Length == 0) return false; string sd = NumberFormatInfo.CurrentInfo.CurrencyDecimalSeparator; cadena = cadena.Replace('.', Char.Parse(sd)); // Elementos bsicos de un complejo string numero = @"(\d+(" + sd + @")?\d*)"; string i = @"(i)"; string signo = @"([+-])";
// Validacin para a, a + i, a + bi string real = signo + "?" + numero; string imaginario = "(" + signo + "(" + numero + ")?" + i + ")?"; string expresion1 = @"\A" + real + imaginario + @"\Z"; Regex complejo1 = new Regex(expresion1); if (complejo1.IsMatch(cadena)) return true;
// Validacin para i, i + a, bi, bi + a imaginario = signo + "?" + numero + "?" + i; real = "(" + signo + numero + ")?"; string expresion2 = @"\A" + imaginario + real + @"\Z"; Regex complejo2 = new Regex(expresion2); if (complejo2.IsMatch(cadena)) return true;
// Validacin para ib, ib + a imaginario = signo + "?" + i + numero; real = "(" + signo + numero + ")?"; string expresion3 = @"\A" + imaginario + real + @"\Z"; Regex complejo3 = new Regex(expresion3); if (complejo3.IsMatch(cadena)) return true;
// Validacin para a + ib real = signo + "?" + numero; imaginario = signo + i + numero; string expresion4 = @"\A" + real + imaginario + @"\Z"; Regex complejo4 = new Regex(expresion4); CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 PROGRAMACION CON C# www.pedrov.phpnet.us 114 114 114 114
return complejo4.IsMatch(cadena); }
// Mtodo para separar la parte real y la parte imaginaria private void PartesComplejo(string cadena) { string sd; sd=NumberFormatInfo.CurrentInfo.CurrencyDecimalSeparator; cadena = QuitarEspacios(cadena); cadena = cadena.Replace('.', Char.Parse(sd)); string parteReal = ""; string parteImag = "";
string signo = @"([+-])"; string numero = @"(\d+(" + sd + @")?\d*)"; string i = @"(i)";
string imaginaria = signo + "?" + numero + "?" + i + numero + "?"; Regex imaginario1 = new Regex(imaginaria); if (imaginario1.IsMatch(cadena)) { // Cargar en mc las cadenas encontrada MatchCollection mc = imaginario1.Matches(cadena); // Recuperar la cadena encontrada foreach(Match m in mc) { parteImag = m.ToString(); } // Analizar algunos casos especiales if (parteImag == "+i" || parteImag == "i") parteImag = "1"; else if (parteImag == "-i") parteImag = "-1"; else parteImag = parteImag.Replace("i", "");
// Eliminar la parte imaginaria parteReal = imaginario1.Replace(cadena, ""); } else { parteReal = cadena; parteImag = "0"; }
// Verificar la cadenas de texto vacas if (parteReal.Length == 0) parteReal = "0"; if (parteImag.Length == 0) parteImag = "0"; // Convierte las cadenas de texto a double // y las asigna a sus atributos respectivos real = Double.Parse(parteReal); imaginario = Double.Parse(parteImag); }
// Elimina los espacios de una cadena de texto private string QuitarEspacios(string cadena) { Regex espacio = new Regex(@"\s+"); cadena = espacio.Replace(cadena, ""); return cadena; } CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 ELEMENTOS DE UN CLASE C#
pedrov.cs@hotmail.com 115 115 115 115
// Da formato a la cadena de texto de salida private string FormatoSalida() { if (real == 0) return String.Format("{0}i", imaginario); else if (imaginario > 0) return String.Format("{0} + {1}i", real, imaginario); else if (imaginario < 0) return String.Format("{0} - {1}i", real, - imaginario); else return real.ToString(); } }
Compile el archivo con la instruccin,
> csc /t:library Complejo.cs
Con los cambios realizados ya contamos con una clase Complejo capaz de definir objetos cuyo comportamiento se asemeja bastante a los nmeros que maneja C#. La sobrecarga de los operadores aritmticos nos permitir codificar las operaciones de este tipo en la misma forma como se hace con cualquier otro tipo numrico. Aunque talvez no es el mejor, esta clase es un buen ejemplo de abstraccin y encapsulamiento, lo cual permite contar con tipos complejos con un buen nivel de autonoma para resolver la mayora de problemas propios de su naturaleza.
El siguiente programa hace uso de la clase Complejo y realiza algunas operaciones con nmeros complejos:
/* Archivo: Ejemplo44.cs */ using System; public class OperacionesComplejos { static void Main() { Complejo w = new Complejo(); Complejo z = new Complejo(); Console.Write("w = "); w.Valor = Console.ReadLine(); Console.Write("z = "); z.Valor = Console.ReadLine(); Console.Write("-w = {0}\n", -w); Console.Write("w + z = {0}\n", w + z); Console.Write("w - z = {0}\n", w - z); Console.Write("w * z = {0}\n", w * z); Console.Write("w / z = {0}\n", w / z); Console.Write("!w = {0}\n", !w); Console.Write("5w = {0}\n", 5 * w); } }
Guarde el archivo con el nombre ejemplo44.cs y complelo con la instruccin,
> csc /r:Complejo.dll ejemplo44.cs
CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 PROGRAMACION CON C# www.pedrov.phpnet.us 116 116 116 116 El lector podr comprobar que la clase Complejo define objetos que en forma autnoma se encargan de realizar la mayora de trreas que les impone su naturaleza, incluyendo su operatoria y el formato para la salida de los resultados, sin necesidad de que el programador tenga que preocuparse de esos detalles. Aunque se ha logrado un buen nivel de abstraccin y encapsulamiento, no podemos decir que todo est terminado. Por ejemplo, cuando se asigna a un objeto Complejo una cadena que no corresponde a la forma de un nmero complejo, la clase no posee un mecanismo para informar directamente sobre esa situacin anmala y en vez de eso asume un valor nulo sin que el usuario se entere de tal situacin. Se podra implementar un mecanismo de mensajes para informar al usuario que existe un error en la asignacin de un valor, pero esto podra afectar la generalidad del componente y limitarlo a un nico entorno de ejecucin. El objetivo es crear un componente de software til en cualquier entorno, consola, sistema grfico de Windows o web. En las siguientes secciones se describirn elementos de la programacin con C# que permiten dar mayor robustez a los componentes de software, y con ellos podremos resolver en forma tcnica las deficiencias de nuestra clase Complejo.
Eventos Un evento es una accin que produce un componente y a la que otro componente puede responder o puede controlar mediante cdigo. Los eventos ms conocidos son aquellos que se producen por accin del usuario, por ejemplo, al hacer clic con el botn principal del ratn sobre un botn de una ventana se produce un evento que a su vez ejecuta un cdigo de programacin. Sin embargo, esta ltima asociacin didctica para intentar explicar el concepto de evento, ms que ayudar, puede distorsionar la nocin que sobre el mismo impone la programacin orientada a objetos. En la prctica un evento es una especie de procedimiento que ejecuta un objeto, pero que se implementa fuera de su clase. Mejor, podemos ver a un evento como una llamada a un procedimiento (o mtodo) que hace un objeto, el cual es programado en la misma clase donde este existe. Los eventos le sirven a una clase para proporcionar notificaciones cuando sucede algo de inters. Una nocin muy general de cmo funciona un evento la podemos visualizar en el siguiente esquema. Supongamos que tenemos una clase ClaseA,
class ClaseA { Miembro { Llamar a MiEvento; } }
en la cual uno de sus miembros ejecuta un procedimiento especial al que hemos llamado MiEvento. Si este procedimiento se define como evento, su implementacin se puede hacer para cada objeto derivado de ClaseA y en el espacio donde estos se definan. Si con la clase ClaseA se definen los objetos a1 y a2, en una clase ClaseB, esta clase puede implementar mtodos que se ejecuten cuando cada uno de estos objetos, internamente, hace el llamado al procedimiento especial que hemos denominado MiEvento.
class ClaseB { CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 ELEMENTOS DE UN CLASE C#
pedrov.cs@hotmail.com 117 117 117 117 ClaseA a1 = new ClaseA(); ClaseA a2 = new ClaseB();
A MiEvento de a1 asociar MetodoA1; A MiEvento de a2 asociar MetodoA2;
MetodoA1() { Implementacin de MiEvento para a1; }
MetodoA2() { Implementacin de MiEvento para a2; } }
La ventaja que tiene el manejo de eventos es que el programador puede implementar respuestas diferentes para el evento de cada objeto, adecundolas a sus intereses y a la forma como desee personalizar el manejo de los componentes en cada caso especfico. Incluso puede no hacer ninguna implementacin. Vistas las cosas como nos las muestra este esquema, podemos decir que un evento es una seal que envan los objetos a1 y a2, descendientes de la clase ClaseA, hacia la clase ClaseB y que en este son respondidas mediante los mtodos MetodoA1 y MetodoA2, respectivamente. En el manejo de un evento es importante tener en cuenta tres elementos bsicos que intervienen: el componente que genera el evento, un manejador de eventos y un mtodo que responde a la seal.
Figura 3.14: Un componente genera un evento que es controlado por un manejador de eventos, quien decide cual es el mtodo que se debe ejecutar.
La mayora de clases de .NET establecen uno o varios eventos en los objetos que definen, lo cual le permite al programador personalizar su comportamiento de acuerdo a la aplicacin donde se vayan a utilizar, y de esta manera imprimir mayor versatilidad a la reutilizacin de componentes. La programacin de eventos facilita enormemente la adecuacin y control de los componentes de software y al mismo tiempo acorta los tiempos de desarrollo utilizados por los programadores. Aunque el uso de eventos no es un tema nuevo en la programacin, la forma de implementarlos si ha estado un tanto escondida para los programadores. Un buen ejemplo de ello son los entornos de desarrollo integrado, herramientas estas que en la mayora de los casos automatizan el proceso de creacin de los eventos y ponen a disposicin del programador el espacio definitivo donde se requiere su intervencin. Sin embargo, conocer como se implementa un evento puede ayudarle a sacar mayor provecho de estos y la programacin de sus propios objetos con eventos adecuados. Adems, en muchos casos los programadores tenemos la tendencia a relacionar los eventos nicamente con objetos grficos, lo cual dificulta su concepcin y utilizacin en componentes que no pertenezcan a este campo. Lo importante aqu, es dejar claro que con C# a todo componente de software que desarrollemos le podemos asignar eventos. Componente Manejador de eventos Mtodo CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 PROGRAMACION CON C# www.pedrov.phpnet.us 118 118 118 118 El proceso de creacin y programacin de un evento requiere la realizacin de una serie de pasos que pueden hacer de este una tarea confusa. Para la descripcin lo hemos dividido en dos etapas: implementacin y control (o respuesta).
Implementacin de un evento Teniendo en cuenta el esquema utilizado en los prrafos anteriores, la implementacin es el trabajo que se debe hacer por fuera de la clase ClaseB. En la implementacin de un evento se deben tener en cuenta los siguientes pasos:
- Crear una clase que guarde los datos del evento. Esta clase se deriva de la clase System.EventArgs y es quien se encarga de establecer los argumentos que puede manejar el evento.
public class ClaseArgumentosEvento: EventArgs { // Datos del evento }
- Definir un delegado para el evento. Un delegado es una clase (o es mejor decir, un tipo) que se encarga de crear una referencia hacia un mtodo. Esta es la forma que tiene .NET de crear punteros seguros hacia funciones.
public delegate tipo ManejadorEventos( object ObjetoEmisor, ClaseArgumentosEvento e);
El manejador de eventos posee dos argumentos que son opcionales. El primero, lleva el nombre del objeto que provoca el evento y el segundo es una variable que identifica los argumentos del evento. Siguiendo el esquema de nuestra explicacin, los anteriores elementos se definen por fuera de la clase ClaseA y, obviamente, tambin de la clase ClaseB. Lo que viene en seguida es lo que se debera incluir en una clase como ClaseA.
- Definir una clase que defina los objetos que van a generar el evento. En esta clase se debe incluir una declaracin del evento en la forma siguiente:
public event ManejadorEventos NombreEvento;
- Establecer una llamada que provoque el evento. La llamada al evento debe estar controlada. Es muy posible que un objeto no haya respondido al evento, en cuyo caso al llamarlo se provocara un error en tiempo de ejecucin.
if (NombreEvento != null) { NombreEvento(this, e) }
La palabra clave this se utiliza para hacer referencia a la identidad de un objeto. En este caso es la nica forma de conocer quin est haciendo uso de esta llamada, por que eso depende del nombre que se le haya dado al objeto definido en una determinada instancia.
Ejemplo 4.5 Un evento con clase
En el siguiente ejemplo vamos a desarrollar una sencilla clase que se encarga de calcular la ensima suma de un nmero. Un objeto definido a partir de la clase, recibir un nmero entero positivo y realizar una suma secuencial desde 1 hasta dicho valor. CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 ELEMENTOS DE UN CLASE C#
pedrov.cs@hotmail.com 119 119 119 119 Adems, cada vez que se realice una suma parcial, el objeto emitir un mensaje de aviso a la clase que lo contiene. Para hacer ms fcil la descripcin no utilizaremos un evento con parmetros, lo cual nos evita tener que definir una clase de argumentos para el mismo. Vamos al segundo paso del proceso. Se define el manejador de eventos, al cual llamaremos EventoSumador y que se define en la siguiente forma:
public delegate void EventoSumador;
El siguiente paso es declarar el evento, pero esto solo tiene sentido si existe una clase que lo vaya a implementar. Esta es nuestra clase objetivo, que va a llamarse Sumador. Inicialmente la clase tendr la siguiente forma,
public class Sumador { public event EventoSumador SumaParcial;
// Otros elementos de la clase Sumador }
El nombre del evento que se va a implementar es SumaParcial y observe que es del tipo EventoSumador. Nos detenemos en este punto y pasamos a implementar la funcionalidad de la clase Sumador. Esta clase se encargar de realizar la suma de los nmeros mediante un ciclo que ir realizando la suma 1 + 2 + 3 + , hasta el valor del nmero pasado a cualquiera de sus objetos.
public class Sumador { // Declaracin del evento public event EventoSumador SumaParcial;
// Atributos int numero;
// Constructor public Sumador(int valorNumero) { numero = valorNumero; }
// Mtodo que realiza la suma total public int Sumar() { int n = 0; for (int i = 1; i <= numero; i++) { n = n + i; } return n; } }
El evento SumaParcial debe generarse justo en el instante en que se realice una suma, es decir que su llamada debe incluirse en el cuerpo del ciclo for. As:
public int Sumar() { CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 PROGRAMACION CON C# www.pedrov.phpnet.us 120 120 120 120 int n = 0; for (int i = 1; i <= numero; i++) { n = n + i; if (SumaParcial != null) { SumaParcial(); } } return n; }
En definitiva, teniendo en cuenta este anlisis, nuestro archivo fuente, al que llamaremos Sumador.cs, queda as:
/* Archivo: Sumador.cs */
using System;
public delegate void EventoSumador();
public class Sumador { // Declaracin del evento public event EventoSumador SumaParcial;
// Atributos int numero;
// Constructor public Sumador(int valorNumero) { numero = valorNumero; }
// Mtodo que se encarga de sumar public int Sumar() { int n = 0; for (int i = 1; i <= numero; i++) { n = n + i; if (SumaParcial != null) SumaParcial(); } return n; } }
Compile el archivo en un ensamblado, tipo librera dinmica, con la instruccin,
> csc /t:library Sumador.cs
Con esto tenemos un componente que define objetos que son capaces de provocar un evento. La siguiente parte consistira en probar como funciona el evento que acabamos de crear. Pero antes de hacerlo vamos a describir como se realiza esta fase.
Controlar un evento Para programar un evento definido en otra clase, se debe definir y registrar un controlador de eventos. Este proceso es el que se realiza en cada clase que vaya a hacer CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 ELEMENTOS DE UN CLASE C#
pedrov.cs@hotmail.com 121 121 121 121 uso de un objeto y sus eventos y es igual para objetos creados por el programador o para los que se han incluido en el Framework de .NET. El proceso se realiza en dos pasos: - Se debe definir un controlador de eventos, que como ya se dijo, es un mtodo que debe tener la misma firma de mtodo (el mismo tipo e iguales parmetros) que el delegado declarado para el evento.
tipo MetodoEvento(object ObjetoEmisor, ClaseArgumentosEvento e) { // Implementacin de la respuesta al evento }
- Registrar el controlador de eventos, agregando el controlador al evento de un objeto en particular.
ObjetoEmisor.NombreEvento += new ManejadorEventos(MetodoEvento);
Una vez agregado el mtodo, este es llamado cada vez que la clase provoca el evento.
Ejemplo 4.6 Un evento controlado
En seguida vamos utilizar el componente que desarrollamos en el ejemplo anterior. Con este componente vamos definir un objeto e implementar el mtodo para su evento SumaParcial. El mtodo que controlar el evento lo definiremos as:
static void RSumaParcial() { Console.WriteLine("Evento de r..."); }
Este mtodo, nicamente se encarga de escribir en la consola la frase evento de r. Para que se ejecute cada vez que la clase genere el evento, es necesario registrar el mtodo asignndolo al evento SumaParcial de r.
r.SumaParcial += new EventoSumador(RSumaParcial);
Esta asignacin debe hacerse dentro de un mtodo de la clase, despus de haber creado el objeto que provoca el evento. La mayora de aplicaciones desarrolladas con ayuda de un entorno integrado de desarrollo realizan esta asignacin en un mtodo de carga inicial que es llamado directamente por el mtodo Main, o en su defecto lo incluyen en el cuerpo de este mtodo. Sin embargo, esto no significa que obligatoriamente deba realizarse de esa manera, ya que el programador puede realizarlo en cualquier otro mtodo aunque sus efectos pueden tener algunas variaciones. Es importante tener en cuenta que el evento no llama a su mtodo controlador hasta tanto no se haya ejecutado el mtodo que lo registra. Este es el archivo fuente de nuestro programa que hace uso del componente Sumador:
/* Archivo: ejemplo46.cs */
using System;
public class Programa { static void Main() CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 PROGRAMACION CON C# www.pedrov.phpnet.us 122 122 122 122
{ Sumador r = new Sumador(10); r.SumaParcial += new EventoSumador(RSumaParcial); int total = r.Sumar(); Console.Write("Total = {0}\n", total); }
static void RSumaParcial() { Console.WriteLine("Evento de r..."); } }
Compile el programa con la instruccin,
> csc /r:Sumador.dll ejemplo46.cs
Al ejecutar el programa, se carga en el objeto el valor 10, lo cual implica que se debern realizar diez ciclos y de hecho se llama igual nmero de veces al evento. La salida del programa se parece a lo siguiente,
Evento de r... Evento de r... Evento de r... Evento de r... Evento de r... Evento de r... Evento de r... Evento de r... Evento de r... Evento de r... Total = 55
Un detalle final. El mtodo controlador del evento se ha definido privado y esttico. Ninguna de las dos cosas es un requisito impuesto por las reglas de control de eventos. El mtodo definido es uno ms de los muchos que el programador puede implementar, y como tal puede tener cualquier nivel de accesibilidad. La opcin de esttico es un requisito impuesto por el mtodo Main. Cuando se hace el registro del mtodo controlador,
r.SumaParcial += new EventoSumador(RSumaParcial);
al incluir unicamente el nombre del mtodo, implcitamente se produce una referencia al objeto que contiene a dicho mtodo. Como esta lnea se encuentra en el cuerpo de un mtodo esttico, no es posible hacer referencia a un objeto de la misma clase y en consecuencia la nica solucin es definir un mtodo esttico, el cual para ser llamado no requiere una referencia a objeto alguno. En el ejemplo anbterior, se han obviado algunos detalles de los eventos, con el objetivo de facilitar la comprensin de su lgica de implementacin y control por parte del lector poco experimentado en el tema.
Ejemplo 4.7 Un evento con argumentos
Basndonos en los ejemplos anteriores vamos a implementar dos eventos que trabajen con argumentos. Uno de los eventos se producir cuando exista un error en el valor de CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 ELEMENTOS DE UN CLASE C#
pedrov.cs@hotmail.com 123 123 123 123 entrada y el otro corresponder a una nueva versin del evento SumaParcial, el cual permitir conocer los diferentes valores que se van generando en la suma. En los dos ltimos ejemplos se mostr la forma de implementar un evento en una clase, y tambin la forma de controlar ese evento en un objeto que lo genera. Pero el evento que se ha programado solo se limita a enviar una seal al cliente y no le retorna ningn parmetro. El objetivo era mostrar al lector la forma de generar sus propios eventos en los componentes que cree. Ahora vamos a incluir dos eventos, para nuestro sumador, que cumplan las especificaciones de la mayora de eventos de .NET. Los eventos generados por los objetos de .NET mantienen un esquema de presentacin. Todo evento devuelve dos parmetros: el primero es la identidad del objeto que lo gener, y el segundo corresponde a una variable que lleva los datos referentes a los argumentos del evento. Lo primero que vamos a hacer es programar una clase que nos permita fijar los datos de los eventos. La clase se llamar SigmaArgumentosEvento y contendr datos sobre errores y valores numricos relacionados con la sumatoria. La devolucin de un error se realizar a travs de los atributos Error y MensajeError. Un tercer dato numrico, Valor, servir para retornar cualquier valor numrico que se requiera.
public class SigmaArgumentosEvento : EventArgs { bool error = false; string mensajeError = ""; int valor;
public bool Error { get { return error; } set { error = value; } }
public string MensajeError { get { return mensajeError; } set { mensajeError = value; } }
public int Valor { get { return valor; } set { valor = value; } } }
Toda clase que permita especificar los datos de eventos se debe heredar de la clase EventArgs. Aunque esta clase no especifica datos, si es importante derivar a partir de ella, para mantener una base comn con todos los eventos generados en .NET. Se debe tener en cuenta que muchas clases del Framework no heredan directamente de EventArgs, sino de otras clases que a su vez heredaron de esta. Igual, siempre existir una lnea de jerarqua en la cual EventArgs es la base. Utilizando la clase anterior, ahora ya es posible definir un manejador de eventos que permita controlar eventos con datos. La definicin de dicho manejador queda como sigue:
El primer parmetro del manejador de eventos nos permitir enviar una referencia al objeto que gener el evento. Este parmetro puede ser importante en un momento dado para determinar quin gener el evento, sobretodo por que, en la prctica, puede ser necesario hacer que un mismo mtodo controle a varios eventos. A continuacin viene la clase que implementar dos eventos basados en el manejador anterior, que se identificar con el nombre de Sigma. Esta clase es una nueva versin del sumador que se desarrollo en el anterior ejemplo y tan solo contiene algunas modificaciones con respecto a los eventos que va a implementar. Estos eventos se llaman SumaParcial y EntradaNumero y su definicin es la siguiente.
public event EventoSumador SumaParcial; public event EventoSumador EntradaNumero;
El evento SumaParcial se generar cada que se realice una suma en el cuerpo de un ciclo for, y devolver el valor de la suma acumulada hasta ese momento. Este valor se devolver a travs de la propiedad Valor, de dicho evento. Dado que la generacin de un mismo evento puede necesitarse hacer desde diferentes miembros de la clase es recomendable incluirla en un mtodo como el siguiente:
De esta manera cualquier miembro de la clase puede llamar nicamente al mtodo para generar un determinado evento. Este no es un requisito de programacin, pero si ayuda a hacer ms claro el cdigo y su mantenimiento. Observe que el evento SumaParcial, a travs del operador this, devuelve una referencia al objeto que lo est generando en un momento dado. En la variable e se devuelven los valores del evento como tal. El evento EntradaNumero se ha diseado para producirse cada que va a iniciarse el proceso de la sumatoria. Este debe informar a su cliente que se present un error de ingreso de datos cuando el nmero asignado para la sumatoria sea menor que 1. El mtodo generador del evento es el siguiente:
if (EntradaNumero != null) { EntradaNumero(this, e); } } CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 ELEMENTOS DE UN CLASE C#
pedrov.cs@hotmail.com 125 125 125 125
En definitiva, y con las explicaciones mostradas en los comentarios de codificacin, la clase Sigma queda as:
public class Sigma { public event EventoSumador SumaParcial; public event EventoSumador EntradaNumero;
//Atributos int numero;
// Constructor public Sigma() { }
// Propiedades public int Numero { get { return numero; } set { numero = value; LlamarEntradaNumero(); } }
// Mtodo que realiza la suma total public int Sumar() { int n = 0;
for (int i = 1; i <= numero; i++) { n = n + i; LlamarSumaParcial(n); } return n; }
// Mtodos generadores de eventos
private void LlamarSumaParcial(int suma) { SigmaArgumentosEvento e = new SigmaArgumentosEvento(); e.Valor = suma; if (SumaParcial != null) { SumaParcial(this, e); } }
private void LlamarEntradaNumero() { SigmaArgumentosEvento e = new SigmaArgumentosEvento(); if (numero < 1) { e.Error = true; e.MensajeError = "El nmero ingresado es incorrecto."; CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 PROGRAMACION CON C# www.pedrov.phpnet.us 126 126 126 126 e.Valor = numero; }
if (EntradaNumero != null) { EntradaNumero(this, e); } } }
En el siguiente es el archivo se incluyen la clase Sigma y todos los elementos que hacen necesita para generar los eventos en estudio.
/* Archivo: Sigma.cs */
using System;
public class SigmaArgumentosEvento : EventArgs { string mensajeError = ""; bool error = false; int valor;
public int Valor { get { return valor; } set { valor = value; } }
public string MensajeError { get { return mensajeError; } set { mensajeError = value; } }
public bool Error { get { return error; } set { error = value; } } }
public delegate void EventoSumador(Object emisor, SigmaArgumentosEvento e);
public class Sigma { public event EventoSumador SumaParcial; public event EventoSumador EntradaNumero;
//Atributos int numero;
// Constructor public Sigma() { }
// Propiedades public int Numero { get { return numero; } set
CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 ELEMENTOS DE UN CLASE C#
pedrov.cs@hotmail.com 127 127 127 127
{ numero = value; LlamarEntradaNumero(); } } // Mtodo que se encarga realizar la suma total public int Sumar() { int n = 0;
for (int i = 1; i <= numero; i++) { n = n + i; LlamarSumaParcial(n); } return n; }
// Mtodos generadores de eventos
private void LlamarSumaParcial(int suma) { SigmaArgumentosEvento e = new SigmaArgumentosEvento(); e.Valor = suma; if (SumaParcial != null) { SumaParcial(this, e); } }
private void LlamarEntradaNumero() { SigmaArgumentosEvento e = new SigmaArgumentosEvento(); if (numero < 1) { e.Error = true; e.MensajeError = "El nmero ingresado es incorrecto."; e.Valor = numero; }
if (EntradaNumero != null) { EntradaNumero(this, e); } } }
Compile el archivo en un ensamblado dll con la instruccin,
> csc /t:library Sigma.cs
El siguiente programa utiliza dos objetos derivados de la clase Sigma, e implementa los mtodos que controlan sus eventos..
/* Archivo: ejemplo47.cs */
using System; using System.Windows.Forms;
public class Programa CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 PROGRAMACION CON C# www.pedrov.phpnet.us 128 128 128 128
{ public static void Main(string[] args) { Sigma suma1; Sigma suma2; suma1 = new Sigma(); // Registro de controladores de eventos suma1.EntradaNumero += new EventoSumador(SumaEntradaNumero); suma1.SumaParcial += new EventoSumador(Suma1SumaParcial); // Realizar suma para -1 suma1.Numero = -1; Console.Write("Suma1 = {0}\n", suma1.Sumar());
suma2 = new Sigma(); // Registro de controladores de eventos para suma2 suma2.EntradaNumero += new EventoSumador(SumaEntradaNumero); suma2.SumaParcial += new EventoSumador(Suma2SumaParcial); // Realizar suma para 10 suma2.Numero = 10; Console.WriteLine("Suma2 = {0}\n", suma2.Sumar()); }
Un aspecto importante. El evento EntradaNumero de los dos objetos, suma1 y suma2, ha sido controlado por un mismo mtodo, SumaEntradaNumero. Este mtodo muestra, en una caja de mensajes, informacin relacionada con el objeto que genera el evento, especficamente el valor numrico que se ha ingresado. Aqu es donde se aprovecha el primer parmetro del evento para conocer cual fue el objeto que lo gener. La forma como se nombran los mtodos controladores es muy importante cuando se va a trabajar con muchos objetos, para evitar confusiones y facilitar el mantenimiento del cdigo. Aqu se ha seguido las recomendaciones hechas por la documentacin del CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 ELEMENTOS DE UN CLASE C#
pedrov.cs@hotmail.com 129 129 129 129 Framework de .NET. Un mtodo controlador debe nombrarse iniciando con el nombre del objeto generador, seguido de una cadena equivalente al nombre del evento.
Ejemplo 4.8 Un evento de la consola
El objeto de .NET que ms hemos utilizado hasta el momento ha sido el objeto Console. Este objeto, es uno de los pocos del Framework que tan solo cuenta con un evento (al menos hasta la versin 3.0). Ese evento se llama CancelKeyPress y se genera cada que el usuario de una aplicacin cancela, en forma forzada, la finalizacin de una aplicacin de consola presionando las teclas CTRL+C. En este ejemplo vamos a programar un mtodo para controlar el evento CancelKeyPress y aprovecharlo para mostrar un mensaje de cancelacin en una caja de texto. El siguiente es el mtodo que controla este evento:
El evento CancelKeyPress ha sido definido mediante el manejador de eventos del objeto Console que se identifica con el nombre ConsoleCancelEventHandler. Por lo tanto el registro del mtodo controlador de este evento se debe realizar de la siguiente forma:
Console.CancelKeyPress += new ConsoleCancelEventHandler(ConsoleCancelKeyPress);
El siguiente es el programa que muestra el evento CancelKeyPress en accin:
/* Archivo: Ejemplo48.cs */
using System; using System.Windows.Forms;
public class Programa { public static void Main(string[] args) { Console.CancelKeyPress += new ConsoleCancelEventHandler(ConsoleCancelKeyPress); Console.WriteLine("Hola consola!"); Console.Write("La aplicacin an no ha terminado . . ."); Console.ReadKey(true); }
Console.ReadKey(true); CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 PROGRAMACION CON C# www.pedrov.phpnet.us 130 130 130 130 y se queda esperando a que el usuario presione una tecla. Mientras esto no ocurra el programa sigue en memoria. Si en este punto el usuario presiona la combinacin de teclas CTRL+C se est forzando al sistema a terminarlo y se genera el evento CancelKeyPress. Obviamente, si el usuario no presiona estas teclas, el evento nunca se genera.
Ejemplo 4.9 Evento de entrada para la clase Complejo
En esta prctica vamos a desarrollar un evento generado por la clase Complejo, despus de que se ingresa y verifica un valor complejo en forma de cadena de texto. A travs de este evento el cliente que haga uso de la clase podr programar un mecanismo para controlar los posibles errores que puedan ocasionarse en el ingreso de los valores complejos. El evento se llamar NumeroComprobado. El evento debe generarse siempre que se ingrese un valor en forma de cadena de texto, sin importar si corresponde o no a un complejo. Aqu necesitamos que el evento retorne un argumento informando sobre el posible error encontrado en la validacin de la cadena de texto. Esto se realizar definiendo el argumento del tipo ComplejoArgumentosEvento, en la siguiente forma
public class ComplejoArgumentosEvento : EventArgs { // Campos bool error;
// Propiedades public bool Error { get { return error; } set { error = value; } } }
Definimos un manejador de eventos para la clase Complejo,
public delegate void ComplejoEvento( object ObjetoEmisor, ComplejoArgumentosEvento e);
En el cuerpo de la clase complejo se define el evento NumeroComprobado as:
public class Complejo { public event ComplejoEvento NumeroComprobado;
// Implementacin de Complejo... }
A su vez, definimos un mtodo que se encargue de generar el evento. Esto nos permite hacer la llamada desde ms de un proceso de la clase Complejo, as
private void GenerarNumeroComprobado(bool existeError) { ComplejoArgumentosEvento e = new ComplejoArgumentosEvento(); e.Error = existeError; if (NumeroComprobado != null) { NumeroComprobado(this, e); } } CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 ELEMENTOS DE UN CLASE C#
pedrov.cs@hotmail.com 131 131 131 131
Como ya se dijo este evento ser generado desde los mtodos que se encargan de leer cadenas de texto y verificar si corresponde a un complejo. Estos son, uno de los constructores
public Complejo(string valorComplejo) { bool existeError = false;
if (EsComplejo(valorComplejo)) PartesComplejo(valorComplejo); else { real = 0; imaginario = 0; existeError = true; } // Generar el evento GenerarNumeroComprobado(existeError); }
y la propiedad Valor,
public string Valor { get { return FormatoSalida(); } set { bool existeError = false;
if (EsComplejo(value)) PartesComplejo(value); else { real = 0; imaginario = 0; existeError = true; } // Generar el evento GenerarNumeroComprobado(existeError); } }
Cargue en un editor de texto el archivo Complejo.cs que se viene trabajando, agregue la clase ComplejoArgumentosEvento y realice los cambios que aqu se han descrito. En definitiva el archivo queda como sigue:
/* Archivo: Complejo.cs */
using System; using System.Text.RegularExpressions; using System.Globalization;
public class ComplejoArgumentosEvento : EventArgs { // Campos bool error;
// Calcular el ngulo del complejo private double Angulo() { double alfa; if (real > 0) alfa = Math.Atan(imaginario / real); else if (real < 0) if (imaginario > 0) alfa = Math.PI + Math.Atan(imaginario / real); else alfa = - Math.PI + Math.Atan(imaginario / real); else if (imaginario > 0) alfa = Math.PI / 2; else if (imaginario < 0) CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 ELEMENTOS DE UN CLASE C#
pedrov.cs@hotmail.com 135 135 135 135
alfa = - Math.PI / 2; else alfa = 0;
return alfa; }
// Mtodo para vlidar un nmero complejo private bool EsComplejo(string cadena) { cadena = QuitarEspacios(cadena); if (cadena.Length == 0) return false; string sd = NumberFormatInfo.CurrentInfo.CurrencyDecimalSeparator; cadena = cadena.Replace('.', Char.Parse(sd)); // Elementos bsicos de un complejo string numero = @"(\d+(" + sd + @")?\d*)"; string i = @"(i)"; string signo = @"([+-])";
// Validacin para a, a + i, a + bi string real = signo + "?" + numero; string imaginario = "(" + signo + "(" + numero + ")?" + i + ")?"; string expresion1 = @"\A" + real + imaginario + @"\Z"; Regex complejo1 = new Regex(expresion1); if (complejo1.IsMatch(cadena)) return true;
// Validacin para i, i + a, bi, bi + a imaginario = signo + "?" + numero + "?" + i; real = "(" + signo + numero + ")?"; string expresion2 = @"\A" + imaginario + real + @"\Z"; Regex complejo2 = new Regex(expresion2); if (complejo2.IsMatch(cadena)) return true;
// Validacin para ib, ib + a imaginario = signo + "?" + i + numero; real = "(" + signo + numero + ")?"; string expresion3 = @"\A" + imaginario + real + @"\Z"; Regex complejo3 = new Regex(expresion3); if (complejo3.IsMatch(cadena)) return true;
// Validacin para a + ib real = signo + "?" + numero; imaginario = signo + i + numero; string expresion4 = @"\A" + real + imaginario + @"\Z"; Regex complejo4 = new Regex(expresion4); return complejo4.IsMatch(cadena); }
// Mtodo para separar la parte real y la parte imaginaria private void PartesComplejo(string cadena) { string sd; sd=NumberFormatInfo.CurrentInfo.CurrencyDecimalSeparator; cadena = QuitarEspacios(cadena); cadena = cadena.Replace('.', Char.Parse(sd)); string parteReal = ""; string parteImag = "";
string imaginaria = signo + "?" + numero + "?" + i + numero + "?"; Regex imaginario1 = new Regex(imaginaria); if (imaginario1.IsMatch(cadena)) { // Cargar en mc las cadenas encontrada MatchCollection mc = imaginario1.Matches(cadena); // Recuperar la cadena encontrada foreach(Match m in mc) { parteImag = m.ToString(); } // Analizar algunos casos especiales if (parteImag == "+i" || parteImag == "i") parteImag = "1"; else if (parteImag == "-i") parteImag = "-1"; else parteImag = parteImag.Replace("i", "");
// Eliminar la parte imaginaria parteReal = imaginario1.Replace(cadena, ""); } else { parteReal = cadena; parteImag = "0"; }
// Verificar la cadenas de texto vacas if (parteReal.Length == 0) parteReal = "0"; if (parteImag.Length == 0) parteImag = "0"; // Convierte las cadenas de texto a double // y las asigna a sus atributos respectivos real = Double.Parse(parteReal); imaginario = Double.Parse(parteImag); }
private void GenerarNumeroComprobado(bool existeError) { CAPITULO CAPITULO CAPITULO CAPITULO 4 44 4 ELEMENTOS DE UN CLASE C#
pedrov.cs@hotmail.com 137 137 137 137
ComplejoArgumentosEvento e = new ComplejoArgumentosEvento(); e.Error = existeError; if (NumeroComprobado != null) { NumeroComprobado(this, e); } } }
Vuelva a compilar el archivo con la instruccin,
> csc /t:library Complejo.cs
El siguiente programa utilice el evento NumeroComprobado para mostrar al usuario un mensaje informndole si el nmero ingresado fue correcto o incorrecto.
/* Archivo: Ejemplo49.cs */
using System;
public class Programa { static void Main() { Complejo zeta = new Complejo(); zeta.NumeroComprobado += new ComplejoEvento(ZetaNumeroComprobado); zeta.Valor = Console.ReadLine(); }
En este caso se utiliz la consola para enviar un mensaje al usuario, pero tambin pudo haberse programado en una caja de mensajes grfica. Esto le da ms versatilidad a la clase Complejo, ya que el programador puede utilizarla en diferentes contextos y adecuar sus mensajes al entorno de desarrollo donde se aplique.