Documentos de Académico
Documentos de Profesional
Documentos de Cultura
ProgramacionCSharp 04
ProgramacionCSharp 04
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.
86
Complejo z;
z = new Complejo();
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
www.pedrov.phpnet.us
87
88
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
www.pedrov.phpnet.us
89
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
}
}
90
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
}
www.pedrov.phpnet.us
91
Conjunto representado
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
pedrov.cs@hotmail.com
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
\Z
\w
{N}
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}
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)
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);
}
}
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.");
}
}
www.pedrov.phpnet.us
95
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:
pedrov.cs@hotmail.com
96
Grupo
1
2
3
4
Formato
a, a + i, a + bi
i, i + a, bi, bi + a
ib, a + ib
ib + a
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:
sd = NumberFormatInfo.CurrentInfo.CurrencyDecimalSeparator;
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.
www.pedrov.phpnet.us
97
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 = @"([+-])";
Formato
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 + ")?";
pedrov.cs@hotmail.com
98
@"\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);
99
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 +
string i = @"(i)";
@")?\d*)";
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);
}
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.
www.pedrov.phpnet.us
101
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
pedrov.cs@hotmail.com
102
103
104
105
106
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";
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:
public static TipoDevuelto operator operador (Tipo operando)
{
// Implementacin
}
Operadores binarios
pedrov.cs@hotmail.com
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);
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:
public static Complejo operator +(Complejo z1,Complejo z2)
{
Complejo suma = new Complejo();
suma.real = z1.real + z2.real;
suma.imaginario = z1.imaginario + z2.imaginario;
return suma;
}
109
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;
}
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
pedrov.cs@hotmail.com
110
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;
www.pedrov.phpnet.us
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();
}
// Sobrecarga del operador +
public static Complejo operator +(Complejo z1, Complejo z2)
{
Complejo suma = new Complejo();
suma.Real = z1.Real + z2.Real;
suma.Imaginario = z1.Imaginario + z2.Imaginario;
return suma;
}
// Sobrecarga del operador - (negativo)
public static Complejo operator -(Complejo z)
{
Complejo inverso = new Complejo();
pedrov.cs@hotmail.com
112
inverso.Real = - z.Real;
inverso.Imaginario = - z.Imaginario;
return inverso;
}
// Sobrecarga del operador public static Complejo operator -(Complejo z1, Complejo z2)
{
Complejo resta = z1 + (- z2);
return resta;
}
// Sobrecarga del operador *
public static Complejo operator *(Complejo z1, Complejo z2)
{
Complejo producto = new Complejo();
double a1 = z1.Real, b1 = z1.Imaginario;
double a2 = z2.Real, b2 = z2.Imaginario;
producto.Real = a1 * a2 - b1 * b2;
producto.Imaginario = a1 * b2 + a2 * b1;
return producto;
}
public static Complejo operator *(double c, Complejo z)
{
Complejo z1 = new Complejo();
z1.Real = c * z.Real;
z1.Imaginario = c * z.Imaginario;
return z1;
}
public static Complejo operator *(Complejo z, double c)
{
return c * z;
}
// Sobrecarga del operador ! para el conjugado
public static Complejo operator !(Complejo z)
{
Complejo conjugado = new Complejo();
conjugado.Real = z.Real;
conjugado.Imaginario = - z.Imaginario;
return conjugado;
}
// Sobrecarga del operador /
public static Complejo operator /(Complejo z1, Complejo z2)
{
Complejo cociente;
cociente = 1 / Math.Pow(z2.Modulo, 2) * (z1 * !z2);
return cociente;
}
// Mtodos privados
private double Tamano()
{
double c;
c = Math.Sqrt(real * real + imaginario * imaginario);
return c;
}
www.pedrov.phpnet.us
113
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 +
string i = @"(i)";
@")?\d*)";
115
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);
}
}
pedrov.cs@hotmail.com
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;
}
}
117
Componente
Manejador de
eventos
Mtodo
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.
pedrov.cs@hotmail.com
118
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 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)
}
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 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()
{
pedrov.cs@hotmail.com
120
int n = 0;
for (int i = 1; i <= numero; i++)
{
n = n + i;
if (SumaParcial != null)
{
SumaParcial();
}
}
return n;
}
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
www.pedrov.phpnet.us
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:
-
Una vez agregado el mtodo, este es llamado cada vez que la clase provoca el evento.
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()
pedrov.cs@hotmail.com
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...");
}
}
www.pedrov.phpnet.us
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;
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:
public delegate void EventoSumador(Object emisor,
SigmaArgumentosEvento e);
pedrov.cs@hotmail.com
124
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;
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:
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);
}
}
www.pedrov.phpnet.us
125
126
e.Valor = numero;
}
if (EntradaNumero != null)
{
EntradaNumero(this, e);
}
}
}
www.pedrov.phpnet.us
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);
}
}
}
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
pedrov.cs@hotmail.com
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());
}
// Controladores de eventos
static void Suma1SumaParcial(object emisor, SigmaArgumentosEvento e)
{
Console.WriteLine(e.Valor);
}
static void Suma2SumaParcial(object emisor, SigmaArgumentosEvento e)
{
Console.WriteLine(e.Valor);
}
static void SumaEntradaNumero(object emisor, SigmaArgumentosEvento e)
{
Sigma s = (Sigma)emisor;
if (e.Error)
MessageBox.Show(e.MensajeError, "N = " + s.Numero);
else
{
MessageBox.Show("Iniciando suma...", "N = " + s.Numero);
}
}
}
129
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.
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);
}
}
www.pedrov.phpnet.us
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);
}
}
pedrov.cs@hotmail.com
132
// Propiedades
public bool Error
{
get { return error; }
set { error = value; }
}
}
public delegate void ComplejoEvento(object Emisor, ComplejoArgumentosEvento e);
public class Complejo
{
// Atributos
private double real;
private double imaginario;
// Eventos
public event ComplejoEvento NumeroComprobado;
// Constructores
public Complejo() { }
public Complejo(double parteReal, double parteImaginaria)
{
real = parteReal;
imaginario = parteImaginaria;
}
public Complejo(string valorComplejo)
{
bool existeError = false;
if (EsComplejo(valorComplejo))
PartesComplejo(valorComplejo);
else
{
real = 0;
imaginario = 0;
existeError = true;
}
// Generar el evento
GenerarNumeroComprobado(existeError);
}
// 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(); }
www.pedrov.phpnet.us
133
134
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 signo = @"([+-])";
string numero = @"(\d+(" + sd +
@")?\d*)";
pedrov.cs@hotmail.com
136
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);
}
private string QuitarEspacios(string cadena)
{
Regex espacio = new Regex(@"\s+");
cadena = espacio.Replace(cadena, "");
return cadena;
}
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();
}
private void GenerarNumeroComprobado(bool existeError)
{
www.pedrov.phpnet.us
137
*/
using System;
public class Programa
{
static void Main()
{
Complejo zeta = new Complejo();
zeta.NumeroComprobado += new ComplejoEvento(ZetaNumeroComprobado);
zeta.Valor = Console.ReadLine();
}
static void ZetaNumeroComprobado(object emisor, ComplejoArgumentosEvento e)
{
if (e.Error)
Console.WriteLine("Incorrecto");
else
Console.WriteLine("Correcto...");
}
}
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.
pedrov.cs@hotmail.com