programacion c sharp_04
DESCRIPTION
Programación básica c sharpTRANSCRIPT
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.
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
CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#
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...");
}
}
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
CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#
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
}
}
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.
CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#
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
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]+
CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#
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,
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
{
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.");
}
}
CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#
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:
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.
CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#
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 + ")?";
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";
CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#
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;
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.
CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#
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
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(); }
}
CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#
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)";
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
CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#
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);
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.
CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#
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;
}
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)
{
CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#
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= −
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;
CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#
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();
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;
}
CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#
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);
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;
}
CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#
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
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
{
CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#
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
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.
CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#
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()
{
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
CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#
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()
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
CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#
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);
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);
}
}
CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#
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.";
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
CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#
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
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
CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#
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);
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);
}
}
CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#
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;
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(); } }
CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#
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)
{
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)
CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#
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*)";
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)
{
CAPITULO CAPITULO CAPITULO CAPITULO 4444 ELEMENTOS DE UN CLASE C#
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.