programacion c sharp_04

53
Capítulo 4 ELEMENTOS DE UNA CLASE C# La clase como elemento fundamental de la programación orientada a objetos requiere toda la atención posible por parte del programador. El correcto manejo de sus elementos y el buen aprovechamiento de los mismos es lo que permitirá sacar el máximo provecho de esta metodología de programación, y sobretodo hacer más fácil y efectiva la tarea de programar una aplicación de software. La teoría general de la programación orientada a objetos define unos elementos básicos que conforman una clase, pero cada uno de los lenguajes de programación ha realizado sus propios aportes a estos elementos, especialmente ampliando su funcionalidad o representándolos mediante elementos propios del lenguaje, con el objetivo de volverlos más potentes y fáciles de manejar. El lenguaje C# ha dotado a las clases de una serie de elementos que en apariencia amplían el conjunto de elementos definidos en la teoría general, pero más que eso, en realidad lo que se busca es poner a disposición del programador toda una gama de recursos que le permitan construir componentes de software que cumplan todos los requerimientos de la programación orientada a objetos y permitan expresar los elementos generales en la forma más efectiva y eficiente posible. Estos son los elementos básicos que constituyen una clase en C#: Constructores Destructores Constantes Campos Métodos Propiedades Indizadores Operadores Eventos Delegados Estructuras Constructores Un constructor es un método de una clase que se ejecuta automáticamente 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 método constructor que no hace absolutamente nada. Además, 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 números complejos, definida en el anterior capítulo, en las siguientes dos líneas de código, la primera línea define un objeto de tipo Complejo y la segunda se encarga de llamar al constructor de esa clase.

Upload: rodolfo-parra

Post on 03-Nov-2014

539 views

Category:

Education


3 download

DESCRIPTION

Programación básica c sharp

TRANSCRIPT

Page 1: Programacion c sharp_04

Capítulo 4

ELEMENTOS DE UNA CLASE C#

La clase como elemento fundamental de la programación orientada a objetos requiere

toda la atención posible por parte del programador. El correcto manejo de sus

elementos y el buen aprovechamiento de los mismos es lo que permitirá sacar el

máximo provecho de esta metodología de programación, y sobretodo hacer más fácil y

efectiva la tarea de programar una aplicación de software. La teoría general de la

programación orientada a objetos define unos elementos básicos que conforman una

clase, pero cada uno de los lenguajes de programación ha realizado sus propios aportes

a estos elementos, especialmente ampliando su funcionalidad o representándolos

mediante elementos propios del lenguaje, con el objetivo de volverlos más potentes y

fáciles de manejar.

El lenguaje C# ha dotado a las clases de una serie de elementos que en apariencia

amplían el conjunto de elementos definidos en la teoría general, pero más que eso, en

realidad lo que se busca es poner a disposición del programador toda una gama de

recursos que le permitan construir componentes de software que cumplan todos los

requerimientos de la programación orientada a objetos y permitan expresar los

elementos generales en la forma más efectiva y eficiente posible.

Estos son los elementos básicos que constituyen una clase en C#:

Constructores

Destructores

Constantes

Campos

Métodos

Propiedades

Indizadores

Operadores

Eventos

Delegados

Estructuras

Constructores

Un constructor es un método de una clase que se ejecuta automáticamente 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 método constructor que no hace absolutamente nada.

Además, 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 números complejos,

definida en el anterior capítulo, en las siguientes dos líneas de código, la primera línea

define un objeto de tipo Complejo y la segunda se encarga de llamar al constructor de

esa clase.

Page 2: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 PROGRAMACION CON C#

www.pedrov.phpnet.us

86868686

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 automática para dar una determinada configuración a

un objeto, o incluso para hacerlo funcional.

Un método constructor lleva el mismo nombre de la clase que lo contiene y se debe

declarar con nivel de accesibilidad pública (public), aunque también es admitido el

nivel interno (internal). El nivel de accesibilidad pública 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 método 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

parámetros que manejan. En el siguiente fragmento de código, la clase Complejo

implementa tres constructores,

public class Complejo

{

public Complejo() { }

public Complejo(string NumeroComplejo)

{

// Código para procesar el parámetro

}

public Complejo(double Real, double Imaginario)

{

// Código para procesar los parámetros

}

}

En el momento de la ejecución, de acuerdo a los parámetros que reciba el constructor,

el sistema decidirá a cual de estos constructores llamar. Si la declaración 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

parámetro un valor tipo cadena de texto.

Destructor

Un destructor es un método que se ejecuta automáticamente justo antes de que un

objeto sea destruido. A diferencias del constructor, un método destructor no puede

sobrecargarse ni tampoco heredarse. Además, no puede invocarse explícitamente.

El lenguaje C# cuenta con una herramienta llamada recolector de basura que se

encarga de destruir aquellos objetos que ya no se estén utilizando y aún sigan

Page 3: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#

[email protected]

87878787

ocupando espacio en memoria. Como ya se había mencionada anteriormente, el

operador new, aplicado a la creación 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 ejecución sale del ámbito que lo definió. En los ejemplos que

hemos analizado hasta el momento, la mayoría de objetos deja de ser referenciado

cuando se finaliza la ejecución del método Main.

La sintaxis para implementar el destructor es la siguiente:

~NombreClase()

{

// Acciones antes de destruir un objeto

}

En la práctica un método 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 configuración 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 prácticamente nada y

la segunda exige el código del estudiante.

/* Archivo: Ejemplo41.cs */

using System;

using System.Windows.Forms;

public class Estudiante

{

string código;

// Constructor por defecto

public Estudiante() { }

// Constructor con código del estudiante

public Estudiante(string CodigoEstudiante)

{

código = CodigoEstudiante;

MessageBox.Show("Código: " + código, "Construyendo...");

}

// Propiedad

public string Codigo

{

set { codigo = value; }

get { return codigo; }

}

// Destructor

~Estudiante()

{

MessageBox.Show("Ejecutando el destructor...", "Destruyendo...");

}

}

Page 4: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 PROGRAMACION CON C#

www.pedrov.phpnet.us

88888888

public class Programa

{

static void Main()

{

Estudiante alumno = new Estudiante();

//Estudiante escolar = new Estudiante("01");

}

}

Compile el archivo con la instrucción,

> csc ejemplo41.cs

Al ejecutar el programa se crea un objeto de tipo Estudiante pero, dado que la

ejecución llega al final del método Main, inmediatamente se inicia el proceso de

destrucción 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 ejecución e iniciar el proceso de destrucción, también 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 línea del programa y active la segunda. Vuelva a compilar el

programa y analice la ejecución. Observará que aparece la caja de mensajes del

constructor y esta se mantiene hasta hacer clic en el botón aceptar. La caja de mensajes

se mantiene por que el programa está en plena ejecución. Pero no ocurre lo mismo

cuando pasa al proceso de destrucción.

Métodos

Hasta este punto ya hemos trabajado con muchos métodos. Sabemos que un método es

lo que otros lenguajes de programación, sobre todo estructurados, se denominan

procedimientos o funciones. Además, se sabe que el principal método que dirige la

ejecución de un programa C# es el método Main, que es el punto por donde se inicia la

ejecución y la carga en memoria por parte del sistema operativo.

Los métodos 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 método existe en la medida que exista para un programador que

hace uso de una determinada clase, es decir un método publico, en la práctica se puede

hablar también de métodos privados, queriendo significar que son acciones internas

que se realizan con los elementos de un objeto.

En general, un método se define con una instrucción que tiene la siguiente sintaxis:

public tipo NombreMétodo(Argumentos)

{

// Implementación del método

return valor

}

Todo método que debe devolver un valor, el cual debe ser del mismo tipo que el

método. Los métodos que ejecutan acciones que no requieren la devolución de un

valor, se deben definir como void.

C# para permitirle al programador compartir métodos genéricos que se ejecutan sin

necesidad de hacer referencia a ningún objeto en particular, permite la definición de

métodos estáticos. Esto métodos, que se definen con el modificador static, se

Page 5: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#

[email protected]

89898989

comportan como las funciones o procedimientos de acceso publico que se utilizan en la

programación 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 actúa la clase en la cual se han incorporado.

Propiedades

Las propiedades tienen un aspecto similar a un método, pero no admiten argumentos.

Se utilizan para establecer o asignar valores a los atributos de un objeto. Aunque,

también 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 sección get se encarga de retornar un valor, ya sea de un atributo o como resultado

de un proceso de datos. La sección set, en cambio, si debe actuar directamente sobre

un atributo, ya que de otra forma no tendría 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 parámetros, pueden presentarse por parte del

programador dudas sobre lo que debe utilizarse: un método o una propiedad. No

existen reglas definidas sobre cual de los dos utilizar, y bajo que condiciones. Todo

depende del diseño o claridad que se desee darle al código de programación. Por

ejemplo, si se tiene una clase Persona, y se desea procesar a través de ella la edad de

una persona, con base en su fecha de nacimiento, que se encuentra incluida en algún

atributo, bien podría implementarse un método o una propiedad, y todo sería correcto.

Propiedades de solo lectura

Una propiedad de solo lectura es aquella que únicamente permite leer un dato

contenido en algún 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 sección set.

La implementación de una propiedad de solo lectura se logra incluyendo en el cuerpo

de la propiedad únicamente una sección 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

}

}

Page 6: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 PROGRAMACION CON C#

www.pedrov.phpnet.us

90909090

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 implementación solo debe incluirse la

sección 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 clasificación de las propiedades es lo que permite establecer

un buen nivel de encapsulamiento en las clases que se diseñen.

Sobrecarga de métodos

Sobrecargar un método significa implementar varios métodos de una clase con el

mismo nombre pero con diferentes parámetros, ya sea en cantidad o en tipo. C#,

también acepta diferencias en los valores devueltos por un método sobrecargado, pero

siempre y cuando esta no sea la única diferencia. Un método podría tener una versión

que devuelva un entero (int) y contar con tres parámetros, todos de tipo cadena

(string). Si otra versión del método devuelve un double, y tiene tres parámetros, 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 métodos,

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 compilación, dado que C#

tratará de identificar cada método a través de la cantidad de argumentos y sus tipos.

Como no encuentra diferencias que permitan una clara identificación, no se puede

terminar el proceso de compilación.

Page 7: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#

[email protected]

91919191

Ejemplo 4.2 Autómata que procesa direcciones web

En este ejemplo vamos a construir una clase con dos métodos para identificar si una

cadena de texto corresponde a una dirección web bien escrita, a un correo electrónico o

no corresponde a ninguno de los dos formatos. Los programas que se encargan de

realizar este tipo de análisis suelen denominarse autómatas.

Una de las tareas más complejas a las que se puede enfrentar un programador es la de

procesar cadenas de texto para determinar si están escritas de acuerdo a un formato o

sintaxis preestablecidos. En la mayoría de los casos esta tarea requiere la revisión, en

forma repetitiva, carácter por carácter, en busca de los patrones que han sido

estudiados y establecidos con anticipación. Una solución, dada por las Ciencias de la

Computación, para facilitar el estudio y análisis de las condiciones que se deben

establecer en el procesamiento de textos, es la teoría de los autómatas y los lenguajes

regulares, y más específicamente las llamadas expresiones regulares, un campo de

permanente investigación que ha permitido alcanzar los logros que hoy en día tienen a

la Computación y a la Informática 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 concatenación de otras cadenas. La definición exacta

de expresión regular se ha establecido a través de un intrincado conjunto de axiomas de

tipo matemático, que por ahora no entraremos a detallar. Tan solo vamos a dar una

noción breve del concepto de expresión 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 búsquedas 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 través del patrón “*.cs”. Esta es una forma de decirle

al sistema operativo que muestre solo los archivos cuyo nombre termine en los

caracteres “.cs”. Podríamos decir que para el sistema operativo la cadena *.cs es una

expresión regular.

A partir de aquí, y en lo que resta de esta práctica, para aquellos lectores que no están

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 básicas que se manejan en la teoría general de la computación. Supongamos

que tenemos un carácter a, entonces se representa:

Expresión regular Conjunto representado

ε Representa a la cadena vacía

a Representa a la cadena formada por a

a+ Todas las cadenas formadas por la concatenación de a, tales como a, aa, aaa, aaa, … etc.

a* Todas las cadenas formadas por la concatenación de a, incluyendo a la

cadena vacía. Es decir, a* = (a+) ∪ }{ε

Entonces, la expresión regular 01*, representa a todas las cadenas que empiezan por el

carácter 0 (cero) seguido de ninguno o cualquier cantidad de unos. Aquí, están

representadas cadenas como 0, 01, 011, 0111, etc. La expresión regular (ab)+c,

representa todas las cadenas que repiten la cadena ab, una o más veces, y terminan en

Page 8: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 PROGRAMACION CON C#

www.pedrov.phpnet.us

92929292

el carácter 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 disposición 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 teoría y las clases

que .NET pone disposición del programador para implementar un método que permita

analizar cadenas de texto y determinar si una cadena de texto corresponde a una

dirección web, a un correo electrónico o a ninguno de ellos.

.NET dispone de una mayor cantidad de elementos, o caracteres especiales, que

aquellos manejados en la teoría general computación para la construcción de las

expresiones regulares. Vamos a describir brevemente algunos de ellos, para utilizarlos

en nuestro ejemplo:

Caracteres especiales Descripción

[ ] Permiten determinar una lista de caracteres, de los cuales se escogerá uno. Por ejemplo, [0123] pone a disposición cualquiera

de estos dígitos 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 repetición 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 carácter que sea un carácter alfanumérico o el carácter de subrayado. También 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 expresión 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 dirección web debe empezar por la repetición del carácter w,

tres veces. Esto podemos expresarlo como,

[w]{3}

A continuación viene un punto. Este símbolo corresponde a un carácter 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 alfanuméricos,

donde no cabe la posibilidad de que sea una cadena vacía. Vamos a suponer que solo

se aceptan caracteres en minúsculas, por lo cual su representación puede hacerse como,

[a-z0-9]+

Page 9: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#

[email protected]

93939393

El tipo de dominio puede corresponder a una de las siguientes posibilidades: com, net,

info u org. En este caso existe una disyunción de la cual se debe escoger solo una

opción 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 expresión regular, respectivamente.

En definitiva la expresión regular que nos permitirá validar una dirección web es la

siguiente:

expresion = @"\A[w]{3}(\.)[a-z0-9]+(\.)(com|net|info|org)\Z";

El símbolo @ al inicio de la asignación le informa al compilador de C# que no

identifique en la cadena de texto las secuencias estándar de escape.

De la misma forma podemos crear una expresión regular que identifique a todas las

direcciones de correo electrónico que cumplan con el formato,

[email protected]

La siguiente es la expresión regular que identifica a este tipo de cadenas:

expresion = @"\A(\w+\.?\w*\@\w+\.)(com)\Z";

En esta expresión 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 expresión 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 método 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 expresión regular o no. La siguiente línea

define un objeto tipo Regex que procesa una expresión regular como las definidas en

este análisis:

Regex automata = new Regex(expresion);

La clase Regex exige que la expresión regular sea asignada en el momento de la

creación del objeto.

Con base en el anterior análisis vamos a programar un ensamblado, tipo librería

dinámica, que se encargará de recibir una cadena de texto y validarla de acuerdo a lo

antes requerido. Este ensamblado estará constituido básicamente por una clase,

llamada AutomataWEB, conformada por dos métodos estáticos, EsWeb y EsCorreo,

que servirán para validar las direcciones web y las direcciones de correo electrónico,

respectivamente.

/* Archivo: AutomataWEB.cs */

using System;

using System.Text.RegularExpressions;

public class AutomataWEB

{

Page 10: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 PROGRAMACION CON C#

www.pedrov.phpnet.us

94949494

public AutomataWEB()

{

}

// Método 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);

}

// Método para identificar direcciones de correo electrónico

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 instrucción,

> csc /t:library AutomataWEB.cs

Los métodos estáticos no pueden hacer parte de ningún 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 dirección 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 verificación con ayuda del autómata 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 minúsculas

if (AutomataWEB.EsWeb(cadena))

Console.Write("La cadena es una dirección web.");

else if (AutomataWEB.EsCorreo(cadena))

Console.Write("La cadena es una dirección de email.");

else

Console.Write("La cadena no es una dirección válida.");

}

}

Page 11: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#

[email protected]

95959595

Compile el programa con la instrucción,

> csc /r:AutomataWEB.dll ejemplo42.cs

Ejecute el programa resultante y pruebe su funcionamiento ingresando cadenas que

correspondan a direcciones web, correos electrónicos y otras que no cumplan con

ninguno de los dos formatos.

El estudio de las expresiones regulares requiere un análisis más profundo y detallado,

por lo cual este ejemplo se constituye solo en una breve introducción a las

potencialidades que ofrece este tema en las aplicaciones de software modernas. Una

descripción más detallada se realizará en un capítulo posterior dedicado

exclusivamente a este tema. Como ejercicio, el lector debería modificar los métodos

programados aquí para validar cualquier dirección web, incluyendo todos los tipos de

dominio y las abreviaturas de los países 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 construcción de la clase Complejo que se ha

tomado como modelo de las descripciones de este capítulo. Sabemos que, todo

complejo tiene la forma a + bi, y como tal tiene su validez dentro de las matemáticas.

Siempre que se necesite trabajar con un complejo, las cosas serían más fáciles si tanto

la asignación como el valor retornado por una variable de este tipo se hacen en

términos de la forma matemática de un número complejo. Sería ideal poder realizar

una asignación 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

numérico válido, por ahora no es posible en el nivel de programación lograr esta

pretensión. Sin embargo, si podemos hacer una aproximación de este tipo de

asignación 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 números complejos sea totalmente transparente.

En este punto, nos enfrentamos a una situación bastante particular. Debemos

programar una validación que se encargue de reconocer un número complejo en una

cadena de texto, interpretándola 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 número complejo válido.

Existen diversas formas para expresar un número complejo. Se sabe que un mismo

número complejo se puede escribir en formas equivalentes, tales como 2 + 3i, 2 + i3, 3i

+ 2, i3 + 2, y cualquiera de ellas es válida. Además existen complejos cuya forma,

debido a las propiedades matemáticas, 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 también que

0 + 4i es igual a 4i. Incluso, cualquier número real puede considerarse como un

complejo de parte imaginaria igual a cero.

La clase Complejo debe poseer la capacidad suficiente de recibir un número complejo

en cualquiera de sus formatos válidos y procesarlo adecuadamente. En general, y para

facilitar la comprensión de las descripciones algoritmicas vamos a dividir en cuatro

grupos los formatos en que puede estar escrito cualquier número complejo:

Page 12: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 PROGRAMACION CON C#

www.pedrov.phpnet.us

96969696

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 sintácticas no se han incluido los signos, positivo o negativo,

que pueden adoptar la parte real y la parte imaginaria de un complejo. Más adelante, en

cada caso particular entraremos a considerar este aspecto. Tampoco, el agrupamiento

realizado obedece a ninguna regla específica relacionada con estos números, sino

únicamente se busca agruparlos de acuerdo a algunas características de forma que

pueden convenientemente facilitar la generalización de cada uno de ellos. El primer

grupo es el formato más usual entre los matemáticos, la parte real va en primera

instancia y la parte imaginara después. El tercer formato suele encontrárselo con

mucha frecuencia en libros de ingeniería. 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 relación 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 través de expresiones regulares que representen a los grupos de la tabla anterior,

para identificar si corresponden o no a números 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 versión antigua del componente. Lo primero que se debe hacer es incluir

dos nuevas líneas de código 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

numéricos 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 número representado, al

convertirla a tipo double se puede presentar una inconsistencia de interpretación si en

la configuración 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 máquina actual para mostrar números

con parte entera y decimal. Para evitar inconsistencias se adecuaran los números al

formato que maneje la máquina donde se esté trabajando.

En la siguiente línea de código, la propiedad CurrencyDecimalSeparator devuelve

una cadena tipo string con el separador decimal que utiliza el sistema operativo en la

máquina actual:

sd = NumberFormatInfo.CurrentInfo.CurrencyDecimalSeparator;

El punto central de este ejemplo, es construir un método que le permita a las variables

instanciadas de la clase Complejo recibir una cadena de texto y validarla para

determinar si corresponde a un número complejo. Para ello vamos a determinar los

patrones que pueden determinarlos.

Page 13: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#

[email protected]

97979797

Sabemos que, un número es una cadena de dígitos 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 número tendría

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 válido

el separador decimal definido por el sistema, sd, entonces introducimos una

concatenación con este, así:

numero = @"(\d+(" + sd + @")?\d*)";

Todo número complejo, exceptuando aquellos que no posean parte imaginaria nula,

incluyen un literal que representa a la raíz cuadrada de -1. Este generalmente se

simboliza con la letra minúscula i. Definimos este símbolo en la siguiente forma:

i = @"(i)";

El signo que puede anteceder a un número puede ser positivo, (+), o negativo, (-).

Sabemos que para expresar opción en la escogencia de uno u otro símbolo se utilizan

los corchetes. Por lo tanto el signo de un número, en términos de expresión regular de

.NET, quedaría 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 más general, un complejo puede estar formado por una parte real y una parte

imaginaria, a + bi, que queda incluida en una expresión regular como la siguiente:

expresion1 = real + imaginario;

Pero, para incluir en la expresión 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 número que la acompaña.

imaginario = "(" + signo + "(" + numero + ")?" + i + ")?";

Page 14: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 PROGRAMACION CON C#

www.pedrov.phpnet.us

98989898

Con estas modificaciones, e indicándole 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 expresión 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

número complejo. La siguiente línea define el objeto complejo1 con la expresión

regular antes analizada:

Regex complejo1 = new Regex(expresion1);

La comprobación de la cadena entrante, puede realizarse mediante el método booleano

IsMatch del objeto complejo1. Así,

if (complejo1.IsMatch(cadena)) return true;

De la misma forma como se realizó la expresión regular para el primer grupo de

complejos, puede definirse las expresiones regulares para los otros casos de posibles

formatos de números complejos. En definitiva, con estos elementos construimos el

método booleano EsComplejo, en la siguiente forma:

// Método para válidar un número 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 básicos de un complejo

string numero = @"(\d+(" + sd + @")?\d*)";

string i = @"(i)";

string signo = @"([+-])";

// Validación 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;

// Validación 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;

// Validación 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;

// Validación para a + ib

real = signo + "?" + numero;

imaginario = signo + i + numero;

string expresion4 = @"\A" + real + imaginario + @"\Z";

Page 15: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#

[email protected]

99999999

Regex complejo4 = new Regex(expresion4);

return complejo4.IsMatch(cadena);

}

Se ha incluido una llamada a un método denominado QuitarEspacios, que se encarga

de eliminar todos los espacios en blanco que puedan existir en la cadena de texto.

Aunque la inclusión de los espacios pudo haberse considerado en las expresiones

regulares de cada uno de los casos, esto haría un tanto más compleja su estructuración,

por lo que se ha optado por el camino más fácil, ¡quitarlos! Este método recibe una

cadena de texto, y a través de una expresión regular apropiada, busca uno o más

espacios y los reemplaza con una cadena vacía.

private string QuitarEspacios(string cadena)

{

Regex espacio = new Regex(@"\s+");

cadena = espacio.Replace(cadena, "");

return cadena;

}

Una vez que se ha determinado la validez de una cadena de texto como número

complejo, es necesario separar sus partes real e imaginaria para asignarlas a sus

respectivos atributos. El método 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.

// Método 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;

Page 16: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 PROGRAMACION CON C#

www.pedrov.phpnet.us

100100100100

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 expresión regular que se utiliza en este método existen dos particularidades. La

primera es que no se busca un sola coincidencia en toda la cadena (aunque, bien podría

haberse hecho), por que se supone que la cadena analizada ya está comprobado que

corresponde a un número 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 expresión regular. La razón, en este punto ya se sabe que la

parte imaginaria está bien escrita y por lo tanto todo lo que se encuentre será válido.

imaginaria = signo + "?" + numero + "?" + i + numero + "?";

Cuando se analiza una cadena a través de una expresión regular, el motor de análisis

busca todas las subcadenas que hagan parte de esa familia y las va guardando en un

objeto de tipo MatchCollection. Para recuperar la colección de cadenas objetivo

encontradas existe el método Matches que hace parte de los objetos de tipo Regex. En

la siguiente línea 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 método, y los anteriores, estamos listos para

ampliar y mejorar las capacidades de nuestra clase Complejo.

Se pondrá a disposición del usuario de la clase, tres constructores sobrecargados. El

primero no pide ningún dato de entrada. El segundo método da la posibilidad de

ingresar los valores real e imaginario del complejo y el tercer método 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 números. El

siguiente método privado se encarga de preparar la salida de un complejo en forma de

cadena de texto, con el formato a + bi.

Page 17: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#

[email protected]

101101101101

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();

}

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 implementación:

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 numéricos 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

método WriteLine, en el formato adecuado. Esto mejora el nivel de abstracción de la

clase Complejo y le asegura a sus objetos un comportamiento más cercano a los

valores numéricos, facilitando su manejo por parte de cualquier programador. Para

lograr esto es necesario sobrescribir el método 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 genérica que forma parte de la raíz 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 método 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 mayoría de los casos cuando se

imprime un objeto, sin especificar ninguna propiedad, este método devuelve el nombre

completo del objeto. En nuestro caso vamos a sobrescribir el método para obligarlo

escribir el valor del número complejo. Así:

// Sobrecarga del método ToString

Page 18: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 PROGRAMACION CON C#

www.pedrov.phpnet.us

102102102102

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(); }

}

Page 19: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#

[email protected]

103103103103

public string Valor

{

get { return FormatoSalida(); }

set

{

if (EsComplejo(value))

PartesComplejo(value);

else

{

real = 0;

imaginario = 0;

}

}

}

// Sobrecarga del método ToString

public override string ToString()

{

return FormatoSalida();

}

// Métodos privados

private double Tamano()

{

double c;

c = Math.Sqrt(real * real + imaginario * imaginario);

return c;

}

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)

alfa = - Math.PI / 2;

else

alfa = 0;

return alfa;

}

// Método para válidar un número 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 básicos de un complejo

string numero = @"(\d+(" + sd + @")?\d*)";

string i = @"(i)";

Page 20: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 PROGRAMACION CON C#

www.pedrov.phpnet.us

104104104104

string signo = @"([+-])";

// Validación 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;

// Validación 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;

// Validación 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;

// Validación 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);

}

// Método 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

Page 21: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#

[email protected]

105105105105

parteImag = parteImag.Replace("i", "");

// Eliminar la parte imaginaria

parteReal = imaginario1.Replace(cadena, "");

}

else

{

parteReal = cadena;

parteImag = "0";

}

// Verificar la cadenas de texto vacías

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();

}

}

Compile este archivo en un ensamblado tipo librería dinámica, con la instrucción,

> 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 número complejo: ");

z.Valor = Console.ReadLine();

Console.Write("z = {0}\n", z);

Console.Write("a = {0}; b = {1}\n", z.Real, z.Imaginario);

Console.Write("Módulo: {0}\n", z.Modulo);

Page 22: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 PROGRAMACION CON C#

www.pedrov.phpnet.us

106106106106

Console.Write("Argumento: {0}", z.Argumento);

// 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("Módulo: {0}\n", w.Modulo);

Console.Write("Argumento: {0}", w.Argumento);

}

}

Compile este archivo con la instrucción de línea de comandos,

> csc /r:Complejo.dll ejemplo43.cs

Ejecute el programa resultante y analice el comportamiento de cada línea 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

versión desarrollada en los anteriores ejemplos. Esta es una característica 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 editándose y aumentando

sus elementos y mantener la compatibilidad hacia versiones anteriores.

Sobrecarga de operadores

El concepto de sobrecarga también 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 más

conocido es el operador sobrecargado es +, quién tiene una versión para valores

numéricos y otra para valores tipo cadena de texto. Cuando el operador se aplica a dos

valores que representan cantidades numéricas, realiza una suma matemática, pero

cuando se aplica a dos cadenas de texto, produce como resultado una cadena que es la

concatenación de las dos primeras. Las siguientes líneas de código muestran un

ejemplo típico:

int a = 5 + 7;

string c = "Hola" + "Mundo";

En la variable entera a se almacena el valor numérico 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 programación la claridad y

naturalidad suficientes para hacer de las operaciones con objetos un trabajo fácil 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 podría hacer una sobrecarga para el operador +, de tal manera que al

aplicarse a un cierto tipo de datos numéricos produjera una multiplicación, algo que

haría perder innecesariamente la lógica del lenguaje de programación. Todo operador,

al momento de sobrecargarse, debe ejecutarse acorde a la función 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 programación es actuar como intermediario en el

proceso de comunicación entre la máquina y el ser humano, y por lo tanto debe buscar

ser lo más claro posible.

Page 23: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#

[email protected]

107107107107

Para sobrecargar un operador se utiliza un método estático 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)

{

// Implementación

}

En forma similar se sobrecarga un operador binario:

public static TipoDevuelto operator operador (Tipo1 operando1,

Tipo2 operando2)

{

// Implementación

}

Aunque C# no permite la sobrecarga de todo los operadores que maneja, si lo hace

para la mayoría de operadores relacionados con las matemáticas y los bits. En la

siguiente lista se muestran todos los operadores que admiten sobrecarga:

Operadores unarios +, -, !, ~, ++, --, true, false

Operadores binarios +, -, *, /, %, &, |, ^, <<, >>, ==, !=,

>, <, >=, <=

Ejemplo 4.4 Operaciones con complejos

En este ejemplo vamos a sobrecargar los operadores matemáticos 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 debería recurrir

a la definición matemática y aplicar el proceso con las partes de los complejos que se

vayan a operar.

Comencemos con la suma de números complejos. Las matemáticas la definen de la

siguiente forma: si se tienen dos complejos 1z y 2z , la suma de ellos es un número

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

notación matemática:

Si 1 1 1z a b i= + y

2 2 2z a b i= + , entonces 1 2 1 2 1 2( ) ( )z z a a b b i+ = + + +

Una posible solución al problema de programar la suma de complejos, podría ser la

definición de un método estático que se encargue de recibir como parámetros dos

complejos, realizar la suma utilizando la definición matemática y devolver el resultado

en términos de una variable compleja. Este método, implementado por la clase

Complejo, bien podría 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;

}

Page 24: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 PROGRAMACION CON C#

www.pedrov.phpnet.us

108108108108

La solución es buena, y de hecho funciona muy bien. En las siguientes líneas de código

se muestra como debería utilizarse el método Suma:

Complejo z1 = new Complejo("5 + 3i");

Complejo z2 = new Complejo("8 - 2i");

Complejo z = new Complejo();

z = Complejo.Suma(z1, z2);

Esta codificación de operaciones matemáticas, basada en llamadas a métodos, puede

resultar molesta en situaciones donde se van a realizar operaciones de uso muy común.

Un programador que haga uso de los complejos, talvez preferiría codificar una suma en

forma más natural, o por lo menos como está acostumbrado a hacerlo con los demás

números que maneja el lenguaje de programación, así

suma = z1 + z2;

Para lograr esto es necesario sobrecargar el operador +, indicándole cual debe ser el

proceso a seguir cuando se aplique a números complejos.

El siguiente método 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;

}

Como puede observarse, el método de sobrecarga hace exactamente lo que debería

hacer el programador usuario de la clase. La ventaja es que solo se programa aquí, y en

adelante bastará con aplicar una operación de suma común y corriente, como si de otro

número cualquiera se tratará.

Antes de sobrecargar las demás operaciones vamos sobrecargar el operador inverso

aditivo, o signo negativo, (-), el cual invierte el signo de las partes que conforman un

complejo. Matemáticamente establece:

Si z a bi= + entonces z a bi− = − −

Entonces la sobrecarga del operador inverso aditivo, o signo negativo, queda como

sigue:

public static Complejo operator -(Complejo z)

{

Complejo inverso = new Complejo();

inverso.real = - z.real;

inverso.imaginario = - z.imaginario;

return inverso;

}

A su vez, la resta de complejos puede definirse a través de la suma, así:

1 2 1 2( )z z z z− = + −

Con lo que la sobrecarga del operador resta sigue la misma noción.

public static Complejo operator -(Complejo z1,Complejo z2)

{

Page 25: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#

[email protected]

109109109109

Complejo resta = z1 + (- z2);

return resta;

}

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

número de operandos sobre los cuales actúa. 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 multiplicación de complejos se obtiene realizando una multiplicación polinomial de

los dos operandos. En general esta operación se simplifica en el siguiente resultado:

Si 1 1 1z a b i= + y

2 2 2z 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 definición, la sobrecarga del operador multiplicación, *, queda así:

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;

}

Pero la multiplicación no solo puede darse entre números complejos, también puede

multiplicarse un numero real por un complejo. Para lograr esto es necesario aplicar dos

sobrecargas más 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

definición matemática de esta multiplicación 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 multiplicación 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 operación propia de los complejos, que no esta definida para ningún otro

tipo numérico. Es el conjugado de un complejo. Esta operación, lo único que hace es

invertir la parte imaginaria del número complejo al cual se aplica.

Si z a bi= + , el conjugado de z se define como z a bi= −

Page 26: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 PROGRAMACION CON C#

www.pedrov.phpnet.us

110110110110

En C# no existe un operador cuya funcionalidad tenga alguna relación con el

conjugado de un complejo. En vista de esto vamos a sobrecargar el operador !

(negación lógica). La sobrecarga queda como sigue:

public static Complejo operator !(Complejo z)

{

Complejo conjugado = new Complejo();

conjugado.Real = z.Real;

conjugado.Imaginario = - z.Imaginario;

return conjugado;

}

La división de números complejos se puede definir, con base en el conjugado y el

módulo del divisor, en la siguiente forma:

11 22

2 2

1zz z

z z= ⋅

Por lo tanto, el método que sobrecarga el operador división, /, se puede implementar

como sigue:

public static Complejo operator /(Complejo z1,Complejo z2)

{

Complejo cociente;

cociente = 1 / Math.Pow(z2.Modulo, 2) * (z1 * !z2);

return cociente;

}

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;

Page 27: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#

[email protected]

111111111111

}

}

// 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 método 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();

Page 28: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 PROGRAMACION CON C#

www.pedrov.phpnet.us

112112112112

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;

}

// Métodos privados

private double Tamano()

{

double c;

c = Math.Sqrt(real * real + imaginario * imaginario);

return c;

}

Page 29: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#

[email protected]

113113113113

// 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)

alfa = - Math.PI / 2;

else

alfa = 0;

return alfa;

}

// Método para válidar un número 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 básicos de un complejo

string numero = @"(\d+(" + sd + @")?\d*)";

string i = @"(i)";

string signo = @"([+-])";

// Validación 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;

// Validación 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;

// Validación 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;

// Validación para a + ib

real = signo + "?" + numero;

imaginario = signo + i + numero;

string expresion4 = @"\A" + real + imaginario + @"\Z";

Regex complejo4 = new Regex(expresion4);

Page 30: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 PROGRAMACION CON C#

www.pedrov.phpnet.us

114114114114

return complejo4.IsMatch(cadena);

}

// Método 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 vacías

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;

}

Page 31: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#

[email protected]

115115115115

// 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 instrucción,

> 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 números que maneja C#. La

sobrecarga de los operadores aritméticos nos permitirá codificar las operaciones de este

tipo en la misma forma como se hace con cualquier otro tipo numérico. Aunque talvez

no es el mejor, esta clase es un buen ejemplo de abstracción y encapsulamiento, lo cual

permite contar con tipos complejos con un buen nivel de autonomía para resolver la

mayoría de problemas propios de su naturaleza.

El siguiente programa hace uso de la clase Complejo y realiza algunas operaciones

con números 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 compílelo con la instrucción,

> csc /r:Complejo.dll ejemplo44.cs

Page 32: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 PROGRAMACION CON C#

www.pedrov.phpnet.us

116116116116

El lector podrá comprobar que la clase Complejo define objetos que en forma

autónoma se encargan de realizar la mayoría de térreas 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 abstracción 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 número complejo, la clase no posee un mecanismo para

informar directamente sobre esa situación anómala y en vez de eso asume un valor

nulo sin que el usuario se entere de tal situación. Se podría implementar un mecanismo

de mensajes para informar al usuario que existe un error en la asignación de un valor,

pero esto podría afectar la generalidad del componente y limitarlo a un único entorno

de ejecución.

El objetivo es crear un componente de software útil en cualquier entorno, consola,

sistema gráfico de Windows o web. En las siguientes secciones se describirán

elementos de la programación con C# que permiten dar mayor robustez a los

componentes de software, y con ellos podremos resolver en forma técnica las

deficiencias de nuestra clase Complejo.

Eventos

Un evento es una acción que produce un componente y a la que otro componente

puede responder o puede controlar mediante código. Los eventos más conocidos son

aquellos que se producen por acción del usuario, por ejemplo, al hacer clic con el botón

principal del ratón sobre un botón de una ventana se produce un evento que a su vez

ejecuta un código de programación. Sin embargo, esta última asociación didáctica para

intentar explicar el concepto de evento, más que ayudar, puede distorsionar la noción

que sobre el mismo impone la programación orientada a objetos.

En la práctica 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 método) 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 interés. Una noción muy general de cómo 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 implementación 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 métodos que se ejecuten cuando cada uno de estos objetos,

internamente, hace el llamado al procedimiento especial que hemos denominado

MiEvento.

class ClaseB

{

Page 33: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#

[email protected]

117117117117

ClaseA a1 = new ClaseA();

ClaseA a2 = new ClaseB();

A MiEvento de a1 asociar MetodoA1;

A MiEvento de a2 asociar MetodoA2;

MetodoA1()

{

Implementación de MiEvento para a1;

}

MetodoA2()

{

Implementación 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, adecuándolas a sus intereses y a la

forma como desee personalizar el manejo de los componentes en cada caso específico.

Incluso puede no hacer ninguna implementación.

Vistas las cosas como nos las muestra este esquema, podemos decir que un evento es

una señal que envían los objetos a1 y a2, descendientes de la clase ClaseA, hacia la

clase ClaseB y que en este son respondidas mediante los métodos MetodoA1 y

MetodoA2, respectivamente.

En el manejo de un evento es importante tener en cuenta tres elementos básicos que

intervienen: el componente que genera el evento, un manejador de eventos y un

método que responde a la señal.

Figura 3.14: Un componente genera un evento que es controlado por un manejador de

eventos, quien decide cual es el método que se debe ejecutar.

La mayoría 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 aplicación donde se vayan a utilizar, y de esta manera imprimir mayor versatilidad

a la reutilización de componentes. La programación de eventos facilita enormemente la

adecuación 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 programación, 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

mayoría de los casos automatizan el proceso de creación de los eventos y ponen a

disposición del programador el espacio definitivo donde se requiere su intervención.

Sin embargo, conocer como se implementa un evento puede ayudarle a sacar mayor

provecho de estos y la programación de sus propios objetos con eventos adecuados.

Además, en muchos casos los programadores tenemos la tendencia a relacionar los

eventos únicamente con objetos gráficos, lo cual dificulta su concepción y utilización

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

Método

Page 34: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 PROGRAMACION CON C#

www.pedrov.phpnet.us

118118118118

El proceso de creación y programación de un evento requiere la realización de una

serie de pasos que pueden hacer de este una tarea confusa. Para la descripción lo

hemos dividido en dos etapas: implementación y control (o respuesta).

Implementación de un evento

Teniendo en cuenta el esquema utilizado en los párrafos anteriores, la implementación

es el trabajo que se debe hacer por fuera de la clase ClaseB. En la implementación 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 método. 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 explicación, los anteriores elementos se definen

por fuera de la clase ClaseA y, obviamente, también de la clase ClaseB. Lo que

viene en seguida es lo que se debería 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 declaración 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 provocaría un error en tiempo de ejecución.

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 quién 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 enésima suma de un número. Un objeto definido a partir de la clase, recibirá

un número entero positivo y realizará una suma secuencial desde 1 hasta dicho valor.

Page 35: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#

[email protected]

119119119119

Además, cada vez que se realice una suma parcial, el objeto emitirá un mensaje de

aviso a la clase que lo contiene.

Para hacer más fácil la descripción no utilizaremos un evento con parámetros, 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 números mediante un ciclo

que irá realizando la suma 1 + 2 + 3 + …, hasta el valor del número pasado a

cualquiera de sus objetos.

public class Sumador

{

// Declaración del evento

public event EventoSumador SumaParcial;

// Atributos

int numero;

// Constructor

public Sumador(int valorNumero)

{

numero = valorNumero;

}

// Método 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()

{

Page 36: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 PROGRAMACION CON C#

www.pedrov.phpnet.us

120120120120

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 análisis, nuestro archivo fuente, al que

llamaremos Sumador.cs, queda así:

/* Archivo: Sumador.cs */

using System;

public delegate void EventoSumador();

public class Sumador

{

// Declaración del evento

public event EventoSumador SumaParcial;

// Atributos

int numero;

// Constructor

public Sumador(int valorNumero)

{

numero = valorNumero;

}

// Método 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 librería dinámica, con la instrucción,

> csc /t:library Sumador.cs

Con esto tenemos un componente que define objetos que son capaces de provocar un

evento. La siguiente parte consistiría 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

Page 37: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#

[email protected]

121121121121

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 método que

debe tener la misma firma de método (el mismo tipo e iguales parámetros) que el

delegado declarado para el evento.

tipo MetodoEvento(object ObjetoEmisor, ClaseArgumentosEvento e)

{

// Implementación 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 método, 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 método para su evento

SumaParcial.

El método que controlará el evento lo definiremos así:

static void RSumaParcial()

{

Console.WriteLine("Evento de r...");

}

Este método, ú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

método asignándolo al evento SumaParcial de r.

r.SumaParcial += new EventoSumador(RSumaParcial);

Esta asignación debe hacerse dentro de un método de la clase, después de haber creado

el objeto que provoca el evento. La mayoría de aplicaciones desarrolladas con ayuda

de un entorno integrado de desarrollo realizan esta asignación en un método de carga

inicial que es llamado directamente por el método Main, o en su defecto lo incluyen en

el cuerpo de este método. Sin embargo, esto no significa que obligatoriamente deba

realizarse de esa manera, ya que el programador puede realizarlo en cualquier otro

método aunque sus efectos pueden tener algunas variaciones. Es importante tener en

cuenta que el evento no llama a su método controlador hasta tanto no se haya ejecutado

el método 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()

Page 38: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 PROGRAMACION CON C#

www.pedrov.phpnet.us

122122122122

{

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 instrucción,

> csc /r:Sumador.dll ejemplo46.cs

Al ejecutar el programa, se carga en el objeto el valor 10, lo cual implica que se

deberán realizar diez ciclos y de hecho se llama igual número 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 método controlador del evento se ha definido privado y estático.

Ninguna de las dos cosas es un requisito impuesto por las reglas de control de eventos.

El método definido es uno más de los muchos que el programador puede implementar,

y como tal puede tener cualquier nivel de accesibilidad. La opción de estático es un

requisito impuesto por el método Main. Cuando se hace el registro del método

controlador,

r.SumaParcial += new EventoSumador(RSumaParcial);

al incluir unicamente el nombre del método, implícitamente se produce una referencia

al objeto que contiene a dicho método. Como esta línea se encuentra en el cuerpo de un

método estático, no es posible hacer referencia a un objeto de la misma clase y en

consecuencia la única solución es definir un método estático, 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 comprensión de su lógica de implementación y control por parte del

lector poco experimentado en el tema.

Ejemplo 4.7 Un evento con argumentos

Basándonos 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

Page 39: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#

[email protected]

123123123123

entrada y el otro corresponderá a una nueva versión 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 también 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 señal al cliente y no le retorna

ningún parámetro. 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 mayoría de eventos de .NET.

Los eventos generados por los objetos de .NET mantienen un esquema de

presentación. Todo evento devuelve dos parámetros: 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 numéricos relacionados con la sumatoria. La devolución de un error

se realizará a través de los atributos Error y MensajeError. Un tercer dato numérico,

Valor, servirá para retornar cualquier valor numérico 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 común 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 línea de jerarquía 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 definición de dicho manejador queda como

sigue:

public delegate void EventoSumador(Object emisor,

SigmaArgumentosEvento e);

Page 40: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 PROGRAMACION CON C#

www.pedrov.phpnet.us

124124124124

El primer parámetro del manejador de eventos nos permitirá enviar una referencia al

objeto que generó el evento. Este parámetro puede ser importante en un momento dado

para determinar quién generó el evento, sobretodo por que, en la práctica, puede ser

necesario hacer que un mismo método controle a varios eventos.

A continuación 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 versión

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 definición 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 través de la propiedad Valor, de dicho evento. Dado que la generación de

un mismo evento puede necesitarse hacer desde diferentes miembros de la clase es

recomendable incluirla en un método como el siguiente:

private void LlamarSumaParcial(int suma)

{

SigmaArgumentosEvento e=new SigmaArgumentosEvento();

e.Valor = suma;

if (SumaParcial != null)

{

SumaParcial(this, e);

}

}

De esta manera cualquier miembro de la clase puede llamar únicamente al método para

generar un determinado evento. Este no es un requisito de programación, pero si ayuda

a hacer más claro el código y su mantenimiento.

Observe que el evento SumaParcial, a través 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 diseñado 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 número asignado para la sumatoria sea menor que 1. El

método generador del evento es el siguiente:

private void LlamarEntradaNumero()

{

SigmaArgumentosEvento e=new SigmaArgumentosEvento();

if (numero < 1)

{

e.Error = true;

e.MensajeError="El número ingresado

es incorrecto.";

e.Valor = numero;

}

if (EntradaNumero != null)

{

EntradaNumero(this, e);

}

}

Page 41: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#

[email protected]

125125125125

En definitiva, y con las explicaciones mostradas en los comentarios de codificación, 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();

}

}

// Método 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;

}

// Métodos 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 número ingresado

es incorrecto.";

Page 42: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 PROGRAMACION CON C#

www.pedrov.phpnet.us

126126126126

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

Page 43: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#

[email protected]

127127127127

{

numero = value;

LlamarEntradaNumero();

}

}

// Método 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;

}

// Métodos 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 número ingresado es incorrecto.";

e.Valor = numero;

}

if (EntradaNumero != null)

{

EntradaNumero(this, e);

}

}

}

Compile el archivo en un ensamblado dll con la instrucción,

> csc /t:library Sigma.cs

El siguiente programa utiliza dos objetos derivados de la clase Sigma, e implementa los

métodos que controlan sus eventos..

/* Archivo: ejemplo47.cs */

using System;

using System.Windows.Forms;

public class Programa

Page 44: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 PROGRAMACION CON C#

www.pedrov.phpnet.us

128128128128

{

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);

}

}

}

Compile el programa con la instrucción,

> csc /r:Sigma.dll ejemplo47.cs

Un aspecto importante. El evento EntradaNumero de los dos objetos, suma1 y

suma2, ha sido controlado por un mismo método, SumaEntradaNumero. Este método

muestra, en una caja de mensajes, información relacionada con el objeto que genera el

evento, específicamente el valor numérico que se ha ingresado. Aquí es donde se

aprovecha el primer parámetro del evento para conocer cual fue el objeto que lo

generó.

La forma como se nombran los métodos controladores es muy importante cuando se va

a trabajar con muchos objetos, para evitar confusiones y facilitar el mantenimiento del

código. Aquí se ha seguido las recomendaciones hechas por la documentación del

Page 45: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#

[email protected]

129129129129

Framework de .NET. Un método 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 más 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 versión 3.0). Ese evento se llama CancelKeyPress y se

genera cada que el usuario de una aplicación cancela, en forma forzada, la finalización

de una aplicación de consola presionando las teclas CTRL+C.

En este ejemplo vamos a programar un método para controlar el evento

CancelKeyPress y aprovecharlo para mostrar un mensaje de cancelación en una caja

de texto. El siguiente es el método que controla este evento:

static void ConsoleCancelKeyPress(object emisor, EventArgs e)

{

MessageBox.Show("Operación cancelada...");

}

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 método 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 acción:

/* 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 aplicación aún no ha terminado . . .");

Console.ReadKey(true);

}

static void ConsoleCancelKeyPress(object emisor, EventArgs e)

{

MessageBox.Show("Operación cancelada ...", "Cancelado");

}

}

Compile el programa con la instrucción,

> csc ejemplo.cs

El programa se ejecuta hasta la línea

Console.ReadKey(true);

Page 46: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 PROGRAMACION CON C#

www.pedrov.phpnet.us

130130130130

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 combinación 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 práctica vamos a desarrollar un evento generado por la clase Complejo,

después de que se ingresa y verifica un valor complejo en forma de cadena de texto. A

través 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 validación 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;

// Implementación de Complejo...

}

A su vez, definimos un método que se encargue de generar el evento. Esto nos permite

hacer la llamada desde más 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);

}

}

Page 47: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#

[email protected]

131131131131

Como ya se dijo este evento será generado desde los métodos 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;

Page 48: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 PROGRAMACION CON C#

www.pedrov.phpnet.us

132132132132

// 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(); } }

Page 49: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#

[email protected]

133133133133

public double Argumento

{

get { return Angulo(); }

}

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);

}

}

// Sobrecarga del método 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();

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)

{

Page 50: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 PROGRAMACION CON C#

www.pedrov.phpnet.us

134134134134

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;

}

// Métodos privados

private double Tamano()

{

double c;

c = Math.Sqrt(real * real + imaginario * imaginario);

return c;

}

// 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)

Page 51: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#

[email protected]

135135135135

alfa = - Math.PI / 2;

else

alfa = 0;

return alfa;

}

// Método para válidar un número 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 básicos de un complejo

string numero = @"(\d+(" + sd + @")?\d*)";

string i = @"(i)";

string signo = @"([+-])";

// Validación 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;

// Validación 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;

// Validación 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;

// Validación 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);

}

// Método 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*)";

Page 52: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 PROGRAMACION CON C#

www.pedrov.phpnet.us

136136136136

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 vacías

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)

{

Page 53: Programacion c sharp_04

CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#

[email protected]

137137137137

ComplejoArgumentosEvento e = new ComplejoArgumentosEvento();

e.Error = existeError;

if (NumeroComprobado != null)

{

NumeroComprobado(this, e);

}

}

}

Vuelva a compilar el archivo con la instrucción,

> csc /t:library Complejo.cs

El siguiente programa utilice el evento NumeroComprobado para mostrar al usuario un

mensaje informándole si el número 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();

}

static void ZetaNumeroComprobado(object emisor, ComplejoArgumentosEvento e)

{

if (e.Error)

Console.WriteLine("Incorrecto");

else

Console.WriteLine("Correcto...");

}

}

Compile el programa con el comando,

> csc /r:Complejo.dll ejemplo49.cs

En este caso se utilizó la consola para enviar un mensaje al usuario, pero también pudo

haberse programado en una caja de mensajes gráfica. Esto le da más 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.