Introducción al lenguaje y características derivadas de su entorno de ejecución.

A lo largo de los últimos meses, hemos estado hablando de algunas características del nuevo entorno .NET Framework que Microsoft está desarrollando y a medida que pasaba el tiempo, aquí en Grupo EIDOS, nos hemos ido dividiendo los roles de trabajo, para poder abarcar todo este nuevo paradigma de la programación con la profundidad que se merece. Así pues, tras unos primeros devaneos con Visual Studio, el nuevo Visual Basic .NET y su interfaz de usuario (ver artículo del mes de Abril), cedo los trastos de Visual Basic a mi compañero Luis Miguel Blanco, de sobra conocido por los lectores de Algoritmo y de La Librería Digital, para dedicarme por entero al nuevo lenguaje de programación: C# (C-Sharp).

Pretendemos, a lo largo de los próximos meses, ir publicando un curso completo del lenguaje que sirva de introducción a nivel básico-intermedio para los lectores, de forma que, cuando hacia el mes de Noviembre aparezca el producto, ya dispongan de una buena base para poder trabajar y continuar la producción con este lenguaje (como quizá sepa ya el lector, el pasado día 21 de Junio, coincidiendo con el primer evento Tech-Ed del año, celebrado en Atlanta (Georgia), se hizo, por fin pública la beta 2 de Microsoft .NET Framework, así como -para usuarios del MSDN y otros- la beta 2 de Visual Studio .NET. A título personal, tengo fundadas razones para dudar de la demora hasta Febrero/2002, como se ha comentado en algunos foros, y por si esto fuera poco, el Dr.GUI, mentor ficticio del MSDN de Microsoft afirmaba en su 6a entrega de sus cursos sobre .NET que "a partir del próximo número, todo el código será compatible con la Beta 2 de .NET Framework, puesto que Microsoft no planea ya ningún cambio significativo al API entre la Beta 2 y el producto final, solamente ajustes, optimización y algún cambio de mínima importancia", lo que da idea de lo avanzado del proyecto).

Introducción a C#

El lenguaje C# se presenta como el último invento en materia de lenguajes de programación, y constituye también la más reciente y ambiciosa apuesta en este sentido por parte de Microsoft. Quizás, lo primero que habría que aclarar, es que, de todo el .NET Framework, es la única parte que puede considerarse terminada, hasta el punto de que el propio Visual Studio .NET ha sido construido al 90% en C# y el 10% restante en C++. Por otro lado, el lenguaje merece el calificativo de estándar, en el sentido de que –al igual que algunos otros aspectos del entorno- está siendo sometido a estandarización por parte de ECMA, la misma entidad de normalización que llevó a cabo la estandarización de Javascript.

Nota: En una reciente visita Madrid por parte de dos de los promotores y desarrolladores de .NET (Ari Bixhorn y Drew Fletcher), a la que tuvimos ocasión de asistir, se respondía a una pregunta del amigo Francisco Charte en ese sentido, afirmando que ellos eran conscientes de que ECMA estaba manteniendo reuniones mensuales con los equipos de estudio de la normalización (los Working Teams), y que esperaban que dicha labor estuviera completada, sino antes, sí para el momento de la aparición del producto.

Rumores sobre el impacto de C# en el mundo de la programación



Es natural que, ante la creciente comunidad mundial de profesionales de las TI, la aparición de un nuevo lenguaje tenga más impacto hoy que en etapas anteriores, más arcaicas. Los medios lo favorecen, y la difusión de Internet, también. Por eso, nos hemos paseado por algunas revistas en la Web donde los gurús mas conocidos dan sus opiniones.

La impresión de estos autores podríamos resumirla así: C# supone una mejora respecto a otros lenguajes existentes por dos razones básicas: primero, por que es el último, y por lo tanto, el más adaptado a las necesidades actuales del programador y el que más ha aprendido de los demás, heredando lo mejor de cada entorno, y añadiendo las cosas que los programadores solicitaban. En segundo término, por que su creador principal (el jefe del equipo de desarrollo, el danés Anders Hejlsberg, de quien Bixhorn y Fletcher hablaban maravillas), ha podido hacer un excelente trabajo basado su experiencia personal (es el diseñador de Turbo Pascal 5.5 y Delphi), con tiempo suficiente, y un pequeño pero impresionante equipo de Anders Hejlsberg, arquitecto principal del lenguaje C#

colaboradores entre los que figuran Peter Kukol, Jeffrey Richter, etc.

De hecho, el propio nombre del lenguaje (se pronuncia CSharp) fue una decisión posterior, como se ha sabido, en el sentido de que era una extensión de C++: C++++ (con 4 +), para indicar su origen principal. Si bien esto es cierto, no lo es menos que en un famoso e-mail que se hizo público a raíz del contencioso Sun- Microsoft, dirigido por Hejlsberg a Bill Gates, se habla del proyecto de desarrollo del nuevo lenguaje con frases esclarecedoras sobre la intención con que se construía...

Así, podemos leer (la traducción es mía) “Peter Kukol y yo hemos estado trabajando en un conjunto de extensiones de Java que yo definiría como “un cuidadoso maridaje entre Java y C” (Original: “Peter Kukol and I have been working on a set of Java language extensions that I would characterize as "A careful marriage of Java and C."). Y más adelante, en una significativa referencia al nombre del nuevo engendro, afirma “A falta de un nombre mejor, estamos llamando al lenguaje J++” (Original: For lack of a better name we're calling the language J++), en una clara alusión a sus fundamentos basados también en el lenguaje Java. Finalmente, otra frase entresacada de este e-mail, explica claramente el propósito del diseño al afirmar que “lo que muchos programadores quieren es una versión “limpia” de C++ que aporte los beneficios de productividad de Java combinados con las características de bajo nivel de C” (Original: what a lot of programmers want is a "cleaned up: version of C++ which gives them the productivity benefits of Java coupled with the lower level features of C”).



Naturalmente, las reacciones han sido muy variadas: desde aquellos que –por el mero hecho de que el producto sea de Microsoft- lo desdeñan, hasta (una mayoría), que aprecian sus nuevas características en lo que valen y promueven su utilización. Quizá por eso, sean de especial valor las opiniones de algunos gurús (Ben Albahari de Genamics, Christopher P. Calisi de SD Times, e incluso Jim Farley de O’Really), que estando dedicados a la programación en Java no les importa reconocer en sus comparativas (el lector tiene las referencias bibliográficas al final de este articulo), que “si eres un seguidor acérrimo de Java... (el utiliza la palabra evangelist, que tiene otras connotaciones en inglés) no te fíes de que C# sea simplemente otro marketing de Microsoft. Mejor, prepárate.”

Bien, pues vamos a prepararnos. Sobre todo teniendo en cuenta que, como se repite con más frecuencia cada vez, éste cambio a .NET va a afectar a la forma de programar al menos por los próximos 10 años...

El lenguaje C# y el Entorno Común de Ejecución (CLR)

Una de las características principales de C# es que se trata de un lenguaje que compila (por defecto) a un formato intermedio, al estilo de Java, denominado Intermediate Language (IL), que posteriormente, debe de ser interpretado por un entorno de ejecución, una máquina JIT (just-in-time), también al estilo de Java. La gran diferencia respecto a Java es que, ése intérprete será común a todos los lenguaje soportados por el entorno de ejecución (veintitantos ya...) y mediante este mecanismo permitirá que los componentes realizados en cualquier lenguaje puedan comunicarse entre sí.

Se trata pues, de una extensión del concepto inicial que dio origen a Java: en lugar de un único lenguaje para muchas plataformas, se pretende un entorno común multiplataforma, que soporte muchos lenguajes, basándose en que todos ellos compilen a un mismo código intermedio. Para hacer viable esta idea, se ha optimizado considerablemente la velocidad, respecto a Java y ya se están anunciando los primeros .NET Framework para otras plataformas: El pasado mes de Marzo, Steve Ballmer anunciaba la disponibilidad para Linux, y están en marcha las implementaciones para Unix, McIntosh System-8 y BEos.

Este lenguaje intermedio, es gestionado por un mecanismo llamado Entorno Común de Ejecución (Common Language Runtime), encargado, además, de la gestión de memoria, y en general, de las tareas más importantes, relacionadas con la ejecución de programas.

Programación en C#



Para una primera toma de contacto con el lenguaje, nada mejor que ponernos manos a la obra e ir analizando algunos casos prácticos, empezando por el ya clásico “Hola Mundo”.

Principios básicos de la construcción de programas en C#

Pero antes, recordemos las bases sobre las que se asienta la construcción de un programa escrito en C#:

1) Todo programa compilado para el entorno común de ejecución (CLR) comparte un mismo código intermedio (Intermediate Language ó IL) que debe ser ejecutado por un intérprete JIT que resida en la plataforma de ejecución. Aunque pueden distinguirse entre componentes y ejecutables, en éste último caso, el formato resultante es independiente del lenguaje que se utilizó en su escritura. El formato de los ejecutables independientes se denomina PE (Portable Executable).

2) El entorno común de ejecución (CLR) es invocado por cualquier programa escrito en el formato PE, y es quien se hace cargo del mantener la zona de memoria en la que se ejecuta el programa y suministra un sistema de tipos de datos común (Common Type System), que describe los tipos soportados por el intérprete y cómo estos tipos pueden interactuar unos con otros y cómo puede ser almacenados en metadatos.

3) Los servicios del sistema operativo se suministran mediante librerías (DLL) dispuestas por el CLR cuando se instala .NET Framework en una plataforma (Windows, McIntosh, Linux, etc.). Dichas librerías son referenciadas en el código de C# mediante la sentencia using seguida del nombre de la librería deseada. Tales declaraciones reciben el nombre de Espacios Calificados o Espacios con Nombre (Namespaces).

4) En C#, todo puede ser tratado como un objeto, aunque no obligatoriamente, y sin la penalización que supone esta característica en lenguajes orientados a objetos "puros", como SmallTalk. Por tanto cualquier código en C# estará incluido en una clase, ya sea esta instanciada o no. Además, se protege la inversión realizada previamente en desarrollo, ya que permite la llamada a componentes COM, a librerías nativas del API de Windows (DLL's), permite el acceso de bajo nivel cuando sea apropiado y los ejecutables y componentes producidos con él se integran perfectamente con otros ejecutables o componentes realizados en otros lenguajes del entorno.

5) La sintaxis de C# recuerda bastante la de Java, si bien los conceptos implicados en la construcción de objetos, tipos, propiedades, métodos, y eventos no suelen ser idénticos teniendo -en ocasiones- notables diferencias conceptuales. La mayor



similitud se encuentra, como cabía esperar, en el tratamiento de las estructuras de control del programa (prácticamente idénticas a C++ y Java).

Primer ejemplo: Hola mundo desde C#

Siguiendo la tradición, comentaremos para empezar el código de la implementación de una salida por la consola con el texto “Hola Mundo”. El código es el siguiente:

Código fuente del “Hola Mundo” using System;

class HolaMundoCS

{

public static void Main()

{

Console.WriteLine("Hola desde C#");

}

}

Y, como es de esperar, produce una salida en una ventana del DOS (Consola), con el texto indicado entre paréntesis por el método WriteLine (“Hola desde C#”).

Análisis del código

Vamos a analizar el código fuente para irnos familiarizando con los mecanismos típicos de un programa en C#: La primera declaración indica que vamos a utilizar el espacio calificado (o NameSpace) llamado System. Un espacio calificado o nominado es una especie de firma digital con la que podemos hacer referencia a una librería o bien -mediante la declaración NameSpace- hacer que sirva de identificador de todos los módulos que pertenezcan a una aplicación, o un fragmento de aplicación. System es el NameSpace básico para todas las aplicaciones que requieran algún servicio del sistema (que son prácticamente todas). Note el lector que la declaración del espacio calificado podría haberse eliminado si en el código hubiéramos hecho referencia a la jerarquía completa, esto es, si hubiéramos escrito:

System.Console.WriteLine(“Hola Mundo”);

Más sobre el concepto de Namespace



Esto es así, porque el CLR pone a disposición de cualquier aplicación todos los servicios de sistema operativo a través de un conjunto jerárquico de clases con sólo hacer referencia a ellas. No obstante, y para facilitar la escritura del código utilizamos el concepto de Namespace (espacio calificado ó espacio con nombre). Un namespace es una forma adecuada de organizar clases y otros tipos de datos en una jerarquía. Podríamos decir que sirven al mismo tiempo para 3 propósitos: organizar (dentro de la jerarquía), para firmar (o marcar de forma única) fragmentos de código como pertenecientes a ese namespace, evitando colisiones con otros fragmentos que puedan contener definiciones iguales, y como método de abreviación, ya que una vez declarado el namespace podemos referirnos a sus contenidos en el código sin necesidad de repetir toda la secuencia jerárquica, tal y como aparece en la línea de código anterior.

Los espacios calificados pueden estar predefinidos, como en este caso, o ser declarados en el código por el propio usuario. Como nuestra aplicación utiliza una única salida de texto por el dispositivo por defecto (Consola), no es preciso hacer ninguna referencia a Windows ni a sus servicios. La consola está representada en dicho espacio por el objeto Console que dispone de métodos para escribir texto en la consola por defecto (Write() y Writeline()) y también para recoger entradas de datos del mismo dispositivo (Read() y Readline()) (Piense el lector, que puede haber casos en los que la labor de un programa es totalmente desatendida (esto es, no dispone de ninguna interfaz de usuario), ya que sus salidas no van dirigidas a un usuario en concreto, sino a un fichero, tal y como ocurre muchas veces con los componentes).

Main(): la función principal

La segunda línea contiene otra declaración importante. Como se ha indicado, todo el código de un programa en C# debe estar incluido en una clase. Además, en C# no existen métodos ni variables globales, por tanto, mediante la palabra reservada class declaramos la clase HolaMundoCS, que contiene todo el código ejecutable.

Esta clase está compuesta por una única función, que, en éste caso, hace las veces de método constructor y función principal (Main). Dicha función esta precedida de 3 calificadores: de ámbito (public), de acceso (static) y de valor de retorno (void) y es la función principal de todo programa ejecutable en C#. En ella es donde comienza y termina el control del programa y es donde se crean objetos y se ejecutan otros métodos.

También debemos tener en cuenta que Main() admite varios modos de implementación similares según devuelva o no valores (en nuestro ejemplo anterior, el calificador void indica que no devuelve ninguno), y también es posible eliminar la declaración public, haciendo que no se pueda acceder a el desde otras clases. Finalmente, se puede declarar un array de argumentos, de manera que recoja los argumentos que se le pasen al programa en la línea de comandos.



Versión del “Hola Mundo” que devuelve un "int" using System;

class HolaMundoCS

{

public static int Main()

{

Console.WriteLine("Hola desde C#");

return 0;

}

}

Versión del “Hola Mundo” que puede recibir argumentos en la línea de comandos using System;

class HolaMundoCS

{

public static void Main ( string[] args )

{

Console.WriteLine("Hola desde C#");

}

}

Es especialmente importante distinguir desde el principio estos calificadores (sobre todo, los dos primeros) por que establecen la manera en que el código que escribimos puede ser utilizado. Aunque, a continuación incluimos unas tablas resumen del modo de funcionamiento de todos los calificadores importantes del lenguaje, aclaremos ante todo (sobre todo para los poco versados en éste tipo de lenguajes) que el código incluido en una clase puede ser accedido básicamente de dos formas: mediante un objeto que haga referencia (instancie) dicha clase, o de forma directa, al estilo de como lo hacemos con las funciones de librería en otros lenguajes, sin utilizar ningún objeto explícito.



Clases, métodos y propiedades de tipo static

En éste segundo caso, la clase o el método al que queramos referirnos en ésta forma será declarado como static. A una clase definida de esta forma, se la denomina clase global. La jerarquía de objetos de .NET contiene muchas clases de este tipo, no pudiendo heredarse de ellas, y estando pensadas para ser usadas directamente. El método inicial de toda aplicación se declara como static precisamente para que el CLR pueda acceder a él sin necesidad de haberse instanciado ningún objeto. Cuando un método se declara como static, no es posible añadir los calificadores que permiten heredar de el (virtual, abstract) o sobrescribir su modo de implementación (override). Lo mismo es aplicable para las propiedades declaradas como static. Tampoco se permite la utilización dentro de ese código de la palabra reservada que se utiliza para referirse a la propia clase (this).

Entradas y salidas de datos

En principio, el lenguaje C# no dispone de forma nativa de ningún mecanismo de acceso directo a los dispositivos periféricos (consola, teclado, etc). Dichos accesos vienen suministrados mediante librerías dentro del Microsoft .NET Framework. En el caso de nuestro programa inicial, dicho acceso se obtiene mediante System.Console como hemos comentado, (que tambien da acceso al dispositivo de errores estándar (standard error). Si la aplicación requiere ventanas de Windows (lo más normal), sería preciso hacer referencia a la librería System.WinForms, y así sucesivamente.

Los procesos de Edición, Compilación y/o llamada al ejecutable.

Por otro lado, y debido a su naturaleza, el lenguaje C# no requiere de ningún entorno de desarrollo, estrictamente hablando. Basta con un editor de texto y una llamada al compilador pasándole los parámetros de compilación adecuados. Podemos utilizar el Bloc de Notas, o cualquiera de los editores Shareware disponibles en la red, para este propósito. El editor Antechinus es un buen ejemplo de ello. Para compilar un programa escrito en C#, podemos utilizar dos mecanismos, dependiendo del editor que estemos utilizando. Si el editor es el propio de Visual Studio .NET, basta con seleccionar la opción de construir la aplicacion (Build). Pero no olvidemos que una llamada directa al compilador puede hacer la misma labor.

Para llamar al compilador de C# desde la línea de comandos, teclearíamos:

csc Hola.cs /out:Hola.exe



donde csc es el nombre del compilador, "Hola.cs" nuestro fichero de código fuente en C#, y "Hola.exe" el nombre del fichero de salida. En caso de que se requiera la presencia de librerías adicionales (por defecto se incluye mscorelib.dll, que es la que contiene a System), haríamos las referencias añadiendo a la línea de comandos tantos modificadores /r: como librerías necesitásemos. Veremos ejemplos de la utilización del compilador en línea más adelante.

A lo largo de los siguientes artículos, iremos profundizando en las distintas características de uso del lenguaje a través de ejemplos. De esta forma, al analizar una simple aplicación vamos a ir revisando el lenguaje en sus características más importantes.

Anexo 1: Accesibilidad de los tipos y métodos de una clase

De cara al futuro, añadimos a continuación las tablas que definen, respectivamente, los calificadores de ámbito,la accesibilidad y los modificadores de acceso, a modo de referencia. Como el lector tendrá oportunidad de comprobar, las palabras reservadas que se indican en las tablas adjuntas, aparecen constantemente en los programas escritos en C#. Por tanto, y resumiendo la situación podemos desglosarla de la siguiente forma:

Calificadores de ámbito de accesibilidad para tipos y métodos de una clase

Declaracion Nivel de Accesibilidad Tipo de accesibilidad

Public Sin restricciones Declara miembros accesibles desde cualquier

otra clase Protected Limitado a la clase contenedora y

tipos derivados

Declara miembros accesibles únicamente desde dentro de la clase y sus clases derivadas Internal Limitado al proyecto actual Declara miembros accesibles únicamente desde

dentro de mismo módulo de ensamblaje (assembly) Protected Internal

Limitado al proyecto actual o tipos derivados Private Limitado al tipo contenedor. Declara miembros accesibles únicamente desde

la clase o estructura en la que son declarados.

Tabla 1. Calificadores de ámbito de accesibilidad

La accesibilidad de un tipo o método se especifica en C# mediante uno de los siguientes modificadores (modifiers) o declaraciones, tal y como aparecen en la tabla adjunta:

De las declaraciones indicadas en la tabla adjunta la única combinación permitida es protected internal. Por otro lado los Espacios Calificados no están sujetos a ningún tipo de restricción. Además, el contexto de la declaración, impone restricciones adicionales, y en caso de no utilizar ningún modificador, se asume la declaración por defecto para el contexto de uso que corresponda.



Y Los miembros de... Disponen de una accesibilidad

a sus miembros, se les por defecto del tipo:

permite un nivel de declaraciones de acceso: enum public Ninguno class private public

protected

internal

private

protected internal interface public Ninguno struct private public

internal

private

Tabla 2. Accesibilidad

Entendemos por dominio de accesibilidad, aquellas secciones del código de un programa en las cuales se puede hacer referencia a un miembro determinado. Según esto, la accesibilidad de un tipo anidado depende de su dominio de accesibilidad, que viene determinado por dos aspectos: el nivel de accesibilidad del miembro y el dominio de accesibilidad del tipo contenedor. Sin embargo, el dominio de accesibilidad de un tipo anidado

[1] no puede exceder el del tipo contenedor.

Otros Modificadores

La lista completa de modificadores en C# es la siguiente:

Modificador Propósito Modificadores de acceso (ya comentados): public, private, internal y protected

Especifican declaraciones de accesibilidad de tipos y tipos miembro.

abstract Indica que una clase sirve solamente de clase base o

clase abstracta para otras clases. No se pueden instanciar