Curso de C# Este mes nos metemos de lleno con las clases. Será una primera aproximación pero no superficial. El tema da para varios artículos, así que intentaremos amenizarlo como siempre: con práctica. Vamos a terminar con las sentencias de control y nos sumergiremos en las clases: la columna vertebral del lenguaje. Para ello, construiremos nuestra propia clase y realizaremos ejemplos. Además, como va a ser habitual, veremos algún programa que haga uso de alguna de las clases más "interesantes". Hola de nuevo. Lo primero es agradecer los correos electrónicos que he recibido, ya sean de apoyo, de crítica, aportando sugerencias o de planteamiento de dudas. Eso quiere decir que no os es indiferente el curso. Además, haciendo caso de algunas de esas sugerencias, voy a incluir en cada artículo algún pequeño programa que haga uso de la biblioteca de clases a modo de "utilidad", para que aquellos que tenéis un nivel un poco más "adelantado", podáis ir viendo cosas interesantes a la vez que seguimos el curso. Y vamos a empezar a hacerlo este mes (lo siento, antes era inviable) ya que es cuando vamos a "atacar" a las clases. De este modo tod@s podremos comprender mejor esos ejemplos "útiles". Y sin más preámbulos..... empezamos. En la entrega anterior vimos bucles y sentencias de control (entre otras cosas) y todo aplicado en un programa totalmente comentado. Pues bien, antes de meternos con las clases vamos a "cerrar" el tema de las sentencias de control explicando...... La instrucción switch. Se trata de una instrucción de selección, ya que permite seleccionar entre varias opciones. Esta instrucción podría ser reemplazada por una serie de instrucciones if anidadas, pero es innegable que el uso de switch resulta más eficaz como podréis ver. Podríamos decir que switch selecciona para su ejecución una sentencia o bloque de sentencias que tiene(n) asociada(s) una etiqueta que corresponde con el valor de la expresión evaluada en el switch. Vamos, que dependiendo del valor resultante de la expresión del switch, se ejecutará el código que esté bajo la etiqueta que coincida con el resultado. Esto no siempre es así literalmente, pero es una sencilla forma de definirlo. Veamos la sintaxis de switch: switch { case resultado1: bloque_sentencias1; case resultado2: bloque_sentencias2; .......... default: bloque_sentencias_por defecto } La expresión del switch se evalúa y dependiendo del resultado se ejecuta el bloque de sentencias correspondiente. La expresión debe ser de tipo entero (int, char, byte, short) o de tipo string. No se permiten, por ejemplo, expresiones de punto flotante. Por otra parte, la expresión, como tal, puede ser simplemente una variable y las constantes resultado1, resultado2, etc... deben coincidir en su tipo (o ser compatibles) con el de la expresión del switch, además de que no se pueden repetir dos resultados case en la misma instrucción switch. Y ¿qué es eso de default?. Pues cuando ninguno de los resultados case coincide con el resultado de la expresión, se ejecuta el bloque_sentencias_por_defecto. Esto es opcional y si no existe, no se ejecuta nada en caso de la no coincidencia. Es muy importante señalar que cada bloque de sentencias debe terminar con una instrucción break. Esto es así porque, a diferencia de C++ y Java, en C# no se permite continuar de una instrucción case a la siguiente. No se trata de un capricho de los desarrolladores, sino que de este modo, el compilador puede reorganizar internamente el orden de las instrucciones case para optimizar el código y además se evita que el programador se equivoque dejando "pasar" de un case a otro sin querer. Sin embargo sí que podemos hacer que varios case tengan el mismo código sin necesidad de repetirlo (ojo: no continuamos de un case a otro, sino que ejecutan el mismo bloque). Si nos fijamos en el Listado 1 y en el resultado de la ejecución del programa (Figura 1), veremos que si x vale 1,2 ó 3, el bloque que se ejecuta es el del case 3.
Figura 1
Para que nadie pueda decir que me dejo intencionadamente algo en el tintero, tengo que decir que existe en C# la instrucción goto (salto incondicional) que se puede utilizar también en la instrucción swith para "saltar", dentro de la misma, de un case a otro o al default (goto case 3, goto default), ya que son etiquetas y goto siempre salta a etiquetas (No se puede saltar a un switch desde fuera). No voy a extenderme ya que soy de los que piensa que el uso de goto es contraproducente y siempre evitable. Simplemente decir que si usamos el goto dentro de un switch (al final de un bloque) no hará falta terminar el bloque correspondiente con un break. Por último señalemos que, como era de esperar, podemos anidar instrucciones swith siempre y cuando respetemos la sintaxis. Lo haríamos, por ejemplo, así: switch { case : switch { case : bloque2-1; case : bloque2-2; default: bloque2-default; } case : bloque1-2; .... default: bloque1-default; } Las clases. Más a fondo. Desde que empezamos el curso estamos escribiendo clases. Ya dijimos lo que era una clase en la primera entrega y ahora insistimos: Las clases son el pilar, la columna vertebral del C#, y con ellas podremos desarrollar programas en C#. Podemos usar las clases que ya existen para .Net y que se encuentran en la librería de clases, y además podemos crearnos las nuestras para ir "encapsulando" nuestros objetos y obtener funcionalidades propias. Para ilustrar la programación de clases vemos a construir la nuestra y la iremos modificando a medida que vayamos avanzando. Y qué mejor que crear una clase llamada "revista". Ya sabemos la sintaxis básica para escribir una clase (lo hemos estado haciendo desde el primer día), así que vamos a ello: class revista { public string nombre; public int paginas; public double precio; } Sólo hemos definido miembros de datos (data members) que son las variables de instancia que almacenarán el nombre, el número de páginas y el precio de la revista. Además las hemos definido como públicas, lo que significa que podrán ser accesibles desde fuera de la clase (de momento, les asignaremos valores desde fuera, así que deben ser públicas). En el Listado 2 se encuentra el código completo para probarlo. No debemos olvidar que estamos hablando de objetos y que uno de los conceptos de la encapsulación de objetos es el control de acceso a sus miembros, por lo tanto deben existir más posibilidades que permitan ese control. Efectivamente, mediante palabras reservadas de acceso , podemos establecer el acceso a los miembros de la clase y/o a las variables de instancia: public: El miembro es accesible desde cualquier función externa y/o interna protected: El miembro sólo es accesible desde los métodos (funciones) miembros de la clase y a los de las clases derivadas de ella. private: El miembro sólo es accesible desde los métodos miembros de la clase. internal: El miembro es accesible desde cualquier función externa y/o interna del mismo ensamblado (describimos lo que era un ensamblado en la primera entrega del curso) y es private para el resto de clases. Podemos decir que el miembro es public para todo el ensamblado donde se encuentre. protected internal: El miembro sólo es accesible para los miembros de la clase o de las clases derivadas, pero del mismo ensamblado (protected para el ensamblado y private para el resto). El acceso por defecto a los miembros de una clase es private, luego si no especificamos palabra reservada de acceso a un miembro, éste será private. Es importante asimilar las palabras reservadas de acceso a los miembros de una clase ya que iremos modificando nuestra clase poco a poco para encapsular nuestro modelo de revista. Y mucho más importante es darse cuenta que, a diferencia de C++, las palabras reservadas de acceso no son asociativas. Es decir, si declaramos un miembro public y al siguiente no le indicamos palabra clave de acceso, éste no hereda la declaración public, sino que es private. Como ya sabemos, al definir una clase estamos creando un nuevo tipo de datos (revista) por lo que podemos crear objetos de este tipo de datos mediante la forma ya vista en anteriores entregas: revista nombre_objeto = new revista(); Más tarde asignamos valores a los miembros de datos mediante simples asignaciones. Insistimos en que esto lo podemos hacer al estar declarados como public. Vale, pero lo normal es que nuestra clase esté en un fichero separado del resto de clases y de nuestro programa principal para que sea más "independiente" ¿no os parece?. Bien, pues separamos el código de
la clase revista y el del programa principal, cada uno a un fichero separado (en mi caso, a revista.cs y ejemplo_revista.cs) e incluyendo sólo en el programa principal la clausula "using System". Si estáis utilizando Visual Studio hay que agregar los dos archivos al proyecto y generar la solución. Si lo hacéis desde consola de Windows, habrá que compilar con csc: csc ejemplo_revista.cs revista.cs Y finalmente, si estáis desarrollando con Mono, desde consola podéis teclear: mcs ejemplo_revista.cs revista.cs Si ahora ponemos los miembros de la clase revista como private (o simplemente les quitamos el public para hacerlos, por defecto, private) y compilamos.....(ver Figura 2)
Figura 2 Lo que decíamos. Los miembros sólo son accesibles desde dentro de la clase revista, por lo que el programa principal no puede asignarles valor ya que no los "conoce". Pero nosotros queremos que sean privados a la clase, ya que así encapsulamos nuestro objeto y no lo exponemos al "mundo", por lo que debemos utilizar los métodos. Los métodos serán los que establezcan los valores de las variables miembro y lo harán desde dentro. De este modo mantenemos y controlamos los miembros de datos propios. Un método es el "equivalente" a una función ó subrutina, pero en la programación orientada a objetos y permiten manipular los datos de la clase así como acceder a ellos en determinadas situaciones. En C#, todo método debe devolver un valor (aunque sea void) y tiene una accesibilidad al igual que los miembros de datos, con lo que determinamos su exposición al "mundo". Bien, pues para nuestro pequeño programa debemos crear al menos los métodos que permitan "setear" (del ingles set, jejeje) los datos y recuperarlos. En el Listado 3 tenéis cómo quedan los dos ficheros de código. Fijaros que hemos creado para cada miembro de datos un método get y otro set: Uno establece y otro recupera el valor. Esto lo hacemos así para ilustrar la accesibilidad, la encapsulación y la creación de métodos. No quiere decir que ésta sea la mejor opción. Uso de "this" También os habréis dado cuenta de que para referenciar al objeto actual dentro de una clase hemos usado la palabra reservada this (aunque no era necesario como luego os explicaré). Resulta que this es una variable de referencia que apunta a la instancia de la clase y que viene heredada desde los primeros tiempos de la programación orientada a objetos, allá por los años 80. Entonces a la variable se le llamó self, pero en C++ y en C# pasó a denominarse this. Nosotros la utilizaremos para resolver posibles "ambigüedades" en el código de una clase, como por ejemplo en este caso: imaginemos que tenemos unas variables de clase llamadas alto y ancho y que también tenemos un método que les asigna valores mediante dos parámetros. Si en este método, los parámetros recibidos los llamamos con el mismo nombre (cosa que es perfectamente viable) tendremos que hacer uso de this para distinguir el parámetro de la variable de la clase. this.alto = alto ; this.ancho = ancho ; Por último, destacar que en los métodos declarados como static no se puede utilizar this. ¿Y eso por qué? pues porque ya dijimos que es posible llamar a un método static sin necesidad de crear una instancia de la clase, y entonces ¿a quién apuntaría this?. Por supuesto podemos crear métodos que utilicen los datos internos y nos devuelvan resultados. Por ejemplo, podemos crear un método (público esta vez) para que nos calcule el coste por página de la revista en cuestión: public double coste_pagina() { return (this.precio / this.paginas); } y llamarlo desde el programa principal, por ejemplo con: Console.WriteLine("El coste por pagina es de {0} euros", arroba.coste_pagina()); ¿A que nos sale barata la revista? jejejeje Ejemplos útiles con clases. Empezamos este apartado viendo un espacio de nombres (namespace) muy, muy utilizado actualmente por motivos evidentes: System.Net Primero vamos con la clase Dns (http://msdn.microsoft.com/library/default.asp?url=/library/enus/cpref/html/frlrfsystemnetdnsclasstopic.asp) que nos permite recuperar información sobre un host específico mediante DNS. La información se devuelve en una instancia de la clase IPHostEntry, de donde obtendremos la(s) IP(s) mediante AdressList: un array que contiene las posibles ip's del host al que hace referencia el objeto IPHostEntry. El procedimiento es sencillo: Con Dns.GetHostByName (nombre_del_host) obtenemos la información necesaria y la introducimos en una instancia de la clase IPHostEntry (ips en nuestro ejemplo). Luego solo hay que mostrar el array ips.AdressList para obtener la(s) ip(s) del host especificado. En el Listado 4 tenéis el código de ejemplo. Podéis observar que si no pasamos ningún argumento (host) al programa, obtenemos con Dns.GetHostName () el nombre de la máquina local para mostrar la ip.
En la Figura 3 tenéis el resultado de ejecutarlo contra www.google.com ;-)
Figura 3 Por último, veamos un ejemplo con la estructura DateTime (http://msdn.microsoft.com/library/default.asp?url=/library/enus/cpref/html/frlrfsystemdatetimememberstopic.asp) y de paso usamos la instrucción switch que hemos visto en este artículo (Listado 5). La usamos para visualizar la fecha actual, tanto en formato corto como en formato largo, dependiendo del parámetro que usemos. No hay mucho que explicar de este ejemplo. El uso de switch es el que ya hemos visto y la utilidad de DateTime es evidente. Os dejo a vosotros el modificar el código para mostrar la hora, minutos, segundos y usar los métodos disponibles (mirad la documentación en el link) para jugar con las horas, minutos, segundos, dias, meses, años, etc.... Hasta el mes que viene. Llegamos al final del artículo habiendo visto mucho código, y alguno casi repetido en cuanto al resultado final, que no en cuanto a la manera de escribirlo. Eso es en parte lo que pretendía: ver el mismo código de diferentes formas. De esta manera aprendemos las cosas "viéndolas". En el próximo artículo seguiremos con las clases. Veremos el por qué del modificador static antes de la palabra clave de acceso y la diferencia entre miembro de una instancia y miembro de una clase (durante el artículo he hecho referencia indistintamente a ambos, incluso llamando miembro de clase a lo que no lo es, pero no es lo mismo. Lo he hecho así ya que todavía no hemos definido la diferencia). También continuaremos viendo mini-ejemplos de utilización de clases de .Net haciendo un programa que nos baje una página web desde internet con muy pocas lineas de código. Yorkshire
[email protected] DESPIECE Listado 1. Ejemplo de switch. using System; class ejemplo1 { static void Main() { for (int x=0; x