|
Tutorial C#
Características de C#
Con la idea de que los
programadores más experimentados puedan obtener una visión general del
lenguaje, a continuación se recoge de manera resumida las principales
características de C# Alguna de las características aquí señaladas no son
exactamente propias del lenguaje sino de la plataforma .NET en general. Sin
embargo, también se comentan aquí también en tanto que tienen repercusión
directa en el lenguaje, aunque se indicará explícitamente cuáles son este tipo
de características cada vez que se toquen:
- Sencillez:
C# elimina muchos elementos que otros lenguajes incluyen y que son
innecesarios en .NET. Por ejemplo:
- El código escrito en C# es autocontenido, lo que significa que no necesita de ficheros
adicionales al propio fuente tales como ficheros de cabecera o ficheros
IDL
- El tamaño de los tipos de datos básicos es fijo e
independiente del compilador, sistema operativo o máquina para quienes se
compile (no como en C++), lo que
facilita la portabilidad del código.
- No se incluyen elementos poco útiles de lenguajes
como C++ tales como macros, herencia múltiple o la necesidad de un
operador diferente del punto (.) acceder a miembros
de espacios de nombres (::)
- Modernidad: C#
incorpora en el propio lenguaje elementos que a lo largo de los años ha
ido demostrándose son muy útiles para el desarrollo de aplicaciones y que
en otros lenguajes como Java o C++ hay que simular, como un tipo básico decimal
que permita realizar operaciones de alta precisión con reales de 128 bits
(muy útil en el mundo financiero), la inclusión de una instrucción foreach
que permita recorrer colecciones con facilidad y es ampliable a tipos
definidos por el usuario, la inclusión de un tipo básico string
para representar cadenas o la distinción de un tipo bool
específico para representar valores lógicos.
- Orientación
a objetos: Como todo lenguaje de programación de propósito general actual, C# es un lenguaje orientado a objetos, aunque eso es
más bien una característica del CTS que de C#. Una diferencia de este
enfoque orientado a objetos respecto al de otros lenguajes como C++ es que
el de C# es más puro en tanto que no admiten ni funciones ni variables
globales sino que todo el código y datos han de definirse dentro de
definiciones de tipos de datos, lo que reduce problemas por conflictos de
nombres y facilita la legibilidad del código.
C# soporta todas las características
propias del paradigma de programación orientada
a objetos: encapsulación, herencia y polimorfismo.
En lo referente a la encapsulación
es importante señalar que aparte de los típicos modificadores public, private y protected, C# añade un cuarto modificador
llamado internal,
que puede combinarse con protected e indica que al elemento a cuya
definición precede sólo puede accederse desde su mismo ensamblado.
Respecto
a la herencia -a diferencia de C++ y al igual que Java- C# sólo admite herencia
simple de clases ya que la múltiple provoca más quebraderos de cabeza que
facilidades y en la mayoría de los casos su utilidad puede ser simulada con
facilidad mediante herencia múltiple de interfaces. De todos modos, esto vuelve
a ser más bien una característica propia del CTS que de C#.
Por otro lado y a diferencia de Java, en C# se ha optado por hacer
que todos los métodos sean por defecto
sellados y que los redefinibles hayan de marcarse
con el modificador virtual (como en C++), lo que permite evitar
errores derivados de redefiniciones accidentales. Además, un efecto secundario de esto es que las llamadas a los
métodos serán más eficientes por defecto al no tenerse que buscar en la tabla
de funciones virtuales la implementación de los mismos a la que se ha de
llamar. Otro efecto secundario es que permite que las llamadas a los métodos
virtuales se puedan hacer más eficientemente al contribuir a que el tamaño de
dicha tabla se reduzca.
- Orientación
a componentes: La propia sintaxis de C# incluye elementos propios del
diseño de componentes que otros lenguajes tienen que simular mediante
construcciones más o menos complejas. Es decir, la sintaxis de C# permite
definir cómodamente propiedades (similares
a campos de acceso controlado), eventos
(asociación controlada de funciones de respuesta a notificaciones) o atributos (información sobre un
tipo o sus miembros)
- Gestión
automática de memoria: Como ya se comentó, todo lenguaje de .NET tiene
a su disposición el recolector de basura del CLR. Esto tiene el efecto en
el lenguaje de que no es necesario incluir instrucciones de destrucción de
objetos. Sin embargo, dado que la destrucción de los objetos a través del
recolector de basura es indeterminista y sólo se realiza cuando éste se
active –ya sea por falta de memoria, finalización de la aplicación o
solicitud explícita en el fuente-, C# también proporciona un mecanismo de
liberación de recursos determinista a través de la instrucción using.
- Seguridad de
tipos: C# incluye mecanismos que permiten asegurar que los accesos a
tipos de datos siempre se realicen correctamente, lo que permite evita que
se produzcan errores difíciles de detectar por acceso a memoria no
perteneciente a ningún objeto y es especialmente necesario en un entorno
gestionado por un recolector de basura. Para ello se toman medidas del
tipo:
- Sólo se admiten conversiones entre tipos compatibles. Esto es, entre un tipo
y antecesores suyos, entre tipos para los que explícitamente se haya
definido un operador de conversión, y entre un tipo y un tipo hijo suyo
del que un objeto del primero almacenase una referencia del segundo (downcasting) Obviamente, lo
último sólo puede comprobarlo en tiempo de ejecución el CLR y no el
compilador, por lo que en realidad el CLR y el compilador colaboran para
asegurar la corrección de las conversiones.
- No se pueden usar variables no inicializadas. El compilador da a los campos un
valor por defecto consistente en ponerlos a cero y controla mediante
análisis del flujo de control del fuente que no se lea ninguna variable
local sin que se le haya asignado previamente algún valor.
- Se comprueba que todo acceso a los elementos de una tabla se realice con índices
que se encuentren dentro del rango de la misma.
- Se puede controlar la producción de desbordamientos en operaciones aritméticas,
informándose de ello con una excepción cuando ocurra. Sin embargo, para
conseguirse un mayor rendimiento en la aritmética estas comprobaciones no
se hacen por defecto al operar con variables sino sólo con constantes (se
pueden detectar en tiempo de compilación)
- A diferencia de Java, C# incluye delegados, que son similares a
los punteros a funciones de C++ pero siguen un enfoque orientado a
objetos, pueden almacenar
referencias a varios métodos simultáneamente, y se comprueba que los
métodos a los que apunten tengan parámetros y valor de retorno del tipo
indicado al definirlos.
- Pueden definirse métodos que admitan un número
indefinido de parámetros de un cierto tipo, y a diferencia lenguajes como
C/C++, en C# siempre se comprueba que los valores que se les pasen en
cada llamada sean de los tipos apropiados.
- Instrucciones
seguras: Para evitar errores muy comunes, en C# se han impuesto una
serie de restricciones en el uso de las instrucciones de control más
comunes. Por ejemplo, la guarda de toda condición ha de ser una expresión
condicional y no aritmética, con lo que se evitan errores por confusión
del operador de igualdad (==) con el de
asignación (=); y todo caso de un switch ha de terminar en un break
o goto
que indique cuál es la siguiente acción a realizar, lo que evita la
ejecución accidental de casos y facilita su reordenación.
- Sistema de
tipos unificado: A diferencia de C++, en C# todos los tipos de datos
que se definan siempre derivarán, aunque sea de manera implícita, de una
clase base común llamada System.Object, por lo
que dispondrán de todos los miembros definidos en ésta clase (es decir,
serán “objetos”)
A diferencia de Java, en C# esto
también es aplicable a los tipos de datos básicos Además, para conseguir que ello no tenga una repercusión negativa
en su nivel de rendimiento, se ha incluido un mecanismo transparente de boxing
y unboxing
con el que se consigue que sólo sean tratados como objetos cuando la
situación lo requiera, y mientras tanto
puede aplicárseles optimizaciones específicas.
El hecho de que todos los tipos del
lenguaje deriven de una clase común facilita enormemente el diseño de
colecciones genéricas que puedan almacenar objetos de cualquier tipo.
- Extensibilidad
de tipos básicos: C# permite definir, a través de estructuras, tipos de datos para los que se apliquen las
mismas optimizaciones que para los tipos de datos básicos. Es decir, que
se puedan almacenar directamente en pila (luego su creación, destrucción y
acceso serán más rápidos) y se asignen por valor y no por referencia. Para
conseguir que lo último no tenga efectos negativos al pasar estructuras
como parámetros de métodos, se da la posibilidad de pasar referencias a
pila a través del modificador de parámetro ref.
- Extensibilidad
de operadores: Para facilitar la legibilidad del código y conseguir
que los nuevos tipos de datos básicos que se definan a través de las
estructuras estén al mismo nivel que los básicos predefinidos en el
lenguaje, al igual que C++ y a diferencia de Java, C# permite redefinir el
significado de la mayoría de los operadores -incluidos los de conversión,
tanto para conversiones implícitas como explícitas- cuando se apliquen a
diferentes tipos de objetos.
Las redefiniciones de operadores se
hacen de manera inteligente, de modo que a partir de una única definición de
los operadores ++
y --
el compilador puede deducir automáticamente como ejecutarlos de
manera prefijas y postifja; y definiendo operadores simples (como +),
el compilador deduce cómo aplicar su
versión de asignación compuesta (+=) Además, para asegurar la
consistencia, el compilador vigila que los operadores con opuesto siempre se
redefinan por parejas (por ejemplo, si se redefine ==, también hay que
redefinir !=)
También se da la posibilidad, a
través del concepto de indizador, de
redefinir el significado del operador [] para los tipos de dato definidos por el
usuario, con lo que se consigue que se pueda acceder al mismo como si fuese una
tabla. Esto es muy útil para trabajar con tipos que actúen como colecciones de
objetos.
·
Extensibilidad
de modificadores: C# ofrece, a través del concepto de atributos, la posibilidad de añadir a los metadatos del módulo
resultante de la compilación de cualquier fuente información adicional a la
generada por el compilador que luego podrá ser consultada en tiempo
ejecución a través de la librería de reflexión de .NET . Esto, que más bien es
una característica propia de la plataforma .NET y no de C#, puede usarse como
un mecanismo para definir nuevos modificadores.
- Versionable:
C# incluye una política de
versionado que permite crear nuevas versiones de tipos sin temor a que
la introducción de nuevos miembros provoquen errores difíciles de detectar
en tipos hijos previamente desarrollados y ya extendidos con miembros de
igual nombre a los recién introducidos.
Si una clase introduce un nuevo
método cuyas redefiniciones deban seguir la regla de llamar a la versión de su
padre en algún punto de su código, difícilmente seguirían esta regla miembros
de su misma signatura definidos en clases hijas previamente a la definición del
mismo en la clase padre; o si introduce
un nuevo campo con el mismo nombre que algún método de una clase hija, la clase hija dejará de
funcionar. Para evitar que esto ocurra, en C# se toman dos medidas:
- Se obliga a que toda redefinición deba incluir el modificador override,
con lo que la versión de la clase hija nunca sería considerada como una
redefinición de la versión de miembro en la clase padre ya que no
incluiría override. Para evitar que por accidente un
programador incluya este modificador, sólo se permite incluirlo en
miembros que tengan la misma
signatura que miembros marcados como redefinibles mediante el
modificador virtual. Así además se
evita el error tan frecuente en Java de creerse haber redefinido un
miembro, pues si el miembro con override no existe en
la clase padre se producirá un error de compilación.
- Si no se considera redefinición, entonces se
considera que lo que se desea es ocultar el método de la clase padre, de
modo que para la clase hija sea como si nunca hubiese existido. El
compilador avisará de esta decisión a través de un mensaje de aviso que
puede suprimirse incluyendo el
modificador new en la definición
del miembro en la clase hija para así indicarle explícitamente la
intención de ocultación.
- Eficiente: En
principio, en C# todo el código incluye numerosas restricciones para
asegurar su seguridad y no permite el uso de punteros. Sin embargo, y a
diferencia de Java, en C# es posible saltarse dichas restricciones
manipulando objetos a través de
punteros. Para ello basta marcar regiones de código como inseguras
(modificador unsafe) y podrán usarse
en ellas punteros de forma similar a cómo se hace en C++, lo que puede
resultar vital para situaciones donde se necesite una eficiencia y
velocidad procesamiento muy grandes.
- Compatible: Para
facilitar la migración de programadores, C# no sólo mantiene una sintaxis
muy similar a C, C++ o Java que
permite incluir directamente en código escrito en C# fragmentos de código
escrito en estos lenguajes, sino que el CLR también ofrece, a través de
los llamados Platform Invocation
Services (PInvoke), la
posibilidad de acceder a código nativo escrito como funciones sueltas no
orientadas a objetos tales como
las DLLs de la API Win32. Nótese que la capacidad de usar punteros en
código inseguro permite que se
pueda acceder con facilidad a este tipo de funciones, ya que éstas
muchas veces esperan recibir o devuelven punteros.
También es
posible acceder desde código escrito en C# a objetos COM. Para facilitar esto,
el .NET Framework SDK incluye una
herramientas llamadas tlbimp y regasm mediante
las que es posible generar automáticamente clases proxy que permitan,
respectivamente, usar objetos COM desde .NET como si de objetos .NET se tratase
y registrar objetos .NET para su uso desde COM.
Finalmente, también se da la posibilidad de usar controles ActiveX
desde código .NET y viceversa. Para lo primero se utiliza la utilidad aximp,
mientras que para lo segundo se usa la ya mencionada regasm.
Principal
|
|