Programaci´ on Orientada a Objetos C#
F´elix G´omez M´armol 3o Ingenier´ıa Inform´atica Julio de 2004
2
´Indice general ´ Indice General
4
2. Clases y Objetos 2.1. Clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.1.1. Estructura . . . . . . . . . . . . . . . . . . . . . . . 2.1.2. Ocultaci´on de la informaci´on . . . . . . . . . . . . . 2.1.3. Relaciones entre clases: Cliente-Servidor y Herencia 2.1.4. Visibilidad . . . . . . . . . . . . . . . . . . . . . . . 2.2. Objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.3. Mensajes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.3.1. Sintaxis. Notaci´on punto . . . . . . . . . . . . . . . . 2.3.2. Sem´antica . . . . . . . . . . . . . . . . . . . . . . . . 2.4. Sem´antica referencia versus sem´antica almacenamiento . . . 2.5. Creaci´on de objetos . . . . . . . . . . . . . . . . . . . . . . 2.5.1. Destructores . . . . . . . . . . . . . . . . . . . . . . 2.6. Sem´antica de la asignaci´on e igualdad entre objetos . . . . . 2.7. Genericidad . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.8. Definici´on de una clase “Lista Doblemente Enlazada” . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
. . . . . . . . . . . . . . .
5 5 5 7 8 9 10 10 10 10 11 11 12 12 14 14
3. Dise˜ no por Contrato: Asertos y Excepciones 3.1. Contrato software . . . . . . . . . . . . . . . . 3.2. Clases y Correcci´on del software: Asertos . . 3.3. Excepciones en C# . . . . . . . . . . . . . . . 3.3.1. Lanzamiento de excepciones . . . . . . 3.3.2. Bloques try . . . . . . . . . . . . . . . 3.3.3. Manejadores . . . . . . . . . . . . . . 3.4. Conversi´on de asertos en excepciones . . . . . 4. Herencia 4.1. Introducci´on . . . . . . . . . . . . . . . . . 4.2. Doble aspecto de la herencia . . . . . . . . 4.3. Polimorfismo . . . . . . . . . . . . . . . . 4.3.1. Sobrecarga . . . . . . . . . . . . . 4.3.2. Regla de aplicaci´on de propiedades 4.3.3. Estructuras de datos polim´orficas . 4.3.4. Operadores is y as . . . . . . . . 4.4. Ligadura Din´amica . . . . . . . . . . . . . 4.5. Clases Diferidas . . . . . . . . . . . . . . . 4.5.1. Interfaces . . . . . . . . . . . . . . 4.6. Herencia, reutilizaci´on y extensibilidad del 4.7. Herencia m´ ultiple . . . . . . . . . . . . . . 3
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
17 17 17 18 20 21 22 22
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . software . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
. . . . . . . . . . . .
25 25 26 26 27 29 29 29 30 31 32 34 37
. . . . . . .
. . . . . . .
4
´ INDICE GENERAL
4.7.1. Problemas: Colisi´on de nombres y herencia repetida . . . . . . . . . . 4.7.2. Ejemplos de utilidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.8. C#, Java y C++: Estudio comparativo . . . . . . . . . . . . . . . . . . . . .
37 38 39
´ Indice de Figuras
41
´ Indice de Tablas
43
´ Indice de C´ odigos
45
Bibliograf´ıa
47
Tema 2
Clases y Objetos 2.1.
Clases
Definici´ on 2.1 Una clase es una implementaci´on total o parcial de un tipo abstracto de dato (TAD). Sus caracter´ısticas m´as destacables son que se trata de entidades sint´acticas y que describen objetos que van a tener la misma estructura y el mismo comportamiento.
2.1.1.
Estructura
Los componentes principales de una clase, que a partir de ahora llamaremos miembros, son: Atributos, que determinan una estructura de almacenamiento para cada objeto de la clase, y M´ etodos, que no son m´as que operaciones aplicables sobre los objetos. Ejemplo 2.1 La clase mostrada en el c´ odigo 2.1, llamada Luna, convierte a kil´ ometros la distancia de la Tierra a la Luna en millas. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// Distancia hasta la Luna convertida a kil´ o metros using System ; public class Luna { public static void Main () { int luna = 238857; int lunaKilo ; Console . WriteLine (" De la Tierra a la Luna = " + luna + " millas ") ; lunaKilo = ( int ) ( luna * 1.609) ; Console . WriteLine (" Kil´ o metros = " + lunaKilo + " km .") ; } } C´odigo 2.1: Clase Luna
5
6
Tema 2. Clases y Objetos
Tipo short ushort int uint long ulong
Bytes 2 2 4 4 8 8
Rango de Valores (-32768, 32767) (0, 65535) (-2147483648, 2147483647) (0, 4294967295) (-9223372036854775808, 9223372036854775807) (0, 18446744073709551615)
Tabla 2.1: Tipos enteros primitivos Tipo float double decimal
Bytes 4 8 16
Rango de Valores (±3,4 × 1038 ) 7 d´ıgitos significativos (±1,7 × 1038 ) de 15 a 16 d´ıgitos significativos (10−28 , 7,9 × 10+28 ) de 28 a 29 d´ıgitos significativos Tabla 2.2: Tipos flotantes primitivos Tipo byte ubyte bool char
Bytes 1 1 1 2
Rango de Valores (-128,127) (0,255) {true, false} Tabla ASCII
Tabla 2.3: Otros tipos primitivos Tipos de datos primitivos Las tablas 2.1, 2.2 y 2.3 muestran los tipos primitivos soportados por C#. Para declarar un tipo consistente en un conjunto etiquetado de constantes enteras se emplea la palabra clave enum (por ejemplo, enum Edades {F´ elix = 21, Alex, Alberto = 15}). Palabras reservadas La tabla 2.4 muestra las palabras reservadas de C#. abstract as base bool break byte case catch char checked class const continue decimal default delegate
do double else enum event explicit extern false finally fixed float for foreach get goto if
implicit in int interface internal is lock long namespace new null object operator out override params
private protected public readonly ref return sbyte sealed set short sizeof stackalloc static string struct switch
Tabla 2.4: Palabras reservadas
this throw true try typeof unit ulong unchecked unsafe ushort using value virtual void volatile while
2.1 Clases
2.1.2.
7
Ocultaci´ on de la informaci´ on
En ocasiones conviene ocultar ciertas caracter´ısticas (atributos y/o m´etodos) de una clase al exterior. Por ejemplo, en una relaci´on entre clases de Cliente-Servidor, el servidor deber´ıa ocultar los aspectos de implementaci´on al cliente. Para llevar a cabo esto, C# proporciona tres tipos de acceso: p´ ublico, privado y protegido. Un miembro con acceso privado (opci´on por defecto) s´olo es accesible desde otros miembros de esa misma clase, mientras que uno con acceso p´ ublico es accesible desde cualquier clase. Por u ´ltimo, un miembro protegido s´olo es accesible por miembros de la misma clase o bien por miembros de alguna de las subclases. Ejemplo 2.2 En el c´ odigo 2.2 se muestra un ejemplo de ocultaci´ on de informaci´ on, mediante la clase Punto. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
public class Punto { private double x , y ; // O simplemente double x , y ; public void SetPunto ( double u , double v ) { x = u; y = v; } } . . . class Test { public void Prueba ( Punto w ) { . . . w . SetPunto (4.3 ,6.9) ; // Correcto w . x = 5.5; // Error sint´ a ctico . . . } } C´odigo 2.2: Ejemplo de Ocultaci´on de Informaci´on A diferencia de C++, en C# es posible declarar un miembro como de s´olo lectura mediante la palabra clave readonly. La diferencia entre readonly y const es que con la primera opci´on la inicializaci´on tiene lugar en tiempo de ejecuci´on, mientras que con la segunda opci´on la inicializaci´on se da en tiempo de compilaci´on. Una forma de simular el efecto de const mediante readonly es usando adem´as la palabra clave static. Ejemplo 2.3 En el siguiente c´ odigo, n y m podr´ıamos decir que son “estructuralmente equivalentes”: public const n; public static readonly m; Otra pr´actica interesante consiste en declarar un atributo como privado y, mediante otro atributo p´ ublico tener acceso de lectura y/o escritura sobre el atributo privado. Para ello nos valemos de los modificadores de acceso get y set. Ejemplo 2.4 En el c´ odigo 2.3 se muestra un ejemplo de ocultaci´ on de informaci´ on mediante el uso de get y set. Obs´ervese que el identificador value es siempre un objeto del mismo tipo que el atributo que lo contiene.
8
1 2 3 4 5 6 7 8 9 10 11 12 13
Tema 2. Clases y Objetos
public class CuentaPalabras { private string m_file_output ; // Atributo privado public string OutFile // Atributo p´ u blico asociado { get { return m_file_output ; } // Acceso de lectura set { // Acceso de escritura if ( value . Length != 0 ) m_file_output = value ; } } } C´odigo 2.3: Otro ejemplo de Ocultaci´on de Informaci´on
2.1.3.
Relaciones entre clases: Cliente-Servidor y Herencia
Un cliente de una clase debe ver a ´esta como un TAD, es decir, conocer su especificaci´on, pero sin importarle su implementaci´on (siempre que ´esta sea eficiente y potente). El creador de la clase, en cambio, debe preocuparse de que dicha clase sea lo m´as reutilizable, adaptable, y eficiente, as´ı como que pueda ser usada por el mayor n´ umero de clientes posible. Definici´ on 2.2 Una clase A se dice que es cliente de una clase B, si A contiene una declaraci´on en la que se establezca que cierta entidad (atributo, par´ametro, variable local) e es de tipo B. Definici´ on 2.3 Una clase A se dice que hereda de otra clase B, si A es una versi´on especializada de B (herencia de especializaci´on), o bien si A es una implementaci´on de B (herencia de implementaci´on). La figura 2.1.a muestra la relaci´on de clientela entre A y B, mientras que en la 2.1.b se observa la relaci´on de herencia entre dichas clases.
A⇐B
B ↑ A
(a)
(b)
Figura 2.1: Clientela y Herencia entre A y B
Ejemplo 2.5 El siguiente c´ odigo muestra la relaci´ on de clientela vista en la figura 2.1.a: class A { . . . B conta; . . . } Por su parte, el siguiente c´ odigo muestra la relaci´ on de herencia mostrada en la figura 2.1.b: class A: B { . . . }
2.1 Clases
9
Por u ´ltimo cabe decir que la herencia es una decisi´on de dise˜ no m´as comprometedora que la clientela, pues en ´esta u ´ltima se puede cambiar la implementaci´on de la clase que se emplea en el cliente, sin afectar a ´este (en nuestro ejemplo, se puede modificar la implementaci´on de B sin que esto afecte a A).
2.1.4.
Visibilidad
Adem´as de lo comentado en el apartado 2.1.2 acerca de la ocultaci´on de informaci´on, en esta secci´on trataremos la cuesti´on de las clases anidadas. Definici´ on 2.4 Una clase anidada no es m´as que una clase que se declara dentro de otra y que tiene visibilidad sobre todas las propiedades de los objetos de la clase que la incluye. Ejemplo 2.6 El c´ odigo 2.4 muestra un ejemplo de clases anidadas. El resultado mostrado por pantalla ser´ıa: IntAnidado(6) = 17 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
public class Uno { public int c ; } public class Dos { public int c ; public class Tres { public int IntAnidado ( int e ) { Dos d = new Dos () ; Uno u = new Uno () ; u.c = 5 + e; d.c = c = e; return u . c + c ; } private int c ; } public Tres y ; } public class PruebaAnidada { public static void Main () { Dos x = new Dos () ; x . y = new Tres () ; Console . WriteLine (" IntAnidado (6) = " + IntAnidado (6) ) ; } } C´odigo 2.4: Ejemplo de Clases Anidadas
10
2.2.
Tema 2. Clases y Objetos
Objetos
Definici´ on 2.5 Un objeto es una instancia de una clase, creada en tiempo de ejecuci´on y formada por tantos campos como atributos tenga la clase. Dichos campos son simples si corresponden a atributos de tipos primitivos (v´ease tablas 2.1, 2.2 y 2.3), mientras que se dice que son compuestos si sus valores son subobjetos o referencias. Mientras exista, cada objeto se identifica un´ıvocamente mediante su identificador de objeto (oid). Una posible clasificaci´on de los objetos podr´ıa ser: Objetos externos: Son aquellos que existen en el dominio de la aplicaci´on. Por ejemplo, Producto, Socio, Comercial, Descuento, ... Objetos software: • Procedentes del an´alisis: objetos del dominio. • Procedentes del dise˜ no/implementaci´on: TDA’s, patrones de dise˜ no y GUI. El estado de un objeto viene dado por la lista de pares atributo/valor de cada campo. Su modificaci´on y consulta se realiza mediante mensajes. En cada instante de la ejecuci´on de una aplicaci´on Orientada a Objetos (en adelante, OO) existe un objeto destacado sobre el que se realiza alg´ un tipo de operaci´on. Este objeto recibe el nombre de instancia actual. Para hacer referencia a la instancia actual en C# se emplea la palabra reservada this.
2.3.
Mensajes
Los mensajes son el mecanismo b´asico de la computaci´on OO y consisten en la invocaci´on de la aplicaci´on de un m´etodo sobre un objeto. Constan de tres partes: objeto receptor, identificador del m´etodo y los argumentos de ´este u ´ltimo.
2.3.1.
Sintaxis. Notaci´ on punto
Como se ha podido observar en algunos de los ejemplos vistos hasta ahora, la forma en que se invoca a un m´etodo es mediante el operador punto, siguiendo una sintaxis como la que a continuaci´on se muestra: receptor.m´etodo(argumentos)
2.3.2.
Sem´ antica
La diferencia entre un mensaje y la invocaci´on a un procedimiento es que en ´este u ´ltimo todos los argumentos reciben el mismo trato, mientras que en los mensajes uno de esos argumentos, a saber, el objeto receptor, recibe un “trato especial”. Ejemplo 2.7 En el mensaje w.SetPunto(4.3,6.9) lo que se est´ a queriendo decir es que se aplique el m´etodo SetPunto sobre el objeto receptor w, efectuando el correspondiente paso de par´ ametros. Cuando un mensaje no especifica al objeto receptor la operaci´on se aplica sobre la instancia actual. Si no se incluye nada, el paso de par´ametros es “por valor”. Para especificar un paso de par´ametros “por referencia” se emplea la palabra clave ref delante del par´ametro en cuesti´on.
2.4 Sem´ antica referencia versus sem´ antica almacenamiento
2.4.
11
Sem´ antica referencia versus sem´ antica almacenamiento
En C# se tiene sem´antica referencia (figura 2.41 ) para cualquier entidad asociada a una clase, aunque tambi´en existen los tipos primitivos (figura 2.22 ), como ya hemos visto. Toda referencia puede estar, o bien no ligada (con valor null), o bien ligada a un objeto (mediante el operador new, o si se le asigna una referencia ya ligada). Pero ojo, la asignaci´on no implica copia de valores sino de referencias (⇒ Aliasing).
Figura 2.2: Variables en los lenguajes tradicionales
Figura 2.3: Variables tipo “puntero” en los lenguajes tradicionales
Figura 2.4: Variables en los lenguajes OO Algunas ventajas de los tipos referencia son: Son m´as eficientes para manejar objetos. Constituyen un soporte para definir estructuras de datos recursivas. Dan soporte al polimorfismo. Los objetos son creados cuando son necesarios. Se permite la compartici´on de un objeto.
2.5.
Creaci´ on de objetos
En C# existe un mecanismo expl´ıcito de creaci´on de objetos mediante la instrucci´on de creaci´on new y los llamados m´etodos constructores (que deben tener el mismo nombre que la clase en la que se definen). Estos constructores se encargan de inicializar los atributos con valores consistentes. Sin embargo tambi´en se pueden inicializar con unos valores por defecto, mediante los constructores por defecto (aquellos que no tienen argumentos). 1 2
TE→Tiempo de Ejecuci´ on TC→Tiempo de Compilaci´ on
12
Tema 2. Clases y Objetos
Ejemplo 2.8 El c´ odigo 2.5 muestra un ejemplo de una clase con un constructor y un constructor por defecto. 1 2 3 4 5 6 7 8 9 10 11 12
public class Contador { private int conta ; public public public { get set } public
Contador () { conta = 0; } // Constructor por defecto Contador ( int i ) { conta = i % 100; } // Constructor int Conta { return conta ; } { conta = value % 100; } void Click () { conta = ( conta + 1) % 100; }
} C´odigo 2.5: Ejemplo de Constructores
2.5.1.
Destructores
Un destructor es un m´etodo cuyo nombre es el nombre de la clase precedido de una tilde ∼. Mientras que los constructores s´ı pueden tener modificadores de acceso, los destructores carecen de ellos. Cuando un objeto es recogido por el recolector de basura3 se llama impl´ıcitamente a su destructor. No obstante, en la mayor´ıa de los casos las clases no necesitan destructores, pues con el recolector de basura es suficiente para la finalizaci´on.
2.6.
Sem´ antica de la asignaci´ on e igualdad entre objetos
Definici´ on 2.6 Una copia se dice que es superficial si se copian los valores de cada campo del objeto origen en el objeto destino y ambos comparten referencias (figura 2.5).
Figura 2.5: Ejemplo de copia superficial Definici´ on 2.7 Una copia se dice que es profunda si se crea un objeto con una estructura id´entica al objeto origen y sin compartici´on de referencias (figura 2.6).
Figura 2.6: Ejemplo de copia profunda 3
garbage collector
2.6 Sem´ antica de la asignaci´ on e igualdad entre objetos
13
Como ya se dijo anteriormente, la asignaci´on implica compartici´on de referencias si se tiene sem´antica referencia, mientras que si se tiene sem´antica almacenamiento entonces se copian los valores. A este tipo de copia se le llama copia superficial y est´a soportada en C#. Si se tiene sem´antica referencia y se realiza una copia superficial, los cambios hechos en una variable tienen repercusi´on en su(s) correspondiente(s) copia(s). Para evitar este efecto debemos realizar una copia profunda, lo cual puede llevarse a cabo, por ejemplo, implementando el m´etodo Clone() de la interfaz ICloneable. Ejemplo 2.9 El c´ odigo 2.6 muestra un ejemplo de una clase que implementa el m´etodo Clone() de la interfaz ICloneable para conseguir una copia profunda. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
public class matrix : ICloneable { . . . public matrix ( int row , int col ) { m_row = ( row <= 0 ) ? 1 : row ; m_col = ( col <= 0 ) ? 1 : col ; m_mat = new double [ m_row , m_col ]; } public object Clone () { matrix mat = new matrix ( m_row , m_col ) ; for ( int i = 0; i < m_row ; ++ i ) for ( int j = 0; j < m_col ; ++ j ) mat . m_mat [i , j ] = m_mat [i , j ]; return mat ; } } C´odigo 2.6: Ejemplo de implementaci´on del m´etodo Clone() Nota.- La l´ınea 6 (equivalentemente la l´ınea 7) del c´odigo 2.6 es equivalente al siguiente c´odigo: if (row <= 0) m_row = 1; else m_row = row; Definici´ on 2.8 Dos variables se dice que son id´enticas si ambas referencian al mismo objeto (figura 2.7). En C# esto se expresa de la siguiente forma: oa == ob
Figura 2.7: Ejemplo de variables id´enticas Definici´ on 2.9 Dos variables se dice que son iguales si ambas referencian a objetos con valores iguales en sus campos (figuras 2.5 y 2.7). En C# esto se expresa de la siguiente forma: oa.Equals(ob)
14
Tema 2. Clases y Objetos
De esta u ´ltima definici´on se deduce que siempre que haya identidad, tambi´en habr´a igualdad superficial (figura 2.7), pero no necesariamente a la inversa (figura 2.5).
2.7.
Genericidad
C++ hace uso de plantillas, que son altamente eficientes y muy potentes, para implementar clases contenedoras gen´ericas. Dichas plantillas est´an propuestas para Java y C#, pero a´ un est´an en una fase experimental. C# consigue “genericidad” al estilo de Java, a saber, mediante el empleo de la clase ra´ız Object, con los consabidos inconvenientes: Necesidad de conversiones expl´ıcitas de tipo. Imposibilidad de asegurar homogeneidad.
2.8. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
Definici´ on de una clase “Lista Doblemente Enlazada”
using System ; public class DListElement { public DListElement ( object val ) { data = val ; } public DListElement ( object val , DListElement d ) { data = val ; next = d ; d . previous = this ; } public DListElement Next { get { return next ; } set { next = value ; } } public DListElement Previous { get { return previous ; } set { previous = value ; } } public DListElement Data { get { return data ; } set { data = value ; } } private DListElement next , previous ; private object data ; } public class DList { public DList () { head = null ; } public DList ( object val ) { head = new DListElement ( val ) ; head . Next = head . Previous = null ; }
2.8 Definici´ on de una clase “Lista Doblemente Enlazada”
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
15
public bool IsEmpty () { return head == null ; } public void Add ( object val ) { if ( IsEmpty () ) { head = new DListElement ( val ) ; head . Next = head . Previous = null ; } else { DListElement h = new DListElement ( val , head ) ; head . Previous = h ; head = h ; } } public object Front () { if ( IsEmpty () ) throw new System . Exception (" Empty DList ") ; return head . Data ; } public DListElement Find ( object val ) { DListElement h = head ; while ( h != null ) { if ( val . Equals ( h . Data ) ) break ; h = h . Next ; } return h ; } private DListElement Delete ( DListElement h ) { if ( h == null ) return null ; DListElement np = h . Next , pp = h . Previous ; if ( np != null ) np . Previous = pp ; else head = null ; if ( pp != null ) pp . Next = np ; return h . Next ; }
16
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
Tema 2. Clases y Objetos
public DListElement Delete ( object v ) { return Delete ( Find ( v ) ) ; } public override string ToString () { return ToStr ingRec ursive ( head ) ; } static string To String Recursive ( DListElement first ) { if ( first == null ) return ""; else return ( first . Data + "\ n ") + ToStr ingRec ursive ( first . Next ) ; } private DListElement head ; } C´odigo 2.7: Lista gen´erica doblemente enlazada A continuaci´on destacaremos algunos aspectos de este c´odigo: Obs´ervese el uso de get y set en las l´ıneas 13 a 20 (en m´etodos declarados como p´ ublicos) y las declaraciones privadas de las l´ıneas 22 y 23. Es este un ejemplo de ocultaci´on de informaci´on. Otro ejemplo de ocultaci´on de informaci´on es la declaraci´on como privados de los m´etodos Delete y ToStringRecursive de las l´ıneas 72 y 93, respectivamente. Podemos ver la implementaci´on de un constructor por defecto y un constructor en las l´ıneas 28 y 29, respectivamente, as´ı como la sobrecarga de constructores en las l´ıneas 4 y 6. Como se observa en la l´ınea 23 de este c´odigo los datos almacenados en la lista son de tipo object. Como ya se dijo anteriormente, as´ı es como se consigue “genericidad” en C#. En la l´ınea 65 vemos un ejemplo de uso del m´etodo Equals. El empleo del operador == en esta l´ınea provocar´ıa una ejecuci´on incorrecta, distinta a la esperada. En la l´ınea 55 se ve un ejemplo del uso de excepciones, las cuales trataremos en el siguiente tema; y en la l´ınea 90 vemos c´omo se sobrescribe el m´etodo heredado ToString()4 .
4
Todo lo relacionado con la herencia se ver´ a en profundidad en el tema 4
Tema 3
Dise˜ no por Contrato: Asertos y Excepciones En este tema se describe el manejo de excepciones en C#. Las excepciones son habitualmente condiciones inesperadas de error que provocan la finalizaci´on del programa en el que tienen lugar, con un mensaje de error. C#, sin embargo permite al programador intentar recuperarse frente a estas situaciones y as´ı continuar con la ejecuci´on del programa.
3.1.
Contrato software
Desde un punto de vista una excepci´on se puede decir que se basa en el incumplimiento del contrato software entre el cliente y el servidor de una clase. Seg´ un este modelo, el usuario (cliente) debe garantizar que se cumplen las condiciones necesarias para una correcta aplicaci´on del software. A estas condiciones se le conocen como precondiciones. El proveedor (servidor), por su parte, debe garantizar que, si el cliente cumple con las precondiciones, el software realizar´a la tarea deseada satisfactoriamente. Si esto es as´ı, se dice que se cumplen las postcondiciones.
3.2.
Clases y Correcci´ on del software: Asertos
La correcci´on de los programas se puede ver en parte como una prueba de que la ejecuci´on terminar´a proporcionando una salida correcta, siempre que la entrada sea correcta. Establecer una prueba formal completa de correcci´on del software es algo muy deseable, pero por desgracia, no se lleva a cabo en la mayor´ıa de las ocasiones. De hecho, la disciplina consistente en plantear asertos apropiados frecuentemente consigue que el programador advierta y evite bastantes errores de programaci´on que antes podr´ıan pasar desapercibidos. En C# la clase Debug contiene el m´etodo Assert(), invocado de la siguiente manera: Assert(expresi´ on booleana,mensaje); Si la expresi´on expresi´ on booleana se eval´ ua como falsa, la ejecuci´on proporciona una salida de diagn´ostico, con el mensaje mensaje. Los asertos se habilitan definiendo la variable DEBUG. Para acceder a dicha salida de diagn´ostico se emplean los listener. Ejemplo 3.1 En el c´ odigo 3.1 se muestra un ejemplo del uso de asertos, as´ı como del empleo de listeners para visualizar los mensajes de diagn´ ostico por pantalla.
17
18
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
Tema 3. Dise˜ no por Contrato: Asertos y Excepciones
# define DEBUG using System ; using System . Diagnostics ; public class AssertSqrRoot { public static void Main () { Debug . Listeners . Clear () ; Debug . Listeners . Add ( new T e x t W r i t e rT r ac eL i st e ne r ( Console . Out ) ) ; double x ; string datos ; Console . WriteLine (" Introduzca un n´ u mero real positivo :") ; datos = Console . readLine () ; x = double . Parse ( datos ) ; Console . WriteLine ( x ) ; Debug . Assert ( x > 0 , " Valor no positivo ") ; Console . WriteLine (" La ra´ ı z cuadrada es " + Math . Sqrt ( x ) ) ; } } C´odigo 3.1: Ejemplo del uso de asertos El uso de asertos reemplaza el uso ad hoc de comprobaciones condicionales con una metodolog´ıa m´as uniforme. El inconveniente de los asertos es que no permiten una estrategia de reparaci´on o reintento para continuar con la ejecuci´on “normal” del programa.
3.3.
Excepciones en C#
C# contiene la clase est´andar System.Exception, que es el objeto o la clase base de un objeto lanzado por el sistema o por el usuario cuando ocurre un error en tiempo de ejecuci´on. El mecanismo de manejo de excepciones en C# es sensible al contexto. Y dicho contexto no es m´as que un bloque try, en el cual se declaran los manejadores (handlers) al final del mismo mediante la palabra clave catch. Un c´odigo de C# puede alcanzar una excepci´on en un bloque try mediante la expresi´on throw. La excepci´on es entonces tratada por alguno de los manejadores que se encuentran al final del bloque try. Ejemplo 3.2 El c´ odigo 3.2 muestra un ejemplo del uso de excepciones, con un bloque try, la expresi´ on throw y los manejadores definidos mediante la palabra clave catch. 1 2 3 4 5 6 7 8
using System ; public class LanzaExcepcion { public static void Main () { try { double x ; string datos ;
3.3 Excepciones en C#
9 10 11 12 13 14 15 16 17 18 19 20 21
19
Console . WriteLine (" Introduzca un double :") ; datos = Console . readLine () ; x = double . Parse ( datos ) ; Console . WriteLine ( x ) ; if ( x > 0) throw ( new System . Exception () ) ; else Console . WriteLine (" La ra´ ı z cuadrada es " + Math . Sqrt ( x ) ) ; } catch ( Exception e ) { Console . WriteLine (" Lanzada excepci´ on " + e); } } } C´odigo 3.2: Ejemplo del uso de excepciones La tabla 3.1 muestra algunas excepciones est´andar en C#, mientras que en la tabla 3.2 podemos observar las propiedades est´andar que toda excepci´on debe tener. SystemException ApplicationException ArgumentException ArgumentNullException ArgumentOutOfRangeException ArithmeticException DivideByZeroException IndexOutOfRangeException InvalidCastException IOException NullReferenceException OutOfMemoryException
Clase base para las excepciones del sistema Clase base para que los usuarios proporcionen errores de aplicaci´on Uno o m´as argumentos son inv´alidos Pasado null no permitido Fuera de los valores permitidos Valor infinito o no representable Auto-explicativa ´Indice fuera de los l´ımites del array Cast no permitido Clase base en el espacio de nombres System.IO para las excepciones de E/S Intento de referenciar a null Ejecuci´on fuera de la memoria heap (mont´on)
Tabla 3.1: Algunas Excepciones est´andar HelpLink InnerException Message StackTrace Source TargetSize
Obtiene o establece un enlace a un fichero de ayuda Obtiene la instancia de Exception que caus´o la excepci´on Texto que describe el significado de la excepci´on Traza de la pila cuando se llam´o a la excepci´on Aplicaci´on u objeto que gener´o la excepci´on M´etodo que lanz´o la excepci´on
Tabla 3.2: Propiedades de las excepciones
20
3.3.1.
Tema 3. Dise˜ no por Contrato: Asertos y Excepciones
Lanzamiento de excepciones
Mediante la expresi´on throw se lanzan excepciones, las cuales deben ser objetos de la clase Exception. El bloque try m´as interno en el que se lanza una excepci´on es empleado para seleccionar la sentencia catch que procesar´a dicha excepci´on. Si lo que queremos es relanzar la excepci´on actual podemos emplear un throw sin argumentos en el cuerpo de un catch. Esta situaci´on puede ser u ´til si deseamos que un segundo manejador llamado desde el primero realice un procesamiento m´as exhaustivo de la excepci´on en cuesti´on. Ejemplo 3.3 El c´ odigo 3.3 muestra un ejemplo del relanzamiento de excepciones. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
using System ; public class ReLanzaExcepcion { public static void LanzaMsg () { try { . . . throw new Exception (" Lanzada en LanzaMsg ") ; . . . } catch ( Exception e ) { . . . Console . WriteLine (" Primera captura " + e ) ; throw ; // Relanzamiento } } public static void Main () { try { . . . LanzaMsg () ; . . . } catch ( Exception e ) { Console . WriteLine (" Excepci´ o n recapturada " + e ) ; } } } C´odigo 3.3: Ejemplo del relanzamiento de excepciones Conceptualmente, el lanzamiento de una excepci´on pasa cierta informaci´on a los manejadores, pero con frecuencia ´estos no precisa de dicha informaci´on. Por ejemplo, un manejador que simplemente imprime un mensaje y aborta la ejecuci´on no necesita m´as informaci´on de su entorno. Sin embargo, el usuario probablemente querr´a informaci´on adicional por pantalla para seleccionar o ayudarle a decidir la acci´on a realizar por el manejador. En este caso ser´ıa apropiado “empaquetar” la informaci´on en un objeto derivado de una clase Exception ya existente.
3.3 Excepciones en C#
21
Ejemplo 3.4 El c´ odigo 3.4 muestra un ejemplo del uso de objetos para empaquetar informaci´ on que se pasa a los manejadores de excepciones. El resultado mostrado por pantalla ser´ıa algo como: Out of bounds with last char z 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
using System ; public class Stack { public char [] s = new char [100]; } public class StackError : Exception { public StackError ( Stack s , string message ) { st = s ; msg = message ; } public char TopEntry () { return st . s [99]; } public string Msg { set { msg = value ; } get { return msg ; }} private Stack st ; private string msg ; } public class StackErrorTest { public static void Main () { Stack stk = new Stack ; stk . s [99] = ’z ’; try { throw new StackError ( stk ," Out of bounds ") ; } catch ( StackError se ) { Console . WriteLine ( se . Msg + " with last char " + se . TopEntry () ) ; } } } C´odigo 3.4: Uso de excepciones con m´as informaci´on
3.3.2.
Bloques try
Un bloque try es el contexto para decidir qu´e manejador invocar para cada excepci´on que se dispara. El orden en el que se definen los manejadores determina el orden en el que se intenta invocar cada manejador que puede tratar la excepci´on que haya saltado. Una excepci´on puede ser tratada por una manejador en concreto si se cumplen alguna de estas condiciones: 1. Si hay una coincidencia exacta. 2. Si la excepci´on lanzada ha sido derivada de la clase base del manejador. Es un error listar los manejadores en un orden en el que se impida la ejecuci´on de alguno de ellos, como muestra el siguiente ejemplo.
22
Tema 3. Dise˜ no por Contrato: Asertos y Excepciones
Ejemplo 3.5 Seg´ un el siguiente c´ odigo: catch(ErrorClaseBase e) catch(ErrorClaseDerivada e) nunca se ejecutar´ıa el cuerpo del segundo catch, pues antes se encontrar´ıa una coincidencia con el primer catch, seg´ un lo dicho en las condiciones anteriores.
3.3.3.
Manejadores
La sentencia catch parece la declaraci´on de un m´etodo con un solo argumento y sin valor de retorno. Incluso existe un catch sin ning´ un argumento, el cual maneja las excepciones no tratadas por los manejadores que s´ı tienen argumento. Cuando se invoca a un manejador a trav´es de una expresi´on throw, nos salimos del bloque try y autom´aticamente se llama a los m´etodos (incluidos los destructores) que liberan la memoria ocupada por todos los objetos locales al bloque try. La palabra clave finally despu´es de un bloque try introduce otro bloque que se ejecuta despu´es del bloque try independientemente de si se ha lanzado una excepci´on o no.
3.4.
Conversi´ on de asertos en excepciones
La l´ogica de las excepciones es m´as din´amica ya que los manejadores pueden recibir m´as informaci´on que los asertos, como ya hemos visto anteriormente. Los asertos simplemente imprimen un mensaje por pantalla, mientras que con las excepciones, adem´as de imprimir m´as informaci´on, se puede decidir (en algunos casos) entre continuar la ejecuci´on del programa o abortarla de inmediato. Ejemplo 3.6 El c´ odigo 3.5 es una reescritura del c´ odigo 3.1 que se muestra en el ejemplo 3.1 del uso de asertos, pero ahora mediante excepciones. En este ejemplo se ve c´ omo el programa hace uso del mecanismo de excepciones para llamarse a s´ı mismo recursivamente hasta que el usuario introduzca un n´ umero v´ alido para la entrada. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
using System ; public class ExAssert { public static void MyAssert ( bool cond , string message , Exception e ) { if (! cond ) { Console . Error . WriteLine ( message ) ; throw e ; } } } class ExceptionSqrRoot { public static void ConsoleSqrt () { double x ; string datos ; Console . WriteLine (" Introduzca un double positivo :") ;
3.4 Conversi´ on de asertos en excepciones
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
23
datos = Console . readLine () ; x = double . Parse ( datos ) ; Console . WriteLine ( x ) ; try { ExAssert . MyAssert ( x > 0 , " Valor no positivo : x = " + x . ToString () , new Exception () ) ; Console . WriteLine (" La ra´ ı z cuadrada es " + Math . Sqrt ( x )); } catch ( Exception e ) { Console . WriteLine ( e ) ; ExceptionSqrRoot . ConsoleSqrt () ; // Se vuelve a intentar } } public static void Main () { Console . WriteLine (" Probando Ra´ ı ces Cuadradas ") ; ConsoleSqrt () ; } } C´odigo 3.5: Ejemplo del uso de excepciones en vez de asertos
24
Tema 3. Dise˜ no por Contrato: Asertos y Excepciones
Tema 4
Herencia 4.1.
Introducci´ on
La herencia es un potente mecanismo para conseguir reutilizaci´on en el c´odigo, consistente en derivar una nueva clase a partir de una ya existente. A trav´es de la herencia es posible crear una jerarqu´ıa de clases relacionadas que compartan c´odigo y/o interfaces. En muchas ocasiones distintas clases resultan ser variantes unas de otras y es bastante tedioso tener que escribir el mismo c´odigo para cada una. Una clase derivada hereda la descripci´on de la clase base, la cual se puede alterar a˜ nadiendo nuevos miembros y/o modificando los m´etodos existentes, as´ı como los privilegios de acceso. Ejemplo 4.1 En la figura 4.1 se muestra un ejemplo de una jerarqu´ıa de clases.
Figura 4.1: Jerarqu´ıa de clases Definici´ on 4.1 Si B hereda de A entonces B incorpora la estructura (atributos) y comportamiento (m´etodos) de A, pero puede incluir adaptaciones: B puede a˜ nadir nuevos atributos. B puede a˜ nadir nuevos m´etodos. B puede redefinir m´etodos de A (bien para extender el m´etodo original, bien para mejorar la implementaci´on). B puede implementar un m´etodo diferido en A. 25
26
Tema 4. Herencia
La tabla 4.1 muestra los m´etodos p´ ublicos y protegidos de la clase object, ra´ız en toda jerarqu´ıa de clases de C#. bool Equals(object o) void Finalize() int GetHashCode() Type GetType() object MemberwiseClone() bool ReferenceEquals (object a, object b) string ToString()
Devuelve true si dos objetos son equivalentes, si no, devuelve false Equivalente a escribir un destructor Proporciona un entero u ´nico para cada objeto Permite averiguar din´amicamente el tipo de objeto Permite clonar un objeto Devuelve true si los objetos son la misma instancia Devuelve un string que representa al objeto actual
Tabla 4.1: Propiedades de las excepciones
4.2.
Doble aspecto de la herencia
Como ya se dijo en la definici´on 2.3, la herencia es, a la vez, un mecanismo de especializaci´on y un mecanismo de implementaci´on. Esto es, una clase B puede heredar de otra clase A, por ser B una especializaci´on de A. Sin embargo, puede ocurrir que el motivo por el cual B hereda de A sea la necesidad de construir una implementaci´on “distinta” (si se quiere, m´as refinada) de A. Ejemplo 4.2 La herencia entre clases que se observa en la figura 4.1, as´ı como en las figuras 4.2.a y 4.2.b sirve como mecanismo de especializaci´ on. Por otra parte, los ejemplos de las figuras 4.2.c y 4.2.d muestran la herencia como mecanismo de implementaci´ on.
Figura 4.2: Herencia de especializaci´on y de implementaci´on
4.3.
Polimorfismo
Definici´ on 4.2 Podr´ıamos definir el polimorfismo como la capacidad que tiene una entidad para referenciar en tiempo de ejecuci´on a instancias de diferentes clases. C# soporta el polimorfismo, lo cual quiere decir que sus entidades (las cuales poseen un u ´nico tipo est´atico y un conjunto de tipos din´amicos) pueden estar conectadas a una instancia de la clase asociada en su declaraci´on o de cualquiera de sus subclases. Podr´ıamos categorizar el polimorfismo en dos tipos: Param´etrico z Real y z Aparente → Sobrecarga Inclusi´on (basado en la herencia)
4.3 Polimorfismo
27
Ejemplo 4.3 Dada la siguiente declaraci´ on, siguiendo la jerarqu´ıa de clases de la figura 4.1, Poligono p;
Triangulo t;
Rectangulo r;
Cuadrado c;
podemos decir que: te(p) = Poligono ctd(p) = {Poligono, Triangulo, Rectangulo, Cuadrado} te(t) = Triangulo ctd(t) = {Triangulo} te(r) = Rectangulo ctd(r) = {Rectangulo, Cuadrado} te(c) = Cuadrado ctd(c) = {Cuadrado} donde “te” significa “tipo est´ atico” y “ctd” significa “conjunto de tipos din´ amicos”. Y por lo tanto las siguientes asignaciones ser´ıan v´ alidas: p = t;
p = r;
Pero no lo ser´ıan, por ejemplo:
4.3.1.
r = c; t = r;
r = p;
p = c; c = p;
c = t;
Sobrecarga
Definici´ on 4.3 La sobrecarga consiste en dar distintos significados a un mismo m´etodo u operador. C# soporta la sobrecarga de m´etodos y de operadores. Uno de los ejemplos m´as claros es el del operador ‘+’. Ejemplo 4.4 La expresi´ on a + b puede significar desde concatenaci´ on, si a y b son de tipo string, hasta una suma entera, en punto flotante o de n´ umeros complejos, todo ello dependiendo del tipo de datos de a y b. Sin embargo, lo realmente interesante es poder redefinir operadores cuyos operandos sean de tipos definidos por el usuario, y C# permite hacer esto. Pero conviene tener en cuenta que este tipo de actuaci´on s´olo es provechosa en aquellos casos en los que existe una notaci´on ampliamente usada que conforme con nuestra sobrecarga (m´as a´ un, si cabe, en el caso de los operadores relacionales <, >, <= y >=). La tabla 4.2 muestra los operadores unarios que en C# se pueden sobrecargar, mientras que la tabla 4.3 hace lo propio con los operadores binarios. + − ! ∼ ++ −− true f alse Tabla 4.2: Operadores unarios “sobrecargables” + − ∗ / % & | ∧ << >> == ! = < > >= <= Tabla 4.3: Operadores binarios “sobrecargables” Cuando se sobrecarga un operador tal como ‘+’, su operador de asignaci´on asociado, ‘+=’, tambi´en es sobrecargado impl´ıcitamente. N´otese que el operador de asignaci´on no puede sobrecargarse y que los operadores sobrecargados mantienen la precedencia y asociatividad de los operandos. Tanto los operadores binarios como los unarios s´olo pueden ser sobrecargados mediante m´etodos est´aticos.
28
Tema 4. Herencia
Ejemplo 4.5 El c´ odigo 4.1 muestra un ejemplo de sobrecarga de operadores, tanto unarios, como binarios. Obs´ervese como en la l´ınea 32 se vuelve a definir el operador ‘*’, previamente sobrecargado por el m´etodo de la l´ınea 29, pero con los par´ ametros en orden inverso. Esta pr´ actica com´ un evita que el operador sobrecargado tenga un orden prefijado en sus operandos. El resultado mostrado por pantalla tras ejecutarse el m´etodo Main() ser´ıa: Initial times day 00 time:00:00:59 day 01 time:23:59:59 One second later day 00 time:00:01:00 day 02 time 00:00:00 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
using System ; class MyClock { public MyClock () { } public MyClock ( unit i ) { Reset ( i ) ; } public void Reset ( unit i ) { totSecs = i ; secs = i % 60; mins = ( i / 60) % 60; hours = ( i / 3600) % 24; days = i / 86400; } public override string ToString () { Reset ( totSecs ) ; return String . Format (" day {0: D2 } time :{1: D2 }:{2: D2 }:{3: D2 }" , days , hours , mins , secs ) ; }
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
public void Tick () { Reset (++ totSecs ) ; } public static MyClock operator ++( MyClock c ) { c . Tick () ; return c ; } public static MyClock operator +( MyClock c1 , MyClock c2 ) { return new MyClock ( c1 . totSecs + c2 . totSecs ) ; } public static MyClock operator *( uint m , MyClock c ) { return new MyClock ( m * c . totSecs ) ; } public static MyClock operator *( MyClock c , uint m ) { return ( m * c ) ; } private uint totSecs = 0 , secs = 0 , mins = 0 , hours = 0 , days = 0; }
4.3 Polimorfismo
37 38 39 40 41 42 43 44 45
29
class MyClockTest { public static void Main () { MyClock a = new MyClock (59) , b = new MyClock (172799) ; Console . WriteLine (" Initial times \ n " + a + ’\n ’ + b ) ; ++ a ; ++ b ; Console . WriteLine (" One second later \ n " + a + ’\n ’ + b ) ; } } C´odigo 4.1: Ejemplo de Sobrecarga de Operadores
4.3.2.
Regla de aplicaci´ on de propiedades
Definici´ on 4.4 Un tipo T1 es compatible con otro tipo T2, si la clase de T1 es la misma que la de T2 o una subclase de T2. Definici´ on 4.5 Regla de la Asignaci´ on 1 Una asignaci´on x = y o una invocaci´on r(..,y,..) a una rutina r(..,T x,..), ser´a legal si el tipo de y es compatible con el tipo de x. Definici´ on 4.6 Regla de Validez de un Mensaje 2 Un mensaje ox.r(y), supuesta la declaraci´on X x, ser´a legal si: 1. X incluye una propiedad con nombre final r. 2. Los argumentos son compatibles con los par´ametros y coinciden en n´ umero. 3. Y r est´a disponible para la clase que incluye el mensaje.
4.3.3.
Estructuras de datos polim´ orficas
La lista doblemente enlazada implementada en el c´odigo 2.7 es un claro ejemplo de estructura de datos polimorfa. Si en la l´ınea 23 de dicho c´odigo en vez de poner private object data escribi´eramos private Figura data, siguiendo el ejemplo de jerarqu´ıa de clases de la figura 4.1, en un instante dado podr´ıamos tener una lista como la que se observa en la figura 4.3.
Figura 4.3: Ejemplo de estructura de datos polimorfa
4.3.4.
Operadores is y as
El operador is, cuya sintaxis es expresi´ on is tipo, devuelve true si se puede hacer un “cast” de la expresi´on expresi´ on al tipo tipo. El operador as, con id´entica sintaxis devuelve la expresi´on expresi´ on convertida al tipo tipo, o bien, si la conversi´on no est´a permitida, devuelve null. 1 2
En adelante, RA En adelante, RVM
30
4.4.
Tema 4. Herencia
Ligadura Din´ amica
Como ya dijimos en la definici´on 4.1, si una clase B hereda de otra clase A, B puede redefinir m´etodos de A e implementar m´etodos que en A sean diferidos. Entonces cuando tengamos un objeto de la clase A y una invocaci´on a un m´etodo redefinido en la clase B, la versi´on del m´etodo que se ejecute depender´a del tipo din´amico del objeto de la clase A. En esto consiste la ligadura din´ amica. Ejemplo 4.6 En la figura 4.4 se muestra una jerarqu´ıa de clases en las que algunas de ellas redefinen el m´etodo f de sus ancestras. Las letras griegas representan la versi´ on de cada m´etodo.
Figura 4.4: Herencia y redefinici´on de m´etodos Dadas las siguientes declaraciones: A oa;
B ob = new B();
C oc = new C();
D od = new D();
A continuaci´ on se muestra qu´e versi´ on del m´etodo f se ejecutar´ıa, dependiendo del tipo din´ amico del objeto oa: oa = ob; oa.f → β oa = oc; oa.f → α oa = od; oa.f → δ En C#, como en C++, la ligadura es est´atica por defecto, siendo necesario incluir la palabra clave virtual en aquellos m´etodos que vayan a ser redefinidos y a los cuales s´ı se aplicar´a ligadura din´amica. Aquellas subclases que quieran redefinir un m´etodo declarado como virtual en la clase base deber´an emplear para ello la palabra clave override, como ya hemos visto en varios ejemplos con el m´etodo ToString(). N´otese la diferencia entre un m´etodo redefinido y un m´etodo sobrecargado. Los m´etodos sobrecargados son seleccionados en tiempo de compilaci´on bas´andonos en sus prototipos (argumentos y valor de retorno) y pueden tener distintos valores de retorno. Un m´etodo redefinido, por contra, es seleccionado en tiempo de ejecuci´on bas´andonos en el tipo din´amico del objeto receptor y no puede tener distinto valor de retorno del que tenga el m´etodo al que redefine. Ejemplo 4.7 El c´ odigo 4.2 muestra un ejemplo de redefinici´ on de un m´etodo heredado de la clase base. Obs´ervese el uso de la palabra clave virtual en la l´ınea 4, as´ı como de la palabra clave override en la l´ınea 9. Obs´ervese tambi´en como el valor devuelto por el m´etodo redefinido de la l´ınea 9 es el mismo que el del m´etodo original de la l´ınea 4, a saber, void. El resultado mostrado por pantalla tras ejecutarse el m´etodo Main() ser´ıa: Dentro de la clase base Dentro de la clase derivada Dentro de la clase derivada
4.5 Clases Diferidas
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
31
using System ; class ClaseBase { public virtual void Status () { Console . WriteLine (" Dentro de la clase base ") ; } } class ClaseDerivada : ClaseBase { public override void Status () { Console . WriteLine (" Dentro de la clase derivada ") ; } } class L i g a d u r a D i n a m i c a T e s t { public static void Main () { ClaseBase b = new ClaseBase () ; ClaseDerivada d = new ClaseDerivada () ; b . Status () ; d . Status () ; b = d; b . Status () ; } } C´odigo 4.2: Redifinici´on de un m´etodo heredado
4.5.
Clases Diferidas
Definici´ on 4.7 Una clase diferida o abstracta es aquella que contiene m´etodos abstractos, esto es, m´etodos que deben ser implementados en las subclases. No se pueden crear instancias de una clase abstracta. Para declarar en C# un m´etodo como abstracto se emplea la palabra clave abstract, y para implementarlo en alguna subclase utilizamos la palabra clave override. Si una clase hereda de otra clase abstracta y no implementa todos sus m´etodos abstractos, entonces sigue siendo abstracta. Ejemplo 4.8 El c´ odigo 4.3 muestra un ejemplo de una clase abstracta Figura, cuyo m´etodo abstracto Area() es implementado en las subclases Rectangulo y Circulo, pero no en la subclase Triangulo. Es por ello que la subclase Triangulo sigue siendo abstracta. Para ser m´ as preciso, es parcialmente abstracta (ver definici´ on 4.8). Sin embargo el m´etodo abstracto Perimetro() es implementado en las tres subclases. 1 2 3 4 5 6
using System ; abstract class Figura { abstract public double Area () ; abstract public double Perimetro () ; }
32
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
Tema 4. Herencia
class Rectangulo : Figura { public Rectangulo ( double a , double b ) { altura = a ; base = b ; } public override double Area () { return ( base * altura ) ; } public override double Perimetro () { return (( base * 2) + ( altura * 2) ) ; } private double altura , base ; } class Circulo : Figura { public Circulo ( double r ) { radio = r ; } public override double Area () { return ( Math . PI * radio * radio ) ; } public override double Perimetro () { return ( Math . PI * radio * 2) ; } private double radio ; } abstract class Triangulo : Figura { public Triangulo ( double l1 , double l2 , double l3 ) { lado1 = l1 ; lado2 = l2 ; lado3 = l3 ; } public override double Perimetro () { return ( lado1 + lado2 + lado3 ) ; } private double lado1 , lado2 , lado3 ; } C´odigo 4.3: Clase abstracta Figura Definici´ on 4.8 Una clase parcialmente abstracta es aquella que contiene m´etodos abstractos y efectivos. Para referenciar al constructor de la clase base a˜ nadimos al final del constructor de la clase derivada lo siguiente: : base(argumentos). El constructor de la clase base es invocado antes de ejecutar el constructor de la clase derivada.
4.5.1.
Interfaces
Definici´ on 4.9 Una clase que u ´nicamente contiene m´etodos abstractos recibe el nombre de interface. Pero no confundamos, no es lo mismo que una clase abstracta, pues una clase abstracta puede estar parcialmente implementada. Adem´as una clase s´olo puede heredar de una u ´nica clase abstracta, mientras que en C# est´a permitida la herencia m´ ultiple de interfaces3 . Todos los m´etodos de una interface, adem´as de ser abstractos como ya hemos dicho, deben ser p´ ublicos. Por lo tanto las palabras clave abstract y public se pueden omitir. Para declarar una interface se emplea la palabra clave interface; y para implementar una interface se emplea la notaci´on vista hasta ahora para la herencia. Una interface puede heredar de otra interface; sin embargo, no puede contener miembros de datos, s´olo m´etodos. 3
Lo veremos en profundidad m´ as adelante
4.5 Clases Diferidas
33
Ejemplo 4.9 En el c´ odigo 4.4 se muestra un ejemplo de una interface IPersona, de la cual heredan otras dos interfaces IEstudiante e IProfesor. Las tres son implementadas por la clase Ayudante. Obs´ervese el convenio de nombrar a las interfaces con una ‘I’ may´ uscula delante del nombre, a diferencia de las clases, que no la llevan. La figura 4.5 muestra el esquema de herencia e implementaci´ on de las interfaces y la clase en cuesti´ on.
Figura 4.5: Herencia e implementaci´on de interfaces
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
interface IPersona { string Nombre { get ; set ; } string Telefono { get ; set ; } int Edad { get ; set ; } bool esMayorDeEdad () ; } interface IEstudiante : IPersona { double NotaMedia { get ; set ; } int A~ n oMatricula { get ; set ; } } interface IProfesor : IPersona { double Salario { get ; set ; } string Nss { get ; set ; } // N o seguridad social } class Ayudante : IPersona , IEstudiante , IProfesor { // M´ e todos y propiedades requeridos por IPersona public string Name { get { return name ; } set { name = value ; } } public string Telefono { get { return telefono ; } set { telefono = value ; } } public int Edad { get { return edad ; } set { edad = value ; } } public bool esMayorDeEdad () { return ( edad >= 18) ; } // M´ e todos y propiedades requeridos por IEstudiante public double NotaMedia { get { return notaMedia ; } set { notaMedia = value ; } } public int A~ n oMatricula { get { return a~ n oMatricula ; } set { a~ n oMatricula = value ; } }
34
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
Tema 4. Herencia
// M´ e todos y propiedades requeridos por IProfesor public double Salario { get { return salario ; } set { salario = value ; } } public string Nss { get { return nss ; } set { nss = value ; } } // Constructores para Ayudante public Ayudante ( string nom , string tlf , int ed , double nm , int am , double sal , string numSec ) { nombre = nom ; telefono = tlf ; edad = ed ; notaMedia = nm ; a~ n oMatricula = am ; salario = sal ; nss = numSec ; } // Otras implementaciones de constructores y m´ e todos ... public override ToString () { return String . Format (" Nombre : {0: -20} Tel´ e fono : {1 ,9} Edad : {2 ,2}" + "\ nSeguridad Social : {3} Salario : {4: C }" + "\ nNota media : {5} A~ n o de matriculaci´ o n : {6}" , nombre , telefono , edad , nss , salario , notaMedia , a~ n oMatricula ) ; } private private private private private private private
string nombre ; string telefono ; int edad ; double notaMedia ; int a~ n oMatricula ; double salario ; string nss ;
} C´odigo 4.4: Herencia M´ ultiple mediante interfaces
4.6.
Herencia, reutilizaci´ on y extensibilidad del software
A continuaci´on enumeramos cu´ales son los requerimientos que debe cumplir un m´odulo para facilitar la reutilizaci´on: 1. Variaci´on en tipos. Conseguida gracias a la genericidad. 2. Variaci´on en estructuras de datos y algoritmos. Conseguida gracias a la ligadura din´amica y el polimorfismo. 3. Independencia de la representaci´on. Conseguida gracias a la ligadura din´amica y el polimorfismo.
4.6 Herencia, reutilizaci´ on y extensibilidad del software
35
4. Captura de similitudes entre un un subgrupo de un conjunto de posibles implementaciones. Conseguida gracias a la herencia. 5. Agrupaci´on de rutinas relacionadas. Conseguida mediante la construcci´on de clases. Existen dos aspectos importantes en la reutilizaci´on de c´odigo: por una parte, la creaci´on de componentes reutilizables, y por otra parte, la utilizaci´on de dichos componentes. En C# existe una gran colecci´on (a´ un en crecimiento) de elementos reutilizables. Nosotros trataremos ahora brevemente el contenido de System.Collections y la interface Collections.IEnumerable. La implementaci´on de la interface IEnumerate por parte de una clase contenedora dota a ´esta de la posibilidad de emplear la instrucci´on foreach, cuya sintaxis es: foreach (Tipo nombreVariable in nombreArray) Mediante la instrucci´on foreach tenemos en cada iteraci´on en la variable nombreVariable el contenido de la clase contenedora. Obs´ervese que este valor no puede ser modificado, s´olo puede consultarse. Para construir un iterador necesitamos alguna manera de avanzar el iterador, devolver el siguiente valor en la colecci´on y comprobar en cada instante si ya hemos iterado sobre todos los elementos de la colecci´on. Para ello la interface IEnumerable declara el m´etodo IEnumerator GetEnumerator(). No obstante, para realizar una implementaci´on concreta deben implementarse (valga la redundancia) los m´etodos MoveNext() y Reset(), as´ı como el m´etodo de acceso Current, todos ellos de la interface IEnumerator. Ejemplo 4.10 El c´ odigo 4.5 muestra un ejemplo de una clase que implementa la interface IEnumerable, de modo que se podr´ a emplear la instrucci´ on foreach con objetos de dicha clase (como puede observarse en la l´ınea ). Se trata de un array de 10 enteros cuyo iterador s´ olo indexa los elementos que ocupan posiciones impares (teniendo en cuenta que la primera posici´ on es la 0, la segunda la 1, ...). El resultado mostrado por pantalla ser´ıa: 1 3 5 7 9 11 13 15 17 19 Iterando... 3 7 11 15 19 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
using System ; using System . Collections ; class ArrayImpar : IEnumerable { public int [] a = new int [10]; public IEnumerator GetEnumerator () { return ( IEnumerator ) new ArrayImparEnumerator ( this ) ; } public int this [ int i ] { get { return a [ i ]; } set { a [ i ] = value ; } } private class A r r a y I m p a r E n umerator : IEnumerator { public A r r a y I m p a r E n u m e r ator ( ArrayImpar a )
36
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
Tema 4. Herencia
{ this . a = a ; indice = -1; } public void Reset () { indice = -1; } public bool MoveNext () { indice += 2; return ( indice <= 9) ; } publoc object Current { get { return a [ indice ]; } } private int indice ; private ArrayImpar a ; } class ArrayImparTest { public static void Main () { ArrayImpar ai = new ArrayImpar () ; for ( int i = 0; i < 10; i ++) { ai . a [ i ] = 2 * i + 1; Console . Write ( ai . a [ i ] + " ") ; } Console . WriteLine ("\ nIterando ...") ; foreach ( int elemento in ai ) Console . Write ( elemento + " ") ; } } C´odigo 4.5: Implementaci´on de un iterador Tratando ahora la cuesti´on referente a la ocultaci´on de informaci´on en los casos en los que existe herencia diremos que en tales situaciones se viola el principio de caja negra, pues puede ocurrir que una subclase apoye algunos de sus m´etodos en la implementaci´on concreta conocida de la clase base. ´ Este es un factor negativo para la extensibilidad, pues si modificamos la clase base, todas sus subclases que hayan actuado de la manera anteriormente indicada se ver´an afectadas y probablemente tambi´en deber´an modificar su implementaci´on. Adem´as de los tres tipos de acceso vistos en la secci´on 2.1.2 existe otro m´as: internal. Una propiedad con este modificador de acceso s´olo puede ser accedida desde c´odigo perteneciente al mismo ensamblado en el que se haya definido dicha propiedad. La herencia privada, que est´a permitida en C++, no es soportada en C#. Sin embargo, s´ı se puede inhabilitar la herencia de una clase mediante la palabra clave sealed. Este tipo de clases, llamadas selladas, pueden ser tratadas de una forma m´as eficiente por parte del compilador, ya que sus m´etodos no pueden ser redefinidos y por tanto se declaran autom´aticamente como no virtuales.
4.7 Herencia m´ ultiple
4.7.
37
Herencia m´ ultiple
Definici´ on 4.10 Se dice que existe herencia m´ ultiple de clases (tambi´en puede ser de interfaces) cuando una clase hereda directamente de m´as de una clase. Ejemplo 4.11 La figura 4.6.(a) muestra un ejemplo de herencia simple, mientras que la figura 4.6.(b) hace lo propio con la herencia m´ ultiple.
(a)
(b)
Figura 4.6: Herencia simple y m´ ultiple En C#, a diferencia de C++, no est´a permitida la herencia m´ ultiple de clases. Sin embargo, al igual que en Java, este hecho se solventa mediante la herencia m´ ultiple de interfaces y simple de clases. Ejemplo 4.12 En el c´ odigo 4.4 as´ı como en la figura 4.5 se muestra un claro ejemplo de herencia m´ ultiple de interfaces.
4.7.1.
Problemas: Colisi´ on de nombres y herencia repetida
Aunque la herencia m´ ultiple nos ofrece varias ventajas a la hora de programar seg´ un la metodolog´ıa OO, tambi´en implica algunos inconvenientes. A continuaci´on explicaremos dos de ellos: la colisi´on de nombres y la herencia repetida (aunque ´esta u ´ltima, como veremos m´as adelante, no se da en C#). La colisi´ on de nombres tiene lugar cuando una clase hereda de dos o m´as clases un m´etodo con el mismo nombre y diferente implementaci´on. Ejemplo 4.13 En la figura 4.7 se muestra un ejemplo de colisi´ on de nombres debida a la herencia m´ ultiple.
Figura 4.7: Herencia m´ ultiple y colisi´on de nombres
La herencia repetida se da cuando una misma propiedad es heredada por distintos caminos m´as de una vez.
38
Tema 4. Herencia
Figura 4.8: Herencia m´ ultiple y herencia repetida Ejemplo 4.14 En la figura 4.8 se muestra un ejemplo de herencia repetida debida a la herencia m´ ultiple. En C#, como ya hemos dicho, no hay herencia repetida, pero s´ı puede darse colisi´on de nombres en el caso en el que una interface hereda de dos o m´as interfaces un m´etodo con el mismo nombre. En este caso existen dos posibilidades: 1. Sobrecarga, si difieren en la signatura y/o en el valor de retorno. 2. Compartici´on, si tienen la misma signatura y valor de retorno.
4.7.2.
Ejemplos de utilidad
Ejemplo 4.15 En el c´ odigo 4.6 se muestra un ejemplo en el que una clase C hereda de dos interfaces distintas A y B un m´etodo con el mismo nombre f(object o), resultando en una ambig¨ uedad de nombres. 1 2 3 4 5 6 7 8 9 10 11 12
interface A { void f ( object o ) ; } interface B { void f ( object o ) ; } class C : A , B { // A . f () o B . f () ?? public void g () { f () ; } } C´odigo 4.6: Ambig¨ uedad de nombres debida a la herencia m´ ultiple Podemos resolver la potencial ambig¨ uedad de nombres dentro de la clase C incluyendo declaraciones expl´ıcitas para cada instancia de interface heredada. Es m´ as, tambi´en podemos implementar un m´etodo f() en C para el caso en el que se manipulen directamente objetos de dicha clase. Estos cambios pueden observarse en el c´ odigo 4.7.
1 2 3 4 5
class C : public public public }
A, B void void void
{ f ( string s ) { ... } A . f ( object o ) { ... } B . f ( object o ) { ... } C´odigo 4.7: Soluci´on a la ambig¨ uedad de nombres
4.8 C#, Java y C++: Estudio comparativo
39
Ejemplo 4.16 Sea la jerarqu´ıa mostrada en la figura 4.9. Las letras enmarcadas representan interfaces y las no enmarcadas, clases. En esta circunstancia podr´ıamos pensar que se dar´ıa un caso de herencia repetida, pero no es as´ı.
Figura 4.9: Herencia m´ ultiple sin herencia repetida (I) La clase Z no necesita implementar los m´etodos de la interface W, pues ya hereda dichas implementaciones de la clase X. No obstante s´ı deber´ a implementar aquellos m´etodos de la interface Y que no est´en en la interface W. Ejemplo 4.17 Sea ahora la jerarqu´ıa mostrada en la figura 4.10. Al igual que en el ejemplo anterior, podr´ıamos pensar que bajo estas premisas nos encontramos ante un caso de herencia m´ ultiple. Pero, al igual que en el ejemplo anterior esto no es as´ı.
Figura 4.10: Herencia m´ ultiple sin herencia repetida (II) Si bien es cierto que la clase Z hereda los m´etodos de la interface W por dos v´ıas, no resulta esto ser un problema, pues como ya vimos en el ejemplo 4.15, se puede solucionar f´ acilmente. Otra soluci´ on distinta a la expuesta en el ejemplo 4.15 pero igualmente v´ alida consiste en implementar una u ´nica vez cada m´etodo “repetido”.
4.8.
C#, Java y C++: Estudio comparativo
Adem´as de las continuas referencias que durante todo el texto se han hecho hacia otros lenguajes OO como Java y C++, a continuaci´on se muestran s´olo algunas comparaciones entre dichos lenguajes y C#. C# es muy parecido a Java. Normalmente el recolector de basura (garbage collector ) se encarga autom´aticamente de los objetos que ya no son necesarios (por ejemplo, los no
40
Tema 4. Herencia
referenciados por ninguna variable). Toda clase tiene como clase base de su jerarqu´ıa a la clase object. En C# todo es un objeto. Los tipos primitivos tales como int pueden ser tratados como objetos. No es este el caso de Java donde los tipos primitivos son estrictamente “tipos valor”. C# permite paso por referencia en los tipos primitivos. C# incorpora la instrucci´on foreach junto con la interface IEnumerator, lo cual permite la construcci´on de un iterador. C# tiene verdaderos arrays multidimensionales. C# ha retenido, aunque de una forma m´as l´ogica y simple, la habilidad de C++ para sobrecargar operadores. En C# existe el concepto de propiedades, lo cual proporciona los m´etodos de acceso get y set para consultar y modificar los valores de los miembros de datos de una clase. Un programador de C# puede convertir r´apidamente su c´odigo a Java, pero no a la inversa. De hecho Java es conceptualmente un subconjunto de C#. C++ es muy complejo e inseguro. Ofrece demasiadas oportunidades al programador de manejar err´oneamente los punteros en aras de la eficiencia. C++ es dependiente del sistema. No est´a preparado para la Web. No maneja la memoria como lo hacen C# o Java. El mero hecho de que el n´ ucleo de C# est´e desprovisto del tipo puntero simplifica enormemente su compresi´on por parte de un estudiante de POO.
´Indice de Figuras 2.1. 2.2. 2.3. 2.4. 2.5. 2.6. 2.7.
Clientela y Herencia entre A y B . . . . . . . . . . . . Variables en los lenguajes tradicionales . . . . . . . . . Variables tipo “puntero” en los lenguajes tradicionales Variables en los lenguajes OO . . . . . . . . . . . . . . Ejemplo de copia superficial . . . . . . . . . . . . . . . Ejemplo de copia profunda . . . . . . . . . . . . . . . Ejemplo de variables id´enticas . . . . . . . . . . . . . .
4.1. Jerarqu´ıa de clases . . . . . . . . . . . . . . . . . 4.2. Herencia de especializaci´on y de implementaci´on 4.3. Ejemplo de estructura de datos polimorfa . . . . 4.4. Herencia y redefinici´on de m´etodos . . . . . . . . 4.5. Herencia e implementaci´on de interfaces . . . . . 4.6. Herencia simple y m´ ultiple . . . . . . . . . . . . . 4.7. Herencia m´ ultiple y colisi´on de nombres . . . . . 4.8. Herencia m´ ultiple y herencia repetida . . . . . . 4.9. Herencia m´ ultiple sin herencia repetida (I) . . . . 4.10. Herencia m´ ultiple sin herencia repetida (II) . . .
41
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
8 11 11 11 12 12 13
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
25 26 29 30 33 37 37 38 39 39
42
´ INDICE DE FIGURAS
´Indice de Tablas . . . .
6 6 6 6
3.1. Algunas Excepciones est´andar . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2. Propiedades de las excepciones . . . . . . . . . . . . . . . . . . . . . . . . . .
19 19
4.1. Propiedades de las excepciones . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2. Operadores unarios “sobrecargables” . . . . . . . . . . . . . . . . . . . . . . . 4.3. Operadores binarios “sobrecargables” . . . . . . . . . . . . . . . . . . . . . . .
26 27 27
2.1. 2.2. 2.3. 2.4.
Tipos enteros primitivos . Tipos flotantes primitivos Otros tipos primitivos . . Palabras reservadas . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
43
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
44
´ INDICE DE TABLAS
´Indice de C´ odigos 2.1. 2.2. 2.3. 2.4. 2.5. 2.6. 2.7. 3.1. 3.2. 3.3. 3.4. 3.5. 4.1. 4.2. 4.3. 4.4. 4.5. 4.6. 4.7.
Clase Luna . . . . . . . . . . . . . . . . . . . . . . . . Ejemplo de Ocultaci´on de Informaci´on . . . . . . . . . Otro ejemplo de Ocultaci´on de Informaci´on . . . . . . Ejemplo de Clases Anidadas . . . . . . . . . . . . . . . Ejemplo de Constructores . . . . . . . . . . . . . . . . Ejemplo de implementaci´on del m´etodo Clone() . . . Lista gen´erica doblemente enlazada . . . . . . . . . . . Ejemplo del uso de asertos . . . . . . . . . . . . . . . . Ejemplo del uso de excepciones . . . . . . . . . . . . . Ejemplo del relanzamiento de excepciones . . . . . . . Uso de excepciones con m´as informaci´on . . . . . . . . Ejemplo del uso de excepciones en vez de asertos . . . Ejemplo de Sobrecarga de Operadores . . . . . . . . . Redifinici´on de un m´etodo heredado . . . . . . . . . . Clase abstracta Figura . . . . . . . . . . . . . . . . . . Herencia M´ ultiple mediante interfaces . . . . . . . . . Implementaci´on de un iterador . . . . . . . . . . . . . Ambig¨ uedad de nombres debida a la herencia m´ ultiple Soluci´on a la ambig¨ uedad de nombres . . . . . . . . .
45
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . .
5 7 8 9 12 13 14 18 18 20 21 22 28 31 31 33 35 38 38
46
´ ´ INDICE DE CODIGOS
Bibliograf´ıa [Lip02] Stanley B. Lippman. C# Primer. A Practical Approach. Addison Wesley, 2002. [Mol04] J. Garc´ıa Molina. Apuntes de programaci´on orientada a objetos. 2004. [Poh03] Ira Pohl. C# by Dissection. Addison Wesley, University of California, 2003.
47