Upload
mirtagf
View
190
Download
0
Embed Size (px)
Citation preview
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 1/80
C#
David Gañán Jiménez
P08/B0036/01627
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 2/80
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 3/80
© FUOC • P08/B0036/01627 C#
Índice
Introducción.......................................................................................... 5
Objetivos................................................................................................. 6
1. Conceptos básicos de C#............................................................... 7
1.1. Estructura de un programa en C# ............................................ 7
1.2. Compilación y ejecución .......................................................... 9
1.3. Elementos básicos de sintaxis ................................................... 10
1.3.1. Fin de instrucción .......................................................... 10
1.3.2. Case Sensitive ................................................................ 10
1.3.3. Comentarios ................................................................... 10
1.3.4. Bloques de código .......................................................... 11
1.3.5. Variables ......................................................................... 12
1.3.6. Constantes ..................................................................... 14
1.3.7. Operadores ..................................................................... 15
1.3.8. Expresiones .................................................................... 16
1.3.9. Tipos de datos ................................................................ 16
1.3.10.Tipos parciales ................................................................ 22
1.3.11.Tipos anulables (nullable types) .................................... 231.3.12.Arrays ............................................................................. 24
1.3.13.Conversiones entre tipos de datos ................................ 27
1.3.14.Tipos y métodos genéricos ............................................ 28
1.4. Instrucciones del lenguaje ........................................................ 31
1.4.1. Sentencias de decisión ................................................... 31
1.4.2. Sentencias de iteración .................................................. 35
1.4.3. Sentencias de salto ........................................................ 39
2. Orientación a objetos en C#........................................................ 41
2.1. Definición de clases .................................................................. 41
2.2. Instanción de clases .................................................................. 42
2.3. Herencia ..................................................................................... 44
2.3.1. Interfaces ........................................................................ 45
2.4. Miembros de datos .................................................................... 46
2.5. Miembros de función ............................................................... 47
2.5.1. Métodos ......................................................................... 48
2.5.2. Propiedades .................................................................... 53
2.5.3. Constructores ................................................................. 54
2.5.4. Destructores ................................................................... 56
2.5.5. Operadores ..................................................................... 57
2.5.6. Indexadores .................................................................... 57
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 4/80
© FUOC • P08/B0036/01627 C#
3. Conceptos avanzados.................................................................... 59
3.1. Tratamiento de excepciones ..................................................... 59
3.1.1. Excepciones definidas por el usuario ............................ 62
3.2. Delegate y eventos .................................................................... 64
3.2.1. Eventos ........................................................................... 66
3.2.2. Métodos anónimos ........................................................ 68
3.3. Atributos .................................................................................... 69
3.3.1. Atributos personalizados ............................................... 70
4. Novedades de C# 3.0...................................................................... 73
4.1. Variables locales de tipo implícito ............................................ 73
4.2. Tipos anónimos ......................................................................... 73
4.3. Métodos de extensión ............................................................... 74
4.4. Inicializadores de objetos .......................................................... 74
4.5. Expresiones lambda .................................................................. 75
4.6. Expresiones de consulta ............................................................ 76
Actividades............................................................................................. 77
Bibliografía............................................................................................ 79
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 5/80
© FUOC • P08/B0036/01627 5 C#
Introducción
Microsoft desarrolló C#, junto con la plataforma .NET, como un lenguaje mo-
derno, fácil de utilizar y totalmente orientado a objetos. El lenguaje C# es ade-
más un Standard ECMA, igual que el motor de ejecución de .NET.
C# consolida la experiencia de programación en otros lenguajes como C++ o
java, adaptando las bondades de estos lenguajes y mejorando sus limitaciones.
Es por eso que los programadores de C, C++ o java encontrarán la sintaxis del
lenguaje muy familiar.
En este tema, se pretende presentar el lenguaje C#, estudiando la sintaxis con-
creta de cada una de las estructuras de programación, así como los elementos
nuevos y diferenciadores de este lenguaje.
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 6/80
© FUOC • P08/B0036/01627 6 C#
Objetivos
El material que se os presenta a continuación ha sido elaborado teniendo en
cuenta los siguientes objetivos específicos:
1. Aprender los conceptos y sintaxis básica de C#.
2. Conocer las características de programación orientada a objetos de C#.
3. Profundizar en otros conceptos más avanzados de programación en C#.
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 7/80
© FUOC • P08/B0036/01627 7 C#
1. Conceptos básicos de C#
1.1. Estructura de un programa en C#
La mejor forma de empezar a aprender un lenguaje de programación nuevo
suele ser analizando algún ejemplo de código sencillo. Y para no romper la
tradición, empezaremos con el típico HolaMundo:.
using System;
namespace HolaMundo
{
class HolaMundo
{
[STAThread]
static void Main(string[] args)
{
Console.WriteLine ("Hola Mundo");
}
}
}
A continuación, analizaremos los diferentes elementos del anterior programa
uno por uno:
• Importación de librerías
using System ;
Al inicio de cada fichero de código fuente C# podemos incluir varias ins-
trucciones using. La utilidad de esta instrucción es la de importar libre-
rías de clases ya existentes, ya sea de la FCL (framework class library del .NET
Framework), desarrolladas por otras empresas, o por nosotros mismos. Al
importar una librería, podremos utilizar desde el código todas las funcio-
nalidades que ésta proporciona.
Como ya hemos visto anteriormente, las librerías de clases se organizan
en carpetas lógicas, llamadas namespaces. Cada instrucción using debe
ir acompañada del nombre del namespace que se quiera importar; en el
ejemplo anterior se está importando el namespace System de la FCL, que
contiene la clase Console que se va a utilizar en el código.
• Definición del namespace
namespace HolaMundo
{ ... }
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 8/80
© FUOC • P08/B0036/01627 8 C#
Todas las clases están incluidas dentro de un namespace concreto, que se
indica dentro del código fuente mediante la instrucción namespace, se-
guida del nombre del namespace.
Pueden existir dos namespaces con el mismo nombre, pero no si están
dentro del mismo namespace. El nombre completo de un namespace es
el resultado de concatenar los nombres de todos los namespaces que hay
que atravesar hasta llegar hasta él, separados por puntos (.), por ejemplo:
System.Drawing
Todos los elementos e instrucciones incluidos dentro de las llaves perte-
necen al namespace HolaMundo. Si se omite la definición del namespace,
los elementos se incluirán en el namespace por defecto de la aplicación.
• Definición de la clase
class HolaMundo
{ ... }
Aprenderemos a definir clases más adelante. La forma general de definir
una clase es mediante la palabra clave class, seguida del nombre que
queremos dar a la clase, y entre las llaves todos los elementos e instruccio-
nes que pertenecen a la definición de la clase.
Pueden existir dos clases con el mismo nombre, pero no si están dentro
del mismo namespace. El nombre completo de una clase es el resultado
de concatenar los nombres de todos los namespaces que hay que atravesar
hasta llegar hasta la clase, separados por puntos (.), seguida del nombre
corto de la clase, por ejemplo: System.Drawing.Rectangle
• El método main
static void Main(string[] args)
{ ... }
El método Main es el método principal de una aplicación, es el punto de
entrada. Cuando se ejecuta una aplicación, se ejecutan única y exclusiva-
mente las instrucciones que se encuentran dentro de este método. Por eso,
es en este método donde hay que escribir las instrucciones y llamadas a
otros métodos, necesarias para llevar a cabo los objetivos de la aplicación.
El tipo de retorno del método Main es por defecto void (no devuelve nin-
gún valor), pero también se puede definir como int (un entero que indi-
ca el estado en que finaliza el programa). En cuanto a los parámetros, el
método Main acepta un único parámetro opcional, que es un vector de
cadenas de caracteres, correspondiente a los argumentos del programa en
la línea de comandos. Estos argumentos permiten añadir opciones de con-
figuración detrás del nombre de la aplicación al ejecutarla desde la consola
de comandos del sistema, como las que proporciona el compilador de C#
• La clase Console
Console.WriteLine ("Hola Mundo");
La funcionalidad de la instrucción anterior es la de mostrar por la consola
de comandos el mensaje 'Hola Mundo'. La clase Console incluye diversos
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 9/80
© FUOC • P08/B0036/01627 9 C#
métodos de lectura y escritura para trabajar con la consola de comandos
del sistema.
Los métodos Write y WriteLine permiten escribir información en la con-
sola (WriteLine añade un salto de línea al final), mientras que los méto-
dos Read y ReadLine capturan los datos que se introducen por el teclado
(ReadLine lee todo lo que se introduce hasta que se introduce un salto de
línea, mientras que Read sólo lee un carácter del teclado).
1.2. Compilación y ejecución
Una vez escrito nuestro programa en C# debemos compilarlo. Para hacerlo,
podemos utilizar el compilador del .NET Framework1, o una herramienta más
avanzada como Visual Studio.
El compilador de C# es una utilidad de consola de comandos. Para utilizarlo
debemos abrir una ventana de la consola y escribir:
csc
Esto producirá un error, ya que no hemos especificado ninguna opción, ni
hemos seleccionado los archivos que queremos compilar. Si escribimos:
csc /?
aparecerá la sintaxis del comando, y una lista de las opciones disponibles.
Por ejemplo, para compilar nuestro programa HolaMundo introduciremos:
csc HolaMundo.cs
La instrucción anterior compilará nuestro programa, y creará un fichero
HolaMundo.exe ejecutable. Para ejecutar este archivo en un ordenador, será
necesario que esté instalado el .NET Framework Redistributable o el SDK. Si
está instalado, podremos ejecutar la aplicación escribiendo en la consola de
comandos:
HolaMundo
o bien haciendo doble clic encima del archivo en el explorador de Windows.
El programa de ejecuta y muestra el mensaje 'Hola Mundo' por pantalla.
(1)El .NET Framework Redistributa-
ble y el SDK se pueden descargar desde el sitio de descargas de Mi-crosoft.El .NET Redistributable además, se
puede instalar automáticamentedesde el sitio de actualizaciones de Windows.Con Visual Studio instalado no esnecesario instalar el .NET frame-work por separado (se instala por defecto).
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 10/80
© FUOC • P08/B0036/01627 10 C#
1.3. Elementos básicos de sintaxis
La sintaxis de un lenguaje es la definición de las palabras clave, los elementos
y las combinaciones válidas entre ellos en ese lenguaje. A continuación, ana-
lizaremos los diferentes elementos e instrucciones que permite el lenguaje C#,
y cuál es su sintaxis.
1.3.1. Fin de instrucción
Todas las instrucciones de C# finalizan con un punto y coma (;) al final de la
línea, salvo en algunas excepciones, como en algunas partes determinadas de
una sentencia, o al final de un bloque de código.
1.3.2. Case Sensitive
Las palabras clave y los identificadores en C# son case sensitive, es decir, que
diferencia ente minúsculas y mayúsculas, por ejemplo holamundo y Hola-
Mundo son dos identificadores distintos, y si escribimos Namespace en vez de
namespace, el compilador dará un error de sintaxis.
1.3.3. Comentarios
Los comentarios son anotaciones del programador dentro del propio código
fuente, útiles para documentar qué hace cada parte del código, y que sea más
fácil realizar modificaciones en el futuro. Estos comentarios son ignorados porel compilador durante el proceso de compilación, y no se incluyen en el ar-
chivo ejecutable. Existen tres tipos de comentarios en C#:
• Comentarios de una sola línea // ...
Cualquier carácter desde las dos barras invertidas hasta el final de la línea
se considera como comentario.
• Comentarios de múltiples líneas /* ... */
Cualquier carácter entre el símbolo /* y el símbolo */, se considera como
parte del comentario, aunque éste abarque varias líneas.
• Comentarios XML ///
Este tipo de comentario permite, posteriormente, generar de modo auto-
mático un archivo XML con la documentación de nuestro código fuente.
Para ello se utilizan una serie de tags XML que permiten definir los dife-
rentes componentes del código fuente, como por ejemplo:
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 11/80
© FUOC • P08/B0036/01627 11 C#
Ejemplo
<summary> ... </summary>. Permite realizar una descripción breve
<remarks> ... </remarks>. Descripción detallada
<example> ... </example>. Permite incluir un ejemplo de utilización
Además, se pueden incluir elementos HTML para formatear la informa-
ción en tablas o listas. Se pueden consultar todos los tags XML que se pue-
den utilizar en la ayuda de Visual Studio, buscando 'XML Documentation
comments'. Para generar la documentación, podemos utilizar la opción
/doc del compilador de consola de comandos de C#.
1.3.4. Bloques de código
Todos los programas están compuestos por líneas de código, pero a veces es
necesario agruparlas en bloques para organizar el código fuente. Estos bloques
se definen en C# mediante los caracteres { y } (llaves). Todas las instrucciones
que se encuentran en el interior de las llaves se consideran un bloque.
{
// instrucciones del bloque
}
Los bloques de código pueden anidarse entre sí, es decir, puede haber un blo-
que de código dentro de otro bloque de código más grande:
{
// instrucciones del bloque principal
{
// instrucciones del bloque anidado
}
// más instrucciones del bloque principal
}
Como veremos más adelante, las diferentes instrucciones del lenguaje utilizan
bloques para separar las diferentes partes de la instrucción unas de otras.
Además de los bloques definidos por llaves, en Visual Studio se pueden definir
regiones que, posteriormente, se pueden ocultar o mostrar mediante el plega-
do de código. Estas regiones son muy útiles a la hora de organizar los elemen-
tos de código según su funcionalidad. Además, se les puede asignar un nombre
que describa la funcionalidad de las instrucciones que contiene. Para definir
un bloque, utilizamos las palabras clave region y endregion:
#region <nombre>
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 12/80
© FUOC • P08/B0036/01627 12 C#
// instrucciones dentro de la región
#endregion
1.3.5. Variables
Para la realización de sus funciones, los programas necesitan manejar cierta
información. Estos datos se almacenan en memoria, y son accesibles desde el
programa mediante el uso de variables.
Una variable es un nombre que se le asigna a un dato concreto, e indica en qué
posición de memoria se encuentra ese dato, por lo que nos permite acceder a
él para consulta o modificación. Normalmente se suelen asignar nombres de
variables que recuerden el objetivo del dato que almacenan.
Para utilizar una variable en un programa es necesario declararla, es decir, re-
servar el espacio necesario en memoria para almacenar el valor de la variable.
Para declarar una variable es necesario especificar su nombre y su tipo de da-
to. Por ejemplo, para declarar una variable entera de 32 bits, con nombre i,
deberíamos escribir:
int i;
Esta instrucción reserva 32 bits de memoria para almacenar el valor entero re-
presentado por i. Una vez declarada una variable, podemos asignarle un valor,mediante la operación de asignación (=):
i = 4;
La instrucción anterior modifica el valor de la variable i a 4. También se puede
asignar a una variable el valor de otras variables, escribiendo expresiones más
complejas.
Ejemplo
Por ejemplo, la siguiente instrucción asigna a i el valor de la variable j más el valor dela variable k, todo dividido entre 2:
i = (j + k) / 2;
La instrucción de asignación permite también encadenar varias variables a las
cuales se quiere asignar el mismo valor.
Ejemplo
Por ejemplo, la siguiente instrucción asigna el valor 10 a las variables i y j:
i = j = 10;
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 13/80
© FUOC • P08/B0036/01627 13 C#
La declaración y la asignación de variables se pueden unificar en un único
paso, para dar un valor inicial a las variables recién creadas. La siguiente ins-
trucción declara e inicializa la variable i con el valor 4:
int i = 4;
Además, se pueden inicializar varias variables de un mismo tipo en una sola
instrucción, e incluso asignarles el mismo valor inicial, como en las siguientes
instrucciones:
int i, j; // declaración de varias variables del mismo tipo
int i = 4, j = 5; // declaración e inicialización de varias variables del mismo tipo
int i = j = 4; // declaración e inicialización de varias variables del mismo tipo
// con el mismo valor inicial
Visibilidad
La visibilidad de una variable es un parámetro adicional que se puede añadir
a la declaración de una variable, para indicar desde qué puntos del programa
se tendrá acceso a ella:
visibilidad tipo_dato nombre_var;
La visibilidad puede ser una de las siguientes:
• public La variable es accesible desde cualquier punto del programa y des-
de otros programas.
• private La variable sólo es accesible internamente al tipo de dato al que
está asociada.
• protected La variable sólo es accesible internamente al tipo de dato al
que está asociada o a alguna de sus subclases (veremos el concepto de he-
rencia en el apartado de orientación a objetos).
• internal La variable es accesible solamente desde puntos del programa
situados dentro del propio ensamblado (no puede ser utilizado por otros
programas).
• protected internal La variable es accesible desde puntos del programa
situados dentro del propio ensamblado, o desde alguna de las subclases
del tipo al que está asociado.
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 14/80
© FUOC • P08/B0036/01627 14 C#
Ámbito de las variables
Dependiendo del punto de un programa en el que se declare una variable, ésta
puede tener los dos siguientes tipos de alcance:
• Global. La variable está disponible desde cualquier punto del programa,siempre y cuando la visibilidad lo permita.
• Local. La variable sólo está disponible dentro del bloque de código en el
que se declara, por ejemplo, dentro de un método o dentro de un bucle
(veremos estas estructuras más adelante).
Las variables definidas dentro de un bloque son locales a ese bloque, es decir,
no se puede acceder a ellas desde fuera de ese bloque, aunque sí que son acce-
sibles desde bloques definidos dentro de él.
Por ejemplo:
{
// bloque A
{
// bloque B
int i;
{
// bloque C
}
}
}
La variable i definida en el bloque B, no es accesible desde el bloque A, pero
sí desde el bloque C.
1.3.6. Constantes
Una constante es una variable cuyo valor no puede ser modificado, es decir, es
sólo de lectura. Para declarar una constante, escribimos la palabra clave const
delante de la declaración de la variable. Es necesario (dado que su valor no
se puede modificar a posteriori) especificar el valor inicial de la variable en la
misma instrucción de declaración.
const int i = 4
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 15/80
© FUOC • P08/B0036/01627 15 C#
1.3.7. Operadores
Los operadores son símbolos especiales que permiten realizar cálculos sobre
las variables o valores que tienen como parámetro. El tipo de datos sobre el
que actúa cada operador y el tipo de resultado dependen del tipo de operador:
• Aritméticos. +, -, *, /, % (módulo)
• Lógicos. & (and bit a bit), | (or bit a bit), ~ (not bit a bit)
&& (and lógico), || (or lógico), ! (not lógico)
• Concatenación de cadenas de caracteres. +
• Incremento / decremento. ++, ––
• Comparación. ==, != (desigual), <, >, <=, >=
• Asignación. =, +=, -=, *=, /=, %=, &=, |=, <<=, >>=
Las combinaciones +=, -=, *=, etc., permiten asignar a una variable el re-
sultado de realizar la operación indicada delante del símbolo =, entre la
variable y el valor indicado a continuación.
Por ejemplo x += 3 es equivalente a x = x + 3
• Condicional. (condición)?(expr_cierto):(expr_falso)
El operador condicional es un operador ternario que recibe tres paráme-tros: una condición, la expresión que se devuelve como resultado si la con-
dición es cierta, y la que se devuelve si es falsa.
Por ejemplo:
string x = (y==5)?("y es igual a 5"):("y es diferente de
5");
Si y equivale a 5, x contendrá la cadena "y es igual a 5" y si no, la cadena
"y es deferente de 5".
• Acceso a miembros. El punto permite acceder a los miembros o elementos
internos de un tipo de dato, es decir a sus atributos, propiedades, métodos,
etc., que ese tipo de dato pueda contener. Para acceder a un miembro, se
escribe el nombre de la variable seguida del punto y seguida del nombre
del miembro, sin espacios:
string s = "Hola";
s.Length // s es una cadena de caracteres (<i>string</i>).
// Length devuelve el número de caracteres de la cadena
Además, se pueden encadenar varios operadores '.' si el miembro contiene
a su vez otros miembros:
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 16/80
© FUOC • P08/B0036/01627 16 C#
s.Trim().LastIndexOf ("H");
El método Trim devuelve el mismo string contenido en s, eliminando
espacios al principio y al final de la cadena2. A continuación, se ejecuta
el método LastIndexOf, que devuelve el ultimo índice de la subcadena
indicada, en el caso anterior la posición 0.
1.3.8. Expresiones
Una expresión es una secuencia de variables, valores y operaciones que se pue-
de evaluar. El tipo de valor de la expresión depende de los tipos de las variables
y/o valores que la forman, así como de los operadores utilizados.
Ejemplo
A continuación, se muestran algunos ejemplos de expresiones:
3 + 4: // Expresión entera
i - 4: // Expresión numérica
true && b: // Expresión booleana
'a' + 'b': // Expresión alfanumérica
1.3.9. Tipos de datos
Existen diferentes tipos de datos. Un tipo de dato es una estructura que define
cómo se almacena una determinada información en la memoria, así como las
operaciones que se pueden realizar sobre ese tipo. Por ejemplo, el tipo de dato
entero de 32 bits se almacena en memoria como un número binario de 32 bits
y permite realizar operaciones aritméticas, de comparación e igualdad, etc.
(2)Las posiciones de una cadena de
caracteres empiezan en 0, es decir,van de 0 a N-1, donde N es la lon-gitud de la cadena.
Dependiendo de la zona de memoria donde se almacenan los datos de un tipo
de datos, éstos pueden ser tipos valor o tipos referencia. Los tipos valor son
aquellos que se almacenan directamente en la pila de ejecución del programa
(stack). Los tipos referencia son aquellos que se almacenan en una zona de
memoria auxiliar llamada heap o montículo. Para poder acceder a estos datos,
se coloca un puntero (dirección de memoria) en la pila de ejecución3, que
indica la posición de memoria del heap donde se encuentran los datos.
Los tipos, además, pueden ser predefinidos o definidos por el usuario. Los ti-
pos predefinidos representan valores simples como enteros, reales, caracteres
o valores booleanos. Los tipos definidos por el usuario o estructuras de datos
son tipos complejos definidos como composición de tipos predefinidos.
Tipos valor por defecto
Los tipos de valor por defecto son los siguientes:
(3)La pila de ejecución de un pro-
grama es la zona de memoria don-de se guardan los datos necesariospara poder ejecutar las instruccio-nes del programa: variables, valo-res parciales, instrucciones de lla-madas a métodos, etc.
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 17/80
© FUOC • P08/B0036/01627 17 C#
• Enteros. Son valores que representan valores numéricos enteros. En la si-
guiente tabla, se muestran los diferentes tipos de datos enteros, así como
su tamaño en memoria, y el rango de números que permiten representar.
Tamaño�en�memoria Rango
sbyte 8 bits con signo -27
: 27
- 1
short 16 bits con signo -215
: 215
- 1
int 32 bits con signo -231
: 231
- 1
long 64 bits con signo 263
: 263
- 1
byte 8 bits sin signo 0 : 28
- 1
ushort 16 bits sin signo 0 : 216
- 1
uint 32 bits sin signo 0 : 232
- 1
ulong 64 bits sin signo 0 : 264
- 1
A veces, es necesario en una expresión desambiguar el tipo de entero al
que pertenece un determinado valor. Para ello, se añaden letras al final del
valor que identifican su tipo de dato, en concreto para números enteros,
la letra U identifica un valor sin signo, y la letra L un valor de 64 bits:
uint ui = 1234U;
long l = 1234L;
ulong ul = 1234UL;
• Reales. Son valores que representan valores numéricos reales. Existen dos
tipos de reales de coma flotante, uno de precisión simple (float) y otro
de precisión doble (double ). Por último, existe un tipo especial (deci-
mal ) para operaciones que requieren una gran precisión (más posiciones
decimales).
Tamaño�en�memoria Número�máxi-mo�de�dígitos
Rango
float 32 bits real de coma flotante deprecisión simple
7 ±1.5 x 10-43
: ±3.4
x 1038
double 64 bits real de coma flotante deprecisión doble
15 / 16 ±5.0 x 10-324
: ±1.7
x 10308
decimal 128 bits real de notación decimalde alta precisión
28 ±1.0 x 10-28
: ±7.9
x 1028
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 18/80
© FUOC • P08/B0036/01627 18 C#
De la misma forma que con los valores enteros, se pueden desambiguar los
valores reales mediante las letras F, para valores float, y M para valores
decimal:
float f = 12.3F;
decimal d = 12.3M;
• Booleanos. El tipo de dato bool representa un dato booleano, cuyo valor
puede ser cierto (true) o falso (false).
• Caracteres. El tipochar representa caracteres unicode de 16 bits. Los valo-
res de tipo carácter se expresan entre comillas simples ('), por ejemplo: 'a',
'b', '0', etc. Existen, además, una serie de caracteres especiales llamados ca-
racteres de escape, que realizan alguna función especial. A continuación,
se muestra una tabla con los caracteres de escape y su funcionalidad:
Carácter�de�escape Funcionalidad
\' Permite representar el carácter '. Este carácter no se puede represen-tar de otro modo ya que la expresión:char c = ''' ;
No es sintácticamente correcta.
\" Permite representar el carácter " dentro de una cadena de caracteresencerrada entre comillas dobles:"Hola \" esto es una cadena"
equivale a la cadena:Hola " esto es una cadena
\\ Permite representar el carácter \, ya que por si solo se interpretaríacomo el inicio de un carácter de escape."Hola \\ esto es una cadena"
equivale a la cadena:Hola \ esto es una cadena
\0 Carácter nulo
\b Backspace . Introduce un espacio.
\f Provoca que se termine de escribir en la página actual y se continúeen la siguiente.
\n Traslada el cursor a la línea siguiente
\r Retorno de carro. Devuelve el cursor al inicio de la línea
\t Tabulador. Introduce una tabulación horizontal
\v Tabulador vertical. Introduce una fabulación vertical
Tipos valor definidos por el usuario
• Enumeraciones. La enumeración es una estructura de datos que permite
definir una lista de constantes y asignarles un nombre. Este tipo de dato
se utiliza para definir secuencias de constantes relacionadas.
Por ejemplo, la siguiente enumeración define los días de la semana:
enum DiasSemana
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 19/80
© FUOC • P08/B0036/01627 19 C#
{
Lunes, Martes, Miercoles,
Jueves, Viernes, Sabado, Domingo
}
Para acceder a los elementos de la enumeración utilizamos su nombre se-
guido del nombre del elemento, separados por un punto (acceso a miem-
bros):
DiasSemana ds = DiasSemana.Lunes;
De ese modo, el código es mucho más legible, ya que en vez de utilizar
un entero del 1 al 7 para representar el día, se utiliza una constante y un
tipo específico de datos para el día de la semana, de modo que se evitan
problemas adicionales como, por ejemplo, controlar que el valor de una
variable que represente el día esté entre 1 y 7.
Actividad 1
Ejecutad las siguientes acciones:
• Cread la enumeración Meses que represente los meses del año.
• Declarad una variable de tipo Meses y asignadle como valor el mes
Agosto.
• Estructuras (structs). Un struct permite definir una estructura compuesta
de uno o más elementos o atributos, cada uno de ellos con su correspon-
diente tipo de dato. Los struct también pueden tener otros elementos
como métodos, constructores o propiedades, que como veremos más ade-
lante también forman parte de la definición de las clases.
La diferencia fundamental entre un struct y una clase, es que el primero
es un tipo de dato por valor, mientras que el segundo es un tipo por refe-
rencia. Por otro lado, existen algunas restricciones de los struct respecto
a las clases, por ejemplo los struct no soportan herencia.
En C# los structs se declaran mediante la palabra clave struct , seguida del
nombre que queremos asignar a la estructura, y de un bloque de instruc-
ciones con los elementos que lo definen:
struct Punto
{
int x;
int Y;
// otros elementos
}
Las estructuras se declaran igual que cualquier tipo por valor, y para acce-
der a sus elementos utilizamos el operador de acceso a miembros:
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 20/80
© FUOC • P08/B0036/01627 20 C#
Punto p;
p.x = 0;
p.y = 0;
Actividad 2
Ejecutad las siguientes acciones:
• Escribid la estructura Producto con los siguientes atributos: nom-
bre, precio, categoría.
• Cread un Producto con los siguientes valores: "Tomate", 1.5, "Ver-
dura".
• Copiad el Producto "Tomate" en el Producto "Zanahoria", y mo-
dificad el nombre y el precio a "2".
Tipos referencia por defecto
Existen, básicamente, dos tipos de datos por referencia predefinidos y son los
siguientes:
• Object. El tipo de dato object (System.Object) es el tipo de dato
principal del cual derivan el resto de tipos de datos, tanto si son por valoro por referencia, como si son por defecto o definidos por el usuario.
• El tipo object define una serie de características y funcionalidades co-
munes para todos los tipos de datos .NET, entre los cuales encontramos
métodos para comparar objetos entre sí (Equals), para consultar el tipo
de un objeto concreto (GetType), o para obtener una representación del
objeto en forma de cadena de caracteres (ToString). Cada subtipo que
deriva del tipo object puede redefinir estos métodos para adecuarlos a
sus necesidades.
• String . El tipo string (System.String) es un tipo especial, ya que por
una parte se comporta como un tipo valor, aunque en realidad es un tipo
referencia. Los valores de tipo string se expresan entre comillas dobles
("):
string s = "Hola esto es un string"
Dentro de un string se pueden además utilizar caracteres de escape:
string s = "Hola esto \\ es un \' string\n"
En C# también es posible evitar los caracteres de escape para escribir carac-
teres como \, ', o incluso \n (las comillas (") no, ya que son el carácter de
final de cadena), si delante de la cadena de caracteres ponemos el carácter
@. La siguiente cadena es equivalente a la anterior:
string s = @"Hola esto \ es un ' string
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 21/80
© FUOC • P08/B0036/01627 21 C#
"
La clase string tiene multitud de métodos para trabajar con cadenas de
caracteres.
Ejemplo
CompareTo. Compara dos cadenas alfanuméricamente
IndexOf. Devuelve la posición de una subcadena
Replace. Cambia una subcadena por otra
Substring. Devuelve la subcadena que empieza en la posición especificada y que tienela longitud indicada.
ToLower. Devuelve la misma cadena pasada a minúsculas
ToUpper. Devuelve la misma cadena pasada a mayúsculas
Trim. Devuelve la misma cadena eliminando los espacios al principio y final de la cadena.
Actividad 3
Ejecutad las siguientes acciones:
• Cread una cadena que contenga la frase "La lluvia en Sevilla
es una maravilla".
• Buscad la primera y la última posición en que aparece la subcadena
"ll", y almacenadlas en dos variables de tipo entero.
• Obtened la subcadena que va desde la primera hasta la última posi-
ción en la que aparece la subcadena "ll" (ambas posiciones inclui-
das). ¿Cuál es la subcadena resultado?
• Pasad la cadena del apartado anterior a mayúsculas.
Tipos referencia definidos por el usuario
Existen, básicamente, dos tipos referencia que se pueden definir: las clases y
las interfaces. Una clase es una estructura de datos que contiene una serie de
miembros, ya sean atributos o métodos (acciones que se pueden realizar sobre
una instancia de la clase). Veremos con más detalle los tipos referencia defini-
dos por el usuario en el apartado de orientación a objetos. Son los siguientes:
• Clases. Los objetos son instancias concretas de una clase, con unos valores
concretos para cada uno de los atributos que la componen. Para crear un
objeto, debemos primero declararlo como en el resto de tipos de datos, y
a continuación inicializarlo, mediante la palabra clave new:MiClase mc = new MiClase ();
Para inicializar una variable sin crear un objeto en concreto, utilizamos la
palabra clave null:
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 22/80
© FUOC • P08/B0036/01627 22 C#
MiClase mc = null;
La palabra null también se utiliza para eliminar la referencia que contiene
la variable hacia el objeto:
MiClase mc = new MiClase ();
// instrucciones que utilizan mc
mc = null; // utilizar mc a partir de esta
// instrucción produce un error de ejecución
• Interfaces. Las interfaces son como patrones o plantillas de comportamien-
to para las clases. Una clase que cumpla una determinada interfaz debe
implementar todas las funcionalidades que se indican en ésta.
1.3.10. Tipos parciales
Un tipo parcial no es más que un tipo definido por el usuario (básicamente
estructuras, clases o interfaces), cuya definición se extiende a lo largo de más
de un fichero de código.
Esta separación de un tipo en partes facilita el desarrollo en equipo (cada
miembro del equipo trabaja con una parte) y el mantenimiento (las actualiza-
ciones se almacenan en una nueva parte). En Visual Studio, se utilizan tipos
parciales para separar el código que generan los diferentes diseñadores (Win-
Forms, WebForms, DataSets, etc.), del código que escribe el usuario.
Para declarar que un tipo es parcial, se indica con la palabra clave partial:
public partial class nombreClase
{
// código 1
}
public partial class nombreClase
{
// código 2
}
Una vez compilado, el código resultante es el mismo que si se hubiera escrito
el siguiente código:
public class nombreClase
{
// código 1
// código 2
}
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 23/80
© FUOC • P08/B0036/01627 23 C#
Todas las partes de un tipo parcial se deben compilar a la vez, es decir, no se
puede añadir una nueva parte a un tipo ya compilado.
1.3.11. Tipos anulables (nullable types)
Los tipos valor, aunque son objetos y heredan de la clase object como ya
hemos dicho, no admiten el valor nulo (null). Esto generalmente no es un
problema, aunque en determinadas ocasiones vendría bien asignar a una va-
riable un valor que no fuera ninguno de los del rango posible, para indicar
que ha habido un error, o simplemente que no hay resultado.
Los tipos anulables (nullable types), son versiones anulables de los tipos valor
ya existentes. Estos tipos se definen escribiendo el símbolo ? detrás del nombre
del tipo original, por ejemplo:
int? i = null;
Estos tipos tienen un método llamadoHasValue de tipo booleano, que indica
si el valor de la variable es nulo o no, y la propiedad Value que devuelve el
valor. Si HasValue es cierto, la propiedad Value devuelve el valor contenido,
si HasValue es falso, acceder a la propiedad Value produce una excepción.
Por otro lado, los tipos anulables son convertibles a sus correspondientes ver-
siones no anulables y viceversa. La conversión de un tipo no anulable a un
tipo anulable es implícita. La conversión de una variable anulable a una noanulable debe hacerse explícitamente (mediante la operación de cast) y puede
producir una excepción.
Las conversiones entre tipos de diferente tamaño se propagan a los tipos anu-
lables, es más, se pueden dar combinaciones diferentes de origen anulable o
no y destino anulable o no, siempre teniendo en cuenta que la conversión de
anulable a no anulable es implícita, pero la inversa no.
Los operadores de los tipos no anulables son también aplicables a los tipos
anulables. Los operadores aritméticos devuelven el valor nulo si alguno de los
parámetros es nulo, independientemente del valor del resto de valores. Los
operadores de comparación devuelven falso si alguno de los parámetros es
nulo.
Ejemplo
Por ejemplo x<y si y es nulo el resultado es falso, pero no quiere decir que x sea menorque y, ya que no son comparables.
Los tipos anulables tienen el operador adicional ??, que permite convertir
implícitamente un valor anulable en uno no anulable, indicando el valor que
se debe asignar en caso de que el tipo anulable tenga valor nulo:
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 24/80
© FUOC • P08/B0036/01627 24 C#
int? i = null;
int j = i ?? -1;
En tipo anulable booleano bool?, pasa a tener tres valores posibles: true,
false y null. Las operaciones booleanas se modifican para adaptarse al nue-
vo valor de la siguiente forma:
• &&. Si alguno de los parámetros es false, el resultado es false.
Si no, si alguno de los parámetros es null, el resultado es null.
Si no, el resultado es true (los dos parámetros son true).
• ||. Si alguno de los parámetros es true, el resultado es true.
Si no, si alguno de los parámetros es null, el resultado es null.
Si no, el resultado es false (los dos parámetros son false).
1.3.12. Arrays
Los arrays o vectores son estructuras de datos que contienen una lista de ele-
mentos de otros tipos de datos, de forma que podemos acceder a esos elemen-
tos indicando la posición concreta dentro del array. Para declarar un array uti-
lizamos la siguiente sintaxis:
tipo [] var;
Y para inicializar el array, utilizamos la instrucción new:
var = new tipo [N]
N es el número de posiciones o elementos que podrá almacenar el array. Por
ejemplo, para declarar e inicializar un array de 4 posiciones de tipo string,
utilizamos la siguiente instrucción:
string [] stringsArray = new string [4];
Para acceder a los elementos de un array utilizamos los símbolos [ y ], indican-
do la posición que queremos consultar o a la que queremos asignar un valor:
// asignación de un valor en la posición 0
stringsArray[0] = "Otro string";
// consulta del valor de la posición 2 string s = string-
sArray[2];
Como se puede ver en el ejemplo, las posiciones de un array se numeran del 0
a N-1 (N es el número de posiciones del array). Intentar acceder a una posición
del array que no existe, produce un error. Para saber cuál es el número de
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 25/80
© FUOC • P08/B0036/01627 25 C#
elementos, las variables array contienen la propiedad Length. Esta propiedad
es útil, por ejemplo, a la hora de realizar un recorrido sobre los elementos de
un array:
for (int i=0;i<stringsArray.Length;i++)
{
Console.WriteLine (stringsArray[i]);
}
Es importante tener en cuenta que un array no es más que un contenedor de
otros objetos. Por eso, la instrucción de inicialización de un array solamente
inicializa las posiciones en las que se ubicarán dichos objetos, pero no los ob-
jetos en sí. Por ejemplo, la variable stringsArray del ejemplo anterior con-
tiene un array de tipo string de 4 posiciones, pero cada una de esas posicio-
nes tendrá valor null (ya que no están inicializadas). Para inicializar una po-
sición es necesario asignarle un valor, por ejemplo:
stringsArray [0] = "string1";
Otra posibilidad es inicializar directamente los elementos de un array en la
misma instrucción de inicialización, como se muestra en el siguiente ejemplo:
string [] stringsArray = { "string1", "string2", "string3",
"string4" };
No obstante, hay una excepción en la que los elementos del array sí que se
inicializan en la instrucción de creación del mismo, y es en el caso de arrays
de tipos valor, ya que los tipos valor no pueden ser null (excepto los tipos
anulables). En este caso, las posiciones del array se inicializan con el valor por
defecto del tipo de dato, por ejemplo en un array de int, todas las posiciones
se inicializarían a 0 (en el caso de int? se inicializarían a null ).
Arrays multidimensionales
Los arrays multidimensionales, tal y como indica su nombre, son arrays demás de una dimensión. Cada dimensión del array se puede entender como
un array de arrays de dimensión menor, es decir, un array de N dimensiones
con N > 1, es un array en que cada posición contiene a su vez otro array de
N-1 dimensiones. Para localizar una posición de un array multidimensional,
es necesario, por lo tanto, especificar N índices, siendo N el número de dimen-
siones del array. Existen dos tipos de arrays multidimensionales: rectangulares
y ortogonales.
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 26/80
© FUOC • P08/B0036/01627 26 C#
Los arrays multidimensionales rectangulares tienen un número fijo de posi-
ciones en cada dimensión. Por ejemplo, un array rectangular de 2 dimensio-
nes equivaldría a una matriz de N x M con N filas de M columnas cada una.
Podemos declarar un array de este tipo de la siguiente forma:
string [,] stringsArray = new string [10, 4];
string [,,] stringsArray2 = new string [10, 10, 10];
En la declaración del array se indica el número de dimensiones (el número de
espacios entre comas), y en la inicialización se indica el número de posiciones
concretas de cada dimensión entre comas. También podemos inicializar los
elementos del array al crearlo, el siguiente ejemplo crea e inicializa un array
de 4x2:
string [,] stringsArray = { { "string11", "string12" } ,
{ "string21", "string22" } ,
{ "string31", "string32" } ,
{ "string41", "string42" } };
Para acceder a una posición del array, se utiliza la misma notación:
// accede a la posición 3,2 del array stringsArray[3,2] =
"Otro string";
Los arrays multidimensionales ortogonales a diferencia de los rectangulares,pueden tener un número diferente de columnas para cada fila. La declaración
de este otro tipo de arrays se realiza de esta forma:
string [][] stringsArray;
string [][][] stringsArray2;
Dado que cada fila puede tener un número diferente de columnas, no se pue-
den inicializar todas sus dimensiones inicialmente, tan sólo la primera:
stringsArray = new string [3][];
La inicialización de cada una de las otras dimensiones se realiza específica-
mente para cada una de las posiciones de la dimensión anterior, por ejemplo:
stringsArray[0] = new string [5];
stringsArray[1] = new string [6];
stringsArray[2] = new string [4];
El acceso a una posición concreta se realiza de forma similar, siguiendo esta
notación:
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 27/80
© FUOC • P08/B0036/01627 27 C#
// accede a la posición 3,2 del array stringsArray[3][2]
= "Otro string";
Actividad 4
Ejecutad las siguientes acciones:
• Declarad un array de tipo Producto.
• Cread e inicializad los elementos de un array de 4 posiciones de tipo
Meses en una misma línea.
• Cread una matriz de enteros de 4 X 4 e inicializad los valores de
cada posición, uno a uno, con los números del 1 al 16.
1.3.13. Conversiones entre tipos de datos
Algunos tipos de datos son compatibles entre sí, como por ejemplo los tipos
numéricos. Por ello, es posible realizar conversiones de valores de un tipo a
otro. A continuación, veremos cómo hacerlo:
• Conversiones implícitas. Si los tipos de datos son compatibles, y en la con-
versión no es posible perder información, la conversión se realiza de for-
ma implícita:int i = 3;
long l = i;
La conversión implícita es posible si se pasa de un tipo a otro con un rango de
valores posibles mayor, de forma que no se pierdan datos en la conversión.
• Conversión explicita (cast). En cualquier otro caso, es necesario especifi-
car que queremos forzar la conversión, aunque ello pueda comportar, en
según qué casos, una pérdida de información. Para ello, utilizamos la ope-
ración de cast:
long l = 3000000000;
int i = (int)l;
Las instrucciones anteriores se ejecutarán y no darán ningún error, pero
el resultado no será correcto, ya que ese número no será representable en
una variable de tipo int. Para que el programa detecte el error en tiempo
de ejecución y lance un error, podemos utilizar la instrucción checked:
long l = 3000000000;
int i = checked((int)l);
Si queremos extender esta comprobación a un conjunto de instrucciones,
podemos utilizar la instrucción checked en forma de bloque:
long l = 3000000000;
checked
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 28/80
© FUOC • P08/B0036/01627 28 C#
{
int i = (int)l;
int j = (int)(l*2);
}
La operación de cast es también válida para convertir variables de tipos
referencia, por ejemplo:
string s = "Hola";
object o = (object)s;
Esta conversión es válida porque todos los tipos de datos (incluida la clase
string, son a su vez subclases de object.
• Boxing/unboxing. Los tipos valor también descienden de la clase object,
como ya dijimos anteriormente. Por ello, es posible convertir un tipo valor
a su correspondiente tipo referencia, y viceversa, mediante las operaciones
de boxing y unboxing respectivamente.
Para realizar el boxing, convertimos el tipo valor a tipo referencia, asig-
nándolo a una variable de tipo object:
int i = 3;
object o = i;
Para realizar el unboxing, es necesario hacer un cast de la variable que
contiene el tipo referencia, al tipo valor correspondiente:
int j = (int)o;
1.3.14. Tipos y métodos genéricos
Los tipos genéricos son tipos de datos que utilizan en su definición tipos de
dato comodín, es decir, que pueden utilizarse como uno u otro tipo de dato
según convenga. Por ejemplo, el siguiente fragmento de código define la clase
genérica Par, con dos elementos de un mismo tipo, representado por el iden-
tificador T:
public class Par<T>
{
T e1;
T e2;
public T E1
{
get { return e1; }
set { e1 = value; }
}
public T E2
{
get { return e2; }
set { e2 = value; }
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 29/80
© FUOC • P08/B0036/01627 29 C#
}
}
Como se puede ver en el ejemplo anterior, el tipo genérico Par tiene un pará-
metro que es el tipo T que se utiliza dentro de su definición. En este caso, sólo
hay un parámetro, pero podría haber más de uno, por ejemplo, el mismo tipo
Par pero con elementos de tipo distinto se puede definir de la siguiente forma:
public class Par2
{
T e1;
S e2;
public T E1
{
get { return e1; }
set { e1 = value; }
}
public S E2
{
get { return e2; }
set { e2 = value; }
}
}
La elección del tipo de dato que, finalmente, tendrá cada parámetro del ti-
po genérico se especifica a la hora de instanciar, por ejemplo, para crear una
instancia de la clase genérica Par2 que contenga elementos de tipo entero y
string respectivamente, utilizamos la siguiente instrucción de inicialización:
Par2<int, string> par_int_str = new Par2<int, string>();
Tras la ejecución de la anterior inicialización, el tipo de dato de par_enteros
será equivalente al siguiente tipo de dato, resultante de substituir T y S por
int y string:
public class Par2_int_string
{
int e1;
string e2;
public int E1
{
get { return e1; }
set { e1 = value; }
}
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 30/80
© FUOC • P08/B0036/01627 30 C#
public string E2
{
get { return e2; }
set { e2 = value; }
}
}
Los métodos genéricos son similares a los tipos genéricos, ya que también uti-
lizan parámetros de tipo para crear algoritmos o fragmentos de código inde-
pendientes del tipo de dato con el que trabajan (por ejemplo un algoritmo de
búsqueda u ordenación). El siguiente ejemplo muestra la sintaxis concreta:
public static bool Equals <T>(T t1, T t2)
{
return t1.Equals(t2);
}
De nuevo, vemos en el ejemplo cómo el método genérico tiene un parámetro
que es el tipo que se va a utilizar en su definición. Al invocar el método debe-
mos especificar el tipo de dato concreto que se va a utilizar, de forma similar
a cuando se inicializa un tipo genérico:
bool b = Equals<int>(i, 5);
Los parámetros de tipo también se pueden utilizar para declarar campos, va-
riables o parámetros de tipo array, por ejemplo:
public static T[] QuickSort <T> (T[] t1, T[] t2)
{
...
}
Es importante tener en cuenta que utilizando parámetros de tipo no es nece-
sario realizar conversiones implícitas de tipos a la hora de pasar los paráme-
tros o de recuperar los resultados. Sin parámetros de tipo el anterior método
QuickSort se habría definido de la siguiente forma:
public static object[] QuickSort(object[] t1, object[] t2)
{
...
}
Y una llamada a dicho método para un array de enteros se realizaría de la
siguiente forma, con la correspondiente conversión de tipos (cast):
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 31/80
© FUOC • P08/B0036/01627 31 C#
int[] ordered = (int[])QuickSort (array1, array2);
Mientras que la llamada al método QuickSort genérico sería:
int[] ordered = QuickSort<int[]>(array1, array2);
Por último, hay determinados casos en los que los parámetros de tipo de los
tipos y métodos genéricos no pueden ser intercambiados con cualquier tipo
de dato. Un ejemplo es el método QuickSort utilizado en los ejemplos ante-
riores, en el que se requiere que los elementos de los arrays que se pasan como
parámetro permitan compararse entre sí para poder realizar la ordenación. Es-
te requisito se puede imponer obligando a que el tipo de dato utilizado here-
de la interfaz IComparable, que contiene el método CompareTo que permite
comparar elementos entre sí. La definición correcta del método QuickSort
genérico seria por lo tanto la siguiente:
public T[] QuickSort<T>(T[] t1, T[] t2)
where T : IComparable
{
...
}
Actividad 5
Ejecutad las siguientes acciones:
• Cread el tipo genérico Par con dos elementos de tipos diferentes.
Cread diferentes instancias de dicho tipo, con diferentes tipos de
datos para cada elemento.
• Cread el método genérico Comparar que compare los elementos de
dos arrays de tipo genérico y devuelva un array de enteros con los
resultados de cada comparación.
1.4. Instrucciones del lenguaje
A continuación, veremos las diferentes instrucciones del lenguaje C# y su co-
rrespondiente sintaxis.
1.4.1. Sentencias de decisión
Las sentencias de decisión, también llamadas sentencias condicionales o de
control de flujo, permiten escoger el código a ejecutar en un momento dado
dependiendo de una condición booleana.
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 32/80
© FUOC • P08/B0036/01627 32 C#
Existen dos sentencias o instrucciones de decisión: la instrucción if, y la ins-
trucción switch.
• If. La sintaxis general de la instrucción if es la siguiente:
if (condicion)
{
// Sentencias a ejecutar si la condición es cierta
}
else
{
// Sentencias a ejecutar si la condición es falsa
}
La condición expresada entre paréntesis debe ser una expresión booleana. Si
esta expresión es cierta, se ejecuta el primer bloque de instrucciones. En cam-
bio, si es falsa, se ejecuta el segundo bloque.
Si alguno de los bloques de sentencias a ejecutar está compuesto por una única
instrucción, se pueden omitir las llaves, aunque es recomendable escribirlas
siempre para mayor claridad del código.
El bloque de sentencias a ejecutar en caso de que la condición sea falsa (el-
se) es opcional. Si no se define un bloque else, en caso de que la condición
sea falsa, no se ejecuta ninguna sentencia, y se continúa la ejecución en lasiguiente instrucción después del if.
Por otro lado, podemos añadir más comprobaciones a la instrucción if en
caso de que la primera sea falsa, mediante los bloques else if:
if (condicion1)
{
// Sentencias a ejecutar si la condicion1 es cierta
}
else if (condicion2)
{
// Sentencias a ejecutar si la condicion1 es falsa y la condicion2 es cierta
}
...
else if (condicionN)
{
// Sentencias a ejecutar si las N-1 condiciones anteriores son falsas
// y la condiciónN es cierta
}
else
{
// Sentencias a ejecutar si todas las condiciones son falsas
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 33/80
© FUOC • P08/B0036/01627 33 C#
}
Solamente se ejecutará uno de los bloques. En caso de que haya más de una
condición cierta, solamente se ejecuta el bloque de la que esté definida antes.
Se pueden encadenar tantos else if como sea necesario, pero sólo puede
haber un bloque else (opcional), y siempre al final de la instrucción if.
• switch. La instrucción switch es una forma específica de instrucción
condicional, en la que se evalúa una variable, y en función de su valor,
se ejecuta un bloque u otro de instrucciones. La sintaxis general de la ins-
trucción switch es la siguiente:
switch (var)
{
case 1: // sentencias a ejecutar en caso de que la variable var tenga valor 1
break;
case 2: // sentencias a ejecutar en caso de que la variable var tenga valor 2
break;
...
default: // sentencias a ejecutar en caso de que la variable var tenga un valor
// diferente a los que se reflejan en los casos anteriores
break;
}
Cada bloque o caso (case) de una sentencia switch define las sentencias o
instrucciones que deben ejecutarse en caso de que la variable var del switch
tenga el valor especificado en ese case. Los valores especificados después de
la palabra clave case deben ser valores o expresiones constantes, de tipos in-
tegrales4
(sbyte, byte, short, ushort, int, uint, long, ulong
y cha), cadenas de caracteres (string) o enumeraciones, no se pueden utili-
zar variables. No se pueden repetir valores en los bloques case, por ejemplo,
la siguiente instrucción switch es incorrecta, ya que los tres primeros case
tienen valor 2:
switch (var)
{
case 2: // bloque A
break;
case 3-1: // bloque B
break;
case 4/2: // bloque B
break;
default: // bloque C
break;
}
(4)Encontrareís una definición de ti-
pos integrales buscando el término'tipo integral' en la ayuda de VisualStudio.
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 34/80
© FUOC • P08/B0036/01627 34 C#
El caso default es parecido al bloque else, define el bloque de instrucciones
que se ejecuta si el valor de la variable var no coincide con ninguno de los
valores definidos en los diferentes bloques case, y también es opcional. La
diferencia es que este bloque se puede definir en cualquier punto de la instruc-
ción switch, ya que el orden de los casos no importa.
La palabra clave break indica que el bloque de sentencias de un caso ha aca-
bado, y traslada la ejecución a la siguiente instrucción después de la instruc-
ción switch. Si omitiéramos la instrucción break, se seguirían ejecutando las
instrucciones del siguiente bloque case, hasta encontrar un break o el final
de la instrucción switch. Por ejemplo:
switch (var)
{
case 1: // bloque A
case 2: // bloque B
break;
default: // bloque C
break;
}
Si la variable var tiene valor 1 se ejecutará el bloque de sentencias A y el blo-
que de sentencias B, ya que después del primero no hay ninguna instrucción
break. Esto es útil también para definir un mismo bloque de instrucciones
para varios casos, por ejemplo:
switch (var)
{
case 1:
case 2:
case 3: // bloque A
break;
case 4:
case 5: // bloque B
break;
default: // bloque C
break;
}
En este ejemplo, el bloque A se ejecuta si var tiene valor 1, 2 o 3, el bloque B
si tiene valor 4 o 5, y el bloque C en cualquier otro caso.
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 35/80
© FUOC • P08/B0036/01627 35 C#
Actividad 6
Ejecutad las siguientes acciones:
• Escribid una instrucción condicional que en función del precio de
un Producto p, escriba por pantalla "Barato" (de 0 a 1 €), "Normal"
(de 1 a 4), o caro (más de 4). En caso de que sea negativo, escribid
por pantalla "Error".
• Escribid una instrucción condicional que en función del valor de
una variable m de tipo Meses muestra por pantalla el nombre del
mes correspondiente.
1.4.2. Sentencias de iteración
Las sentencias de iteración, o bucles, permiten repetir una serie de instruccio-
nes mientras se cumpla una determinada condición. Cuando la condición es
falsa, el bucle termina y la ejecución continúa en la siguiente instrucción des-
pués del bucle. En C# existen 4 instrucciones de iteración diferentes: while,
for, do while y foreach
1)�for. La sintaxis de la instrucción for es la siguiente:
for (inicializacion; condicion; iterador)
{
// bloque de sentencias
}
La inicialización es una instrucción que se ejecuta una única vez al principio
de la instrucción for. Se utiliza generalmente para inicializar la variable de
control que indica cuando ha de finalizar el bucle.
El iterador es una instrucción que se ejecuta al final de cada iteración, y que
modifica el valor de la variable de control, de forma que en un momento dado
la condición del bucle sea falsa y éste finalice. En otro caso, si la condición
nunca fuese falsa, se crearía un bucle infinito.
La condición de la instrucción for es una condición booleana que se evalúa
a cada iteración del bucle. Si la condición es cierta, se ejecuta el bloque de
sentencias del for, después se ejecuta la instrucción de iteración, y a conti-
nuación se vuelve a evaluar la condición. Si la condición es falsa, se continúa
la ejecución en la instrucción siguiente después del for. El siguiente ejemplo
ejecuta el bloque A de instrucciones 10 veces:
for (int i = 0; i<10; i++)
{
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 36/80
© FUOC • P08/B0036/01627 36 C#
// bloque A
}
2)�while. La instrucción while se diferencia de la instrucción for en que
solamente tiene una condición de finalización. La inicialización e iteración se
deben realizar justo antes del while y dentro del while respectivamente. La
sintaxis concreta es la siguiente:
while (condicion)
{
// bloque de instrucciones a ejecutar
}
Mientras la condición sea cierta, se ejecutará el bloque de instrucciones. Por
eso, es necesario que haya alguna instrucción dentro del bloque que modifique
alguna de las variables que intervienen en la condición, de forma que, en un
momento dado, ésta pase a ser falsa y, por consiguiente, finalice el bucle. El
siguiente ejemplo es equivalente al ejemplo del for:
int i = 0;
while (i<10)
{
// bloque A
i++;
}
3)�do-while. Así como en las instrucciones for y while, primero se compro-
baba la condición y después se ejecutaba el bucle, en la instrucción do-while
es al contrario. Su sintaxis es la siguiente:
do
{
// bloque de sentencias a ejecutar
}
while (condición);
La diferencia fundamental es que, mientras que en un bucle for o while, si
la condición es falsa de entrada, no se ejecuta ninguna iteración, en un bucle
do-while siempre se ejecuta como mínimo una iteración.
4)�foreach. La instrucción foreach es un tipo de bucle específico para co-
lecciones de objetos. Permite recorrer todos los elementos de una colección
desde el primero al último. La sintaxis concreta es la siguiente:
foreach (tipo var in col)
{
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 37/80
© FUOC • P08/B0036/01627 37 C#
// bloque de sentencias a ejecutar
}
Donde tipo es el tipo de dato de los elementos que hay en la colección, var
es una variable que representa en cada iteración el elemento actual de la co-
lección, y col es la variable que almacena la colección.
Una colección no es más que un tipo de dato que implementa la interfaz
IEnumerable. Esta interfaz define el método GetEnumerator, que devuelve
una instancia de la interfaz IEnumerator, que a su vez define la propiedad
Current, y los métodos MoveNext y Reset, que se utilizan para recorrer los
elementos de una secuencia. Un ejemplo de colección son los arrays:
int [] nums = new int [] { 4,2,5,7,3,7,8 };
foreach (int i in nums)
{ ... }
También es posible crear tipos de datos que se puedan utilizar dentro de un bu-
cle foreach, utilizando iteradores. Los iteradores son sentencias que permiten
implementar el método GetEnumerator fácilmente, indicando que valores
se deben devolver en cada iteración de la instrucción foreach. Por ejemplo,
supongamos que tenemos una clase que contiene un array de enteros a, y que
queremos que las variables de esa clase se puedan utilizar dentro de un fo-
reach. En primer lugar, indicamos que la clase va a implementar la interfazIEnumerable:
public class Clase : IEnumerable
{
int [] a;
...
}
A continuación, implementamos el método GetEnumerator del siguiente
modo:
public void IEnumerator GetEnumerator()
{
for (int i = 0;i<a.Length;i++)
{
yield return a[i];
}
}
Entonces, podemos utilizar una variable de la clase en una instrucción fo-
reach, como si de una colección se tratase:
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 38/80
© FUOC • P08/B0036/01627 38 C#
Clase c = new Clase ();
foreach (int i in c)
{
Console.WriteLine(i);
}
Los iteradores también son aplicables a métodos. En vez de implementar el
método GetEnumerator, podríamos haber utilizado un método auxiliar con
tipo de retorno IEnumerable:
public static IEnumerable GetNumbers()
{
for (int i = 0;i<a.Length;i++)
{
yield return a[i];
}
}
En este caso, no podremos utilizar una variable de la clase en el foreach, sino
el resultado del método, es decir:
foreach (int i in P.GetNumbers())
{
Console.WriteLine(i);
}
Recurso web
En la siguiente página web,se puede encontrar más in-formación acerca de los itera-dores:
<http://msdn.microsoft.com/en-us/library/dscyy5s0.aspx>
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 39/80
© FUOC • P08/B0036/01627 39 C#
Actividad 7
Ejecutad las siguientes acciones:
• Cread un bucle que realice la suma de dos matrices de enteros a1 y
a2. Sabemos que las dos matrices tienen el mismo número de filas
y de columnas, pero no sabemos cuántas exactamente. El resultado
debe de almacenarse en una tercera matriz a, que hay que declarar
e inicializar.
• Inicializad una variable i a 0, mostrad por pantalla el valor de i e
incrementad i en uno, mientras i sea menor que N (N>=0). Mos-
trad todas las formas posibles de implementar este bucle.
• Buscad todas las posiciones en las que se encuentra la subcadena
"ll" dentro de la cadena "La lluvia en Sevilla es una ma-
ravilla", y mostradlas por pantalla. No se puede modificar la ca-
dena, ni utilizar variables string auxiliares. Mostrar todas las for-
mas posibles de implementar este bucle.
1.4.3. Sentencias de salto
Las instrucciones de salto permiten pasar o 'saltar' inmediatamente desde una
instrucción a otra línea del programa no necesariamente a continuación de lalínea actual. Las instrucciones de salto de C# son las siguientes:
• Break. La instrucción break se utiliza dentro de instrucciones de bucle y
en la instrucciónswitch. Si dentro de un bucle o de un switch encontra-
mos una instrucción break, la ejecución 'sale' inmediatamente del bucle
o del switch, y continúa en la siguiente línea después de la instrucción
en la que se encontraba el break.
• Continue. La instrucción continue es similar al break, pero se utiliza
sólo dentro de instrucciones de bucle. Al encontrar un continue dentro
de un bucle, finaliza la ejecución de la iteración actual del bucle, y se con-
tinúa en la siguiente. Hay que tener un especial cuidado con esto, ya que
puede provocar bucles infinitos, por ejemplo, en el siguiente bucle la ins-
trucción continue se encuentra justo antes de la instrucción que modi-
fica la variable de control, por lo que la variable i siempre valdrá 10 y el
bucle nunca terminará:
int i = 10;
while (i>0)
{
// instrucciones del bucle
continue;
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 40/80
© FUOC • P08/B0036/01627 40 C#
i--;
}
• Return. La instrucción return es la instrucción de salto que se utiliza
para abandonar un método y volver a la instrucción siguiente a la que
llamó a ese método. Si el método devuelve valores, la instrucción return
irá acompañada del valor que se ha de devolver. En caso contrario, la ins-
trucción return es opcional (se realiza implícitamente). Hablaremos so-
bre métodos más adelante.
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 41/80
© FUOC • P08/B0036/01627 41 C#
2. Orientación a objetos en C#
C# es un lenguaje orientado a objetos puros, es decir, todas sus características
son orientadas a objetos. La orientación a objetos es una técnica de progra-
mación que consiste en tratar cada elemento del programa como un objeto,
e intentar abstraer las características de cada objeto para clasificarlos en dife-
rentes familias o clases.
El desarrollo de una aplicación orientada a objetos consiste en definir un con-
junto de clases de objetos, especificando las características comunes que tie-
nen todos los elementos u objetos que pertenecen a esa clase, así como las
acciones que se pueden realizar sobre un objeto concreto de la clase. Para con-
seguir el objetivo de la aplicación a desarrollar, los objetos interaccionan entre
sí, pasándose información. Cada objeto tiene su funcionalidad y sabe a qué
objeto tiene que pedir cierta información necesaria para realizar sus funciones.
A continuación, veremos los diferentes conceptos de orientación a objetos y
su utilización en C#.
2.1. Definición de clases
En C# podemos definir una clase mediante la palabra clave class, seguidodel nombre de la clase:
class nombre
{
// definicion de la clase
}
Todas las instrucciones contenidas dentro de las llaves {} pertenecen a la de-
finición de la clase. Dentro de esa definición, podemos encontrar dos tipos
de elementos o miembros: miembros de datos y miembros de función. Los
miembros de datos son aquellos que describen el estado de un objeto de la
clase, mientras que los miembros de función son los que definen su compor-
tamiento y funcionalidad.
Los miembros de una clase definen las características de los objetos o instan-
cias de la clase. Cada instancia tendrá asignados diferentes valores para los
miembros de datos, y, por consiguiente, los miembros de función tendrán un
resultado distinto dependiendo de en qué instancia de la clase se ejecuten.
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 42/80
© FUOC • P08/B0036/01627 42 C#
Existe un tipo especial de miembros, los miembros estáticos, que se comportan
igual para todas las instancias de la clase, porque actúan globalmente, a nivel
de clase en vez de a nivel de instancia como el resto de miembros. Para indicar
que un miembro es estático, se añade el modificador static en la definición
del miembro, como veremos para cada caso concreto.
Actividad 8
Ejecutad las siguientes acciones:
• Cread la clase Coche.
2.2. Instanción de clases
La declaración de variables de una clase es idéntica a la declaración de variables
de cualquier otro tipo de dato, por ejemplo:
MiClase mc;
La inicialización de una variable de clase implica la creación de un espacio en
el heap para los datos de la nueva instancia, y la asignación de la referencia
correspondiente a la variable. Esta operación se realiza mediante la instrucción
new:
mc = new MiClase ();
Y como en el resto de tipos, se puede unir la definición y la inicialización en
una única línea:
MiClase mc = new MiClase ();
El método que se utiliza después de la palabra clave new, que tiene el mis-
mo nombre que la clase, se denomina método constructor. Hablaremos de los
constructores de la clase más adelante.
El valor por defecto de una variable es el valor nulo, es decir, la variable no
está asignada a ningún objeto. El valor nulo se representa mediante la palabra
clave null.
El valor nulo sirve para inicializar una variable a la que, de momento, no que-
remos asignar un objeto (no podemos dejarla inicializada porque se produce
un error de compilación):
MiClase mc = null;
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 43/80
© FUOC • P08/B0036/01627 43 C#
Otra utilidad de null es 'eliminar' la referencia contenida en la variable, por
ejemplo:
MiClase mc = new MiClase ();
mc = null;
Después de la última instrucción, la variable mc no se puede volver a utilizar
para acceder al objeto creado anteriormente. De todas formas, hay que tener
en cuenta que esto no elimina el objeto que se encuentra en el heap. El encar-
gado de eliminar el objeto es el Garbage Collector, que detecta aquellos objetos
situados en el heap que han dejado de ser referenciados por alguna variable, y
que por lo tanto pueden ser eliminados sin provocar problemas.
Hay que tener claro, por lo tanto, que una cosa es el objeto que reside en el
heap, y otra la variable que contiene una referencia a ese objeto. Las variables
no tienen por qué estar siempre relacionadas con el mismo objeto en memoria.
Ni un objeto tiene por qué estar relacionado siempre con la misma variable,
ni con una sola. Por ejemplo:
MiClase mc = new MiClase ();
MiClase mc1 = mc;
La variable mc1 referencia exactamente al mismo objeto que mc, no se crea un
nuevo objeto, por lo que si se modifica el objeto utilizando la variable mc1, alacceder con la variable mc observaremos los cambios realizados.
Otra cosa a tener en cuenta de los objetos y las variables es que dos objetos no
se pueden comparar, comparando simplemente las variables, ya que lo que se
comparan son las referencias. Si la referencia es la misma (el mismo objeto),
son iguales, sino no. Por ejemplo:
MiClase mc1 = new MiClase ();
MiClase mc2 = m1;
MiClase mc3 = new MiClase ();
if (mc1==mc3)
{
// estas instrucciones no se ejecutan nunca, porque
// mc1 y mc3 referencian dos objetos diferentes,
// independientemente de que entre ellos sean
// equivalentes.
}
if (mc1==mc2)
{
// estas instrucciones se ejecutan siempre, porque
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 44/80
© FUOC • P08/B0036/01627 44 C#
// mc1 y mc2 contienen la misma referencia.
}
Existe una excepción de tipo por referencia que sí se puede comparar con la
operación de comparación, como siempre el tipo string. El resto de tipos
referencia deben de utilizar el mecanismo de comparación para objetos, rede-
finiendo el método Equals, definido en la clase Object, para que devuelva
true si los dos objetos son equivalentes (aunque no sean la misma instancia).
Actividad 9
Ejecutad las siguientes acciones:
• Cread una instancia de la clase Coche.
2.3. Herencia
Las clases pueden organizarse en jerarquías complejas que permiten extender
y aprovechar las funcionalidades de unas clases en otras. Estas jerarquías se
crean mediante la relación de herencia, que relaciona dos clases: superclase y
subclase (o clase padre y clase hija), de forma que la subclase hereda toda la
funcionalidad de la superclase (atributos, métodos, etc.).
Cualquier objeto del tipo de la subclase es a la vez del tipo de la superclase,pero no a la inversa. Es decir, un objeto de tipo B (que es subclase de A), se
puede convertir a tipo A de la siguiente forma:
B varB = B();
A varA = (A)varB;
Sin embargo, mediante la variable varA no es posible acceder a los miembros
específicos del tipo B, ya que en esta variable el objeto está desempeñando la
función del tipo A (trabaja como si fuera directamente un objeto del tipo A).
Todas las clases que descienden de una clase (aunque no sean clases hijas di-
rectamente), son subclases de esa clase. Todas las clases que hay que recorrer
en la jerarquía de herencia desde la clase hasta la clase objec t, son superclases
de esa clase.
Para indicar que una clase hereda de otra, añadimos dos puntos detrás del
nombre de la clase, y a continuación el nombre de la superclase:
class nombre : superclase
{
}
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 45/80
© FUOC • P08/B0036/01627 45 C#
Si no se especifica alguna superclase, la clase heredará por defecto de la clase
object.
2.3.1. Interfaces
En C#, y en .NET en general, una clase sólo puede heredar de una única clase
(herencia simple). Existe otro tipo de herencia que permite que una clase pue-
da heredar de más de una clase (herencia múltiple), pero este tipo de herencia
conlleva una serie de complicaciones que hacen que su implementación sea
demasiado costosa.
La solución que ofrecen los lenguajes de .NET al problema de la herencia múl-
tiple es la utilización de interfaces. Una interfaz es una definición o plantilla
de las funcionalidades (métodos) que debe de implementar toda clase que he-
rede de esa interfaz. Así como no es posible heredar de más de una clase, sí
que es posible heredar de más de una interfaz.
Una interfaz tiene una estructura similar a la de una clase:
interface nombre
{
}
Dentro de las llaves sólo puede haber definiciones de métodos. Cada defini-
ción consiste en la cabecera del método (visibilidad, tipo de retorno, nombrey parámetros), pero sin incluir una implementación. Cada definición finaliza
con un punto y coma (;).
Las interfaces también pueden heredar a su vez de otras interfaces (una o va-
rias). De este modo, la clase que herede de una interfaz debe de implementar
todos los métodos definidos en esa interfaz y en todas las interfaces de las que
esta hereda, recursivamente hasta llegar a la clase object (que aunque pueda
parecer una contradicción, es la clase de la que hereda cualquier interfaz si no
se indica lo contrario).
Actividad 10
Ejecutad las siguientes acciones:
• Cread la subclase CocheGasolina de la clase Coche.
• Cread una interfaz llamada Vehiculo.
• Haced que la clase Coche herede de la interfaz Vehiculo.
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 46/80
© FUOC • P08/B0036/01627 46 C#
2.4. Miembros de datos
Los miembros de datos son los siguientes:
• Campos (fields). Los campos o atributos de una clase son las características
que definen a los objetos de esa clase. Contienen los datos que almacenan
y con los que trabajan los objetos de esa clase.
En cualquier punto de la definición de la clase (excepto dentro de un
miembro de función), se puede definir un campo como la declaración de
una variable e incluso definir su visibilidad, por ejemplo:
int i;
public string s;
protected MiClase mc;
Las variables i, s y mc a partir de este momento son variables globales
dentro del ámbito de la clase.
Para acceder a los campos desde la propia clase, basta con escribir el nom-
bre de la variable, ya que los campos son globales en todo el ámbito de
la clase. A veces, es necesario desambiguar entre una variable local con
el mismo nombre y el propio campo. Para ello, existe la variable especial
this, que representa el objeto actual. Usando this, podemos acceder a
todos los campos de la propia clase:
int i; // variable local
i = this.i; // asignamos el valor del campo i a la
// variable local i
Para acceder a los campos públicos desde fuera de la clase, hay que tener
una variable que haga referencia a un objeto de la clase. Escribimos el
nombre de la variable, seguida de un punto y del nombre del campo, su-
poniendo que la clase MiClase tiene un campo con nombre i:
MiClase mc = MiClase ();
mc.i = 4;
Aparte de los campos que definen las características que diferencian los
distintos objetos de una clase, existen campos que definen características
comunes a todos los objetos de la clase. Estos campos se denominan cam-
pos estáticos, y se definen añadiendo la palabra clave static entre la vi-
sibilidad y el tipo de dato:
public static MiClase mc;
Para acceder a los campos estáticos desde la propia clase, podemos escribir
el nombre de la variable como si se tratara de campos normales. Para ac-
ceder desde fuera de la clase a los campos estáticos públicos, utilizamos el
nombre de la clase, seguido de un punto y del nombre de la variable. Su-
poniendo que la clase tiene un campo estático de tipo string llamado s:
string s = MiClase.s;
• Constantes. Las constantes son variables que contienen un valor que se
establece en tiempo de compilación y que no se puede modificar. Para
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 47/80
© FUOC • P08/B0036/01627 47 C#
declarar una constante utilizamos la palabra clave const, seguida de la
definición e inicialización de la variable:
const int N = 30;
Otro mecanismo para definir constantes son las variables de sólo lectura.
Estas variables se pueden inicializar dentro del constructor de la clase (al
crear un objeto de la clase), de forma que no tienen por qué tener un
valor fijo constante asignado en tiempo de compilación, sino que depende
de algún parámetro en tiempo de ejecución. Una vez inicializadas dentro
del constructor, sólo pueden ser consultadas, no asignarlas un valor. Para
definir una variable de sólo lectura, utilizamos la palabra clave readonly:
readonly string saludo;
• Eventos. Los eventos son unos miembros especiales de la clase que permi-
ten almacenar una lista de objetos interesados en un determinado evento.
Cuando ese evento ocurre en el objeto, todos los objetos subscritos en el
evento son avisados. Veremos con detalle la definición y utilización de
eventos en el apartado de conceptos avanzados.
Actividad 11
Ejecutad las siguientes acciones:
• Añadid el campo caballos de tipo entero a la clase Coche y el
campo carburante de tipo string a la clase CocheGasolina.
• Comprobad que no se pueden añadir campos o constantes a la in-
terfaz Vehiculo.
• Haced que el campo caballos de la clase Coche sea accesible desde
la clase CocheGasolina.
• Comprobad que se puede acceder al campo caballos desde Co-
cheGasolina, pero no se puede acceder al campo carburante desde
la clase Coche.
2.5. Miembros de función
Los miembros de función son los que definen el comportamiento de los obje-
tos de una clase. A continuación, veremos los diferentes tipos de miembros de
función que se pueden definir y cuál es su utilidad.
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 48/80
© FUOC • P08/B0036/01627 48 C#
2.5.1. Métodos
Los métodos son los miembros de una clase que encapsulan su funcionalidad.
Las aplicaciones orientadas a objetos se basan en la ejecución de los métodos
adecuados de los objetos con los que trabaja, para obtener el resultado espe-
rado.
La definición de un método indica, aparte de su nombre, los parámetros que
necesita para realizar su funcionalidad, y cuál es el tipo de resultado que de-
vuelve, si es el caso. La estructura general de definición de un método, tam-
bién llamada signatura, es la siguiente:
visibilidad modificador tipo_retorno nombre (parámetros)
{
// instrucciones del método
return x; // instrucción que devuelve el valor de la variable
// o expresión x como resultado del método.
// Si el tipo de retorno es void (no devuelve nada),
// no hay que poner esta instrucción.
}
• Visibilidad. La visibilidad indica desde que puntos del programa se po-
drá acceder a ese método. Existen los siguientes tipos de visibilidad:
– Public. Se puede utilizar desde cualquier punto del programa.
– Prívate. Sólo se puede utilizar dentro del tipo (clase o estructura) enla que está definido.
– Protected. Se puede ejecutar dentro del propio tipo o de sus subcla-
ses.
– Internal. Se puede ejecutar en cualquier clase dentro del mismo en-
samblado.
– Protected internal. Combinación de las dos anteriores.
• Modificador. El modificador es una palabra clave opcional que identifica
el tipo de método:
– New. Esta definición del método oculta un método heredado de la su-
perclase, con la misma signatura.
– Static. El método es un método de clase. Los métodos de clase no se
ejecutan sobre una instancia en concreto de la clase.
– Virtual. El método puede ser redefinido por alguna subclase.
– Abstract. El método debe de de ser redefinido en alguna de las sub-
clases. No se incluye implementación, solo su signatura.
– Override. Esta definición del método sobrescribe un método hereda-
do de la superclase.
– Sealed. Prohíbe que el método pueda ser sobrescrito (override),
por subclases de esta clase.
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 49/80
© FUOC • P08/B0036/01627 49 C#
– Extern. El método esta implementado externamente, en otro lengua-
je distinto.
• Tipo de retorno. El tipo de retorno es el tipo de dato del valor que se obtiene
al ejecutar el método. Si el método sólo realiza operaciones y no devuelve
ningún valor, el tipo de retorno se indica como void.
• Nombre del método. El nombre del método es el identificador mediante
el cual se accederá a este método, por lo que es importante que el nombre
escogido permita comprender su funcionalidad fácilmente.
• Utilización de un método. Los métodos en general necesitan una variable
de la clase para ser ejecutados, a excepción de los métodos estáticos, que
veremos a continuación, y de los abstractos, que no se pueden ejecutar.
Para ejecutar o invocar un método, escribimos el nombre de la variable,
seguido de un punto y del nombre del método.
A continuación, escribimos entre paréntesis una lista de 0 a N variables o
expresiones, que vayamos a pasar como parámetros, en total tantos como
parámetros indique la definición del método que vamos a ejecutar, con el
mismo tipo de dato, y en el mismo orden. El resultado de la ejecución del
método (si tiene tipo de retorno), se puede asignar a una variable, utilizar
dentro de una expresión o como parámetro de otro método. Por ejemplo:
string s = "Hola";
string s1 = s.ToString();
s1 = s1 + s.SubString (1, 2);
int i = s1.IndexOf (s.SubString (0, 2));
El método ToString no tiene parámetros, por lo que no debemos especi-
ficar ninguno. El resultado lo asignamos a la variable s1. A continuación,
utilizamos el método SubString dentro de una expresión de concatena-
ción. En este caso, indicamos indicar el índice de inicio de la subcadena
y la longitud.
Es importante darse cuenta de que hay dos versiones del método SubS-
tring: una recibe dos parámetros, pero hay otra que sólo recibe uno, la
posición de inicio (devuelve la subcadena desde esta posición hasta el fi-
nal del string). Esto es porque, como veremos un poco más adelante, se
pueden escribir varias definiciones de un mismo método, con diferentes
tipos de parámetros (El método en sí hace lo mismo, pero hay diferentes
formas de pasarle los parámetros como en el caso del SubString.
En la tercera instrucción, estamos utilizando el resultado de la operación
SubString como parámetro del método SubString que devuelve la pri-
mera posición en la que aparece la subcadena indicada.
• Métodos estáticos. Por otro lado, tenemos los métodos estáticos (static)
o métodos de clase. Estos métodos no se ejecutan mediante una variable
concreta de la clase, y que son métodos globales. Para ejecutarlos, es ne-
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 50/80
© FUOC • P08/B0036/01627 50 C#
cesario especificar el nombre de la clase, seguida de un punto, el nombre
del método y la lista de variables o expresiones a pasar como parámetro:
String.CompareTo ("Hola", "Adios");
Dentro de la definición de un método estático sólo se pueden utilizar atri-
butos estáticos de la propia clase. No se puede acceder al resto de atributos,
ya que no existe una instancia de la clase (el método no se está ejecutando
sobre una instancia, sino globalmente).
Un ejemplo de método estático bastante claro es el método Main, que es
el método principal que se ejecuta al iniciar el programa. Es un método
estático porque no se ejecuta sobre ninguna instancia de clase, ya que al
ser el primer método que se ejecuta aún no se ha creado ninguna.
• Parámetros. Los parámetros o argumentos del método son una lista de 0
a N variables, locales al método, que son necesarias para la ejecución del
mismo. En el momento de ejecución del método, estos parámetros reciben
unos valores determinados mediante el mecanismo de paso de parámetros.
Los parámetros se pasan siempre por valor, es decir, se copia el valor de la
variable o expresión especificadas en la llamada al método, en la variable
local representada por el parámetro. De ese modo, si se modifica el valor
de las variables de los parámetros, no se modifica el valor de las variables
que se pasaron como parámetro.
No obstante, debido a que las variables de los tipos referencia sólo contie-
nen la referencia a la dirección de memoria donde están los datos del tipo,al pasarlo como parámetro, lo que se pasa es simplemente la dirección y
por lo tanto si se accede al tipo dentro del método mediante la variable
del parámetro, se está modificando directamente el tipo que se pasó como
parámetro. Por ejemplo:
int i = 0;
int [] a = { 0, 1, 2, 3 };
PasoDeParámetros (i, a);
public void PasoDeParámetros (int j, int [] b)
{
j = 3;
b[0] = 3;
}
Una vez ejecutado el método, la variable i seguirá teniendo valor 0, mien-
tras que la posición 0 del vector a tendrá valor 3, en vez de 0 como al
principio.
El string como parámetro
El string es una excepción, ya que al pasar un string co-mo parámetro se copia la ca-dena, es decir, se comportacomo un tipo de valores.
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 51/80
© FUOC • P08/B0036/01627 51 C#
También es posible pasar por referencia las variables de tipos por valor, de
forma que se pueda modificar su valor dentro del método. Para ello, es
necesario añadir la palabra clave ref delante del parámetro en la defini-
ción del método, y delante de la variable (no es válido con expresiones)
en la llamada al método. En el ejemplo siguiente, después de la ejecución
del método, la variable i tendrá valor 3:
int i = 0;
PasoDeParámetros (ref i);
public void PasoDeParámetros (ref int j)
{
j = 3;
}
• Retorno. Los métodos que especifican un tipo de retorno diferente a void,
deben especificar como ultima instrucción cuál es el valor que se debe
devolver. Para ello utilizamos la instrucción return, seguida de la variable
o expresión cuyo valor queremos que se devuelva:
public int Retorno1 ()
{
return 3;
}
public int Retorno2 (int i)
{
return i * 2;
}
Este mecanismo sólo permite devolver un único valor de un método. Si
es necesario devolver más de un valor, podemos utilizar parámetros de
salida. Los parámetros de salida se definen añadiendo la palabra clave out
delante de la definición del parámetro en la signatura del método, y antes
de la variable que se va a utilizar para recuperar el valor en la llamada del
método:
int i;
Parametrosalida (out i);
public void Parametrosalida (out int j)
{
j = 3;
}
Así como los parámetros normales deben estar inicializados antes de ser
pasados como parámetros (de otro modo se produce un error de compila-
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 52/80
© FUOC • P08/B0036/01627 52 C#
ción), los parámetros de salida no requieren que la variable que se pasa
este inicializada.
• Sobrecarga de métodos. A veces, un mismo método puede ser invocado
con un número de parámetros distinto, ya sea porque algunos parámetros
son opcionales o porque hay varias formas de proporcionar la información
necesaria para ejecutar el método. Esto es posible gracias a la sobrecarga de
métodos. En C# y .NET en general, se puede escribir más de un método con
el mismo nombre, siempre que todos tengan el mismo tipo de retorno, y
diferentes parámetros.
• Sobrescritura de métodos. Cuando una clase hereda de otra, puede sobres-
cribir los métodos virtuales, abstractos o sobrescritos en la clase padre, para
que su resultado sea el adecuado para instancias de la subclase. Al sobres-
cribir un método de la superclase, debemos indicar el modificador ove-
rride:
public override string ToString ()
{
...
}
Para acceder a las versiones de los métodos de la superclase, podemos uti-
lizar la variable especial base, que identifica la instancia actual, converti-
da explícitamente al tipo de la superclase:
base.ToString (); // devolverá el resultado del método
// ToString de la superclase.
• Ocultación de métodos. Si lo que queremos es ocultar un método de la
superclase, debemos especificar el modificador new. De ese modo, el nuevo
método ocultará la definición del mismo método de la superclase en la
subclase:
public new string ToString ()
{
...
}
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 53/80
© FUOC • P08/B0036/01627 53 C#
Actividad 12
Ejecutad las siguientes acciones:
• Cread el método MarchaAtras en la clase Coche con el parámetro
metros de tipo entero, que devuelva un booleano.
• Declarad el método anterior en la interfaz Vehiculo.
• Cread el método estático ActualizarCoches en la clase Coche sin
valor de retorno, con el parámetro fichero de tipo string.
• Llamad a los métodos anteriores desde el programa principal.
2.5.2. Propiedades
Las propiedades son un caso muy concreto de métodos, que permiten acceder
o modificar el valor de un atributo de la clase. El motivo de utilizar propieda-
des, en vez de acceder directamente a los atributos de la clase, es doble. Por
un lado, se garantiza la encapsulación y ocultación de tipos, que consiste en
no permitir que se pueda acceder directamente a la información para evitar
modificaciones inadecuadas. Por otro lado, las propiedades permiten añadir
código adicional, por ejemplo, para realizar comprobaciones sobre si el valor
que se quiere modificar es correcto. Las propiedades también pueden devolvero modificar un valor que no se corresponda con ningún atributo concreto.
Para definir una propiedad, utilizamos la siguiente sintaxis:
visibilidad tipo_retorno Nombre_Propiedad
{
get
{
return var;
}
set
{
var = value;
}
}
Las instrucciones contenidas dentro del bloque get son las que se ejecutan
para acceder al valor de la propiedad. Al final del bloque, debe haber una ins-
trucción return que devuelva el valor correspondiente. Las instrucciones del
bloque set se ejecutan cuando se asigna un valor a la propiedad mediante la
instrucción de asignación. La palabra clave value identifica el valor que se
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 54/80
© FUOC • P08/B0036/01627 54 C#
asigna a la propiedad dentro del bloque set. Si omitimos el bloque set, ten-
dremos una propiedad de sólo lectura. Si omitimos el bloque get, tendremos
una propiedad de sólo escritura.
Podemos utilizar las propiedades de una clase directamente como si fueran
atributos de la misma, tanto a la izquierda de una asignación (si el bloque set
está definido), como dentro de una expresión (si el bloque get está definido).
Actividad 13
Ejecutad las siguientes acciones:
• Añadid la propiedad Matricula de tipo string a la clase Coche.
• Cread una instancia de Coche y asignadle un valor a la propiedad
anterior.
• Asignad el valor de la propiedad anterior a una variable.
2.5.3. Constructores
Los constructores son una serie de métodos especiales que se ejecutan al crear
un objeto de la clase. La funcionalidad de estos métodos es la de inicializar los
atributos de la clase con valores predefinidos o bien con valores que se pasancomo parámetros del constructor. Puede haber tantos constructores como sea
necesario, pero cada uno de ellos debe tener tipos de parámetros distintos. La
sintaxis de un constructor es la siguiente:
visibilidad nombreClase (parámetros)
{
}
Los métodos constructores no tienen tipo de retorno (lo que hacen es crear
una instancia de una clase y ese es el resultado del constructor), y todos ellos
se llaman igual que el nombre de la clase.
La definición de constructores es opcional, ya que por defecto hay siempre un
constructor vacío sin parámetros. Si se define explícitamente algún construc-
tor sin parámetros, éste sobrescribe al constructor por defecto.
Los constructores se utilizan en el momento de crear una instancia de una
clase, junto con la palabra clave new:
Clase var = new Clase (parámetros);
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 55/80
© FUOC • P08/B0036/01627 55 C#
Esta sería la instrucción de creación de la clase Clase, que llamará al método
constructor que corresponda según el número de parámetros que se especifi-
quen. Si no hay ningún constructor para la clase con ese número y tipos de
parámetros, se produce un error de compilación.
A veces, los constructores de una subclase realizan las mismas instrucciones
que un constructor de la superclase, más algunas otras. Es posible especificar
que, antes de ejecutar el constructor, se ejecute también uno de los construc-
tores de la superclase, de la siguiente forma:
public MiClase (parámetros) : base (parámetros_base)
{
}
Antes de ejecutar el constructor anterior, se llamará al constructor de la co-
rrespondiente superclase que tenga el mismo número y tipo de parámetros
especificados en parámetros base. Esto también se puede utilizar para que un
constructor ejecute inicialmente las instrucciones de otro constructor de la
propia clase. En este caso, en vez de utilizar la palabra clave base, utilizamos
la palabra clave this:
public MiClase (parámetros) : this (parámetros_this)
{
}
Los constructores de una clase generalmente son públicos. No obstante, tam-
bién se pueden definir constructores privados. La utilidad de un constructor
privado es la de evitar que se creen instancias de una clase, por ejemplo, por-
que todos sus métodos sean estáticos. Para ello, definimos un único construc-
tor, sin parámetros, con visibilidad private. Otra utilidad es la de crear un
constructor para uso interno de otros constructores de la clase, pero que no
debe de ser público.
Los constructores estáticos no son constructores para instancias de clase, sino
que se utilizan para inicializar los valores estáticos de una clase. Antes de que se
cree una instancia de esa clase, tenemos asegurado que se ejecutará el método
constructor estático, si está definido. El método constructor estático de una
clase va precedido de la palabra static, no se puede definir visibilidad, y
no puede tener parámetros, ya que no puede ser invocado directamente (se
invoca antes de instanciar la clase por primera vez, y solo entonces).
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 56/80
© FUOC • P08/B0036/01627 56 C#
Actividad 14
Ejecutad las siguientes acciones:
• Añadid a la clase Coche un constructor que acepte la matricula co-
rrespondiente como parámetro.
• Cread una instancia de la claseCoche utilizando el nuevo construc-
tor.
2.5.4. Destructores
En .NET, cuando un objeto deja de ser utilizado (porque se eliminan todas las
referencias a este objeto), no es realmente eliminado de memoria, hasta que el
Garbage Collector realiza una comprobación de memoria destruye los objetos
no usados.
Es en este momento cuando el Garbage Collector llama a los métodos destruc-
tores de los objetos que va a destruir (si es que hay algún método destructor
definido). Esto quiere decir que la llamada a los métodos destructores no es
determinista (no podemos saber a priori cuándo se va a producir).
Por otro lado, la utilización de destructores hace bajar el rendimiento de la
aplicación porque el Garbage Collector realiza dos inspecciones hasta podereliminar un objeto que tiene destructor definido (una para llamar al destructor,
y la otra para destruir el objeto definitivamente).
Por eso, es importante razonar si es necesario o no definir métodos destruc-
tores, y tener en cuenta la característica indeterminista de los mismos. Estos
métodos destructores pueden ser necesarios (y se recomiendan sólo), si el ob-
jeto maneja recursos no administrados (conexiones de bases de datos, objetos
COM, etc., para liberar estos recursos.
Otra forma de liberar recursos no administrados, sin tener que definir destruc-
tores es la de implementar la interfaz IDisposable, y su correspondiente mé-
todo Dispose. Cuando un objeto de la clase deja de ser necesario, se llama al
correspondiente método Dispose, para que libere los recursos que está utili-
zando. De este modo, se dispone de un mecanismo determinista para destruir
el objeto (eso sí, es necesario no olvidarse de llamar al método Dispose).
Dado que se invocan de forma indeterminista, los métodos destructores no
pueden tener parámetros, y sólo hay uno por clase. Para definir el método
destructor de la clase, utilizamos la siguiente sintaxis:
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 57/80
© FUOC • P08/B0036/01627 57 C#
~MyClass ()
{
// instrucciones del destructor
}
2.5.5. Operadores
Como ya hemos comentado, los operadores son una serie de símbolos que
realizan cálculos con los parámetros que reciben y devuelven un resultado.
Estos operadores sólo funcionan generalmente sobre tipos numéricos, carac-
teres, booleanos y cadenas de caracteres. No obstante, es posible redefinir es-
tos operadores en otras clases para definir su comportamiento cuando sus pa-
rámetros no son de los tipos anteriores.
Para redefinir el significado de un operador O cuando sus parámetros son del
tipo de la clase actual, incluimos una definición de operador con la siguiente
sintaxis:
public static tipo_ret operator O(parámetros)
{
// Instrucciones del operador
}
Igual que con los métodos normales, es posible sobrecargar los operadores de
una clase. La diferencia es que los operadores pueden especificar diferentestipos de retorno en función de los parámetros especificados.
Actividad 15
Ejecutad las siguientes acciones:
• Sobrescribid el operador + para la clase Coche.
• Utilizad el operador creado con dos instancias de la clase Coche.
2.5.6. Indexadores
Los indexadores son muy similares a las propiedades; la diferencia es que los
indexadores permiten acceder al objeto como si se tratara de un array, indi-
cando una o más posiciones.
Para definir un indexador, utilizamos la sintaxis siguiente:
visibilidad tipo_ret this [tipo_indice nombre_indice]
{
get { ... }
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 58/80
© FUOC • P08/B0036/01627 58 C#
set { ... }
}
En vez de un índice, podemos especificar más de uno, como en los arrays. Por
ejemplo, en la clase Matriz podemos definir un indexador que nos permita
acceder al valor de una posición concreta, de la siguiente forma:
public int this [int fila, int col]
{
get { return matriz [fila, col]; }
set { matriz [fila, col] = value; }
}
La utilización de un indexador permite acceder a él como si el objeto de la
clase fuese un array, por ejemplo:
Matriz m = new Matriz ();
M[0,0] = 4;
Actividad 16
Ejecutad las siguientes acciones:
• Cread un indexador de tipo booleano en la clase Coche. Cada po-
sición N del indexador debe indicar si el asiento N-essimo está ocu-pado (true) o no (false).
• Consultad una de las posiciones del indexador.
• Cambiad una de las posiciones del indexador.
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 59/80
© FUOC • P08/B0036/01627 59 C#
3. Conceptos avanzados
En este apartado, veremos algunos aspectos más avanzados del lenguaje C#.
3.1. Tratamiento de excepciones
Los programas pueden provocar errores en tiempo de ejecución. Los errores
provocados por un comportamiento incorrecto son difíciles de detectar, ya
que se deben a un error en el código fuente del programa, pero no producen
errores ni al compilar ni al ejecutarse; simplemente, el programa no funciona
bien y devuelve resultados incorrectos.
Existen otros errores que sí que son detectables y no son evitables a priori. Son
errores producidos por circunstancias ajenas al programa, como por ejemplo
un fallo de acceso a un fichero o a una base de datos, un fallo de comunica-
ciones, un desbordamiento de pila, etc.
Los errores o excepciones, se pueden gestionar en C# para que no termine
el programa bruscamente o aparezcan mensajes de error del sistema, y para
intentar solventar el error en la medida de lo posible. Para ello, C# y .NET en
general tratan las excepciones como objetos. El .NET Framework define una
serie de objetos Exception generales, pero como veremos a continuación sepueden definir nuevos objetos excepción por defecto.
Las excepciones se producen en instrucciones concretas, ya sean instrucciones
de nuestro propio programa o de un método que hemos invocado de otra
librería de clases (por ejemplo de la FCL del .NET Framework). Para capturar
una excepción cuando se produzca, utilizamos la instrucción try/catch:
try
{
// instrucciones que potencialmente pueden producir
// excepciones
}
catch
{
// Gestion de las excepciones encontradas
}
finally
{
// instrucciones a ejecutar tanto si se producen
// excepciones como sino
}
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 60/80
© FUOC • P08/B0036/01627 60 C#
Dentro del bloque try se incluyen las instrucciones que pueden producir erro-
res. Si se produce una excepción dentro del bloque, automáticamente el flujo
de ejecución pasa a las instrucciones del bloque catch (el resto de instruccio-
nes del bloque try se ignoran). Si no hay ningún bloque, el programa conti-
núa ejecutándose
En el bloque catch se incluyen las instrucciones necesarias para controlar el
error: informar al usuario, subsanar el error, restaurar el flujo de ejecución del
programa, etc. En principio, el bloque catch sin ningún parámetro se ejecu-
ta para cualquier excepción que se produzca en el bloque try. Si nos intere-
sa discernir entre diferentes excepciones, podemos especificar varios bloques
catch, indicando el tipo de excepción que gestiona cada uno:
try
{
}
catch (Exception1)
{
// gestion de la excepción Exception1
}
catch (Exception2 e)
{
// gestion de la excepción Exception2
// declaramos la variable e para poder acceder a las
// propiedades del error desde este bloque catch
}
...
Cuando se produce una excepción, se revisan los bloques catch secuencial-
mente hasta que se encuentra uno que gestione el tipo de excepción que se ha
producido. Si no se encuentra ninguno, la excepción se propaga, es decir, se
cancela la ejecución del método actual y se devuelve el control del programa
al método que lo llamó. Si este método tampoco gestiona el error, se vuelve
a propagar, y así recursivamente hasta que se llega al método principal. Si el
método principal no gestiona el error, el programa se aborta y provoca una
excepción de sistema. Una vez gestionada una excepción en un bloque catch,
continúa la ejecución del programa en la línea siguiente al bloque try/catch
en el que se gestionó.
Por otro lado, hay que tener en cuenta que el primer bloque catch que se
encuentra es el que se ejecuta; por ejemplo, si tenemos un catch que gestiona
la excepción IOException y otro que gestiona la excepción FileNotFoun-
dException, deberemos colocar el segundo antes que el primero para que
se ejecute si se produce una excepción de tipo FileNotFoundException, ya
que una excepción de este tipo es a la vez del tipo IOException por herencia.
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 61/80
© FUOC • P08/B0036/01627 61 C#
Asimismo, si tenemos un bloque catch sin parámetros, lo deberemos colocar
en último lugar, porque se ejecuta independientemente de la excepción que
se produzca.
El bloque finally contiene instrucciones que se han de ejecutar indepen-
dientemente de si se han producido o no excepciones dentro del bloque
catch. Es un bloque opcional, que se suele utilizar si es necesario liberar algún
recurso como una conexión a una base de datos o cerrar un canal de lectura o
escritura. Si se produce un error y no hay un bloquefinally definido, el flujo
de ejecución sale del método actual, sin ejecutar ninguna de las instrucciones
que hay después de la instrucción try/catch.
Veamos un ejemplo de funcionamiento del bloque try/match:
static void Main()
{
try
{
method();
}
catch (System.MissingFieldException)
{
MessageBox.Show("Hola4");
}
MessageBox.Show("Hola5");
}
public static void method ()
{
try
{
// En esta linea se produce una excepcion
MessageBox.Show("Hola1");
}
catch (System.TimeoutException)
{
MessageBox.Show("Hola2");
}
MessageBox.Show("Hola3");
}
El método principal llama al método method, que lanza una excepción en la
línea indicada. Si la excepción fuese de tipo TimeoutException seria gestio-
nada por el catch del método method y se mostraría el mensaje Hola2 por
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 62/80
© FUOC • P08/B0036/01627 62 C#
pantalla. A continuación, el programa continuaría la ejecución en la siguiente
instrucción después del try/catch, mostrando por pantalla el mensaje Ho-
la3 y posteriormente Hola5 del método principal.
En cambio si la excepción fuese de tipo MissingFieldException, el bloque
try/catch del método method no gestionaría el error y lo propagaría al mé-
todo main. El método main sí que gestiona el error, mostrando el mensaje
Hola4 y posteriormente Hola5.
Por último, si la excepción fuese excepción de otro tipo diferente (tal que no
fuese superclase de ninguno de los dos anteriores), dado que no hay ningún
bloque catch que la gestione en el método method, ésta se propaga al método
principal. Como el método principal tampoco puede gestionar la excepción,
se produce un error en la aplicación y finaliza su ejecución.
3.1.1. Excepciones definidas por el usuario
Como ya hemos dicho, las excepciones en C# se tratan como objetos. El .NET
Framework define una jerarquía de clases de excepción que parten de la clase
Exception como raíz. Todas las clases que heredan de Exception son trata-
das como excepciones (y por lo tanto se pueden utilizar en un bloque try/
catch).
Existen dos subclases de la clase Exception que son las que se deberían uti-
lizar para crear excepciones nuevas: SystemException y ApplicationEx-ception. La primera se utilizaría para definir nuevas excepciones de sistema,
mientras que la segunda es la que se utiliza normalmente para definir una je-
rarquía personalizada de excepciones para una aplicación.
Para crear una excepción nueva, por lo tanto, creamos una clase que herede
de alguna otra excepción, por ejemplo ApplicationException. Las clases
excepción definen una serie de métodos que permiten obtener información
acerca del error que se ha producido. Podemos sobreescribir algunos de estos
métodos para adecuarlos a la nueva excepción que estamos definiendo, entre
otros:
Message. Devuelve un mensaje de error. Este método se suele sobrescribir
siempre para adecuar el mensaje al nuevo tipo de excepción que se está crean-
do.
Source. Devuelve el nombre del programa u objeto que produjo la excepción
StackTrace. Devuelve un string que contiene la pila de ejecución hasta el
punto en el que se produjo el error. Es una información muy útil para encon-
trar bugs o errores dentro del código fuente.
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 63/80
© FUOC • P08/B0036/01627 63 C#
Por último, nos falta ver cómo provocar una excepción. Esto es útil cuando
el programa se encuentra una situación que le impide realizar las acciones
que debe realizar, por ejemplo, el usuario no ha indicado algún valor en un
formulario. En este caso, podemos crear una excepción especial llamada, por
ejemplo FieldCannotBeEmptyException, y lanzar esta excepción cuando
detectemos que nos falta algún valor. La interfaz gráfica capturará este error
y avisará al usuario de que debe de rellenar ese dato. De este modo, evitamos
que la capa de lógica de programa tenga que avisar directamente a la capa
de interfaz (con lo que estaríamos rompiendo la distribución en capas de la
aplicación).
Para lanzar una excepción, utilizamos la instrucción throw, indicando a conti-
nuación un objeto del tipo de la excepción que queramos provocar, por ejem-
plo:
throw new FieldCannotBeEmptyException ("nombre");
Como se puede ver en el ejemplo anterior, los objetos excepción pueden te-
ner parámetros, en este caso pasamos el nombre del campo que está vacío, de
modo que el mensaje producido por la propiedad Message de la excepción
puede ser algo genérico para cualquier campo como: "El campo <nombre
campo> no puede estar vacio". Dependiendo del tamaño de la aplica-
ción se puede decidir si implementar excepciones personalizadas o no. Si la
aplicación es pequeña se puede utilizar directamente la clase Exception o
ApplicationException:
throw new Exception("El campo nombre no puede estar vacio");
throw new ApplicationException("El campo nombre no puede estar
vacio");
La instrucción throw lanza la excepción saliendo automáticamente del méto-
do actual (ignorando el resto de instrucciones), y propagándose desde el mé-
todo actual hacia el método que lo invoco, hasta encontrar algún método que
haya definido un bloque try/catch que gestione la excepción, y en cuyo
bloque try se encuentre la instrucción que provocó la excepción. Si no se en-
cuentra ningún bloque try/catch, la excepción llega al Main. Si no se trata
tampoco en el método principal, ésta llega al CLR, que termina la ejecución
del programa y muestra un mensaje del error que se ha producido al usuario.
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 64/80
© FUOC • P08/B0036/01627 64 C#
Actividad 17
Ejecutad las siguientes acciones:
• Cread la excepción NoHayGasolinaException.
• Cread el método ComprobarNivelGasolina en la clase CocheGa-
solina que lance la excepción NoHayGasolinaException.
• Llamad al método anterior desde el programa principal, capturando
la excepción.
3.2. Delegate y eventos
Un delegate es un tipo de dato especial, que permite utilizar un método como
si fuera un objeto, es decir, permite almacenarlo en una variable, pasarlo como
parámetro de otro método, etc. Para definir un delegate, utilizamos la palabra
clave delegate, y a continuación especificamos la signatura del método co-
rrespondiente:
visibilidad delegate tipo_retorno NombreDelegado (parame-
tros);
La definición anterior crea un nuevo tipo llamado NombreDelegado, que re-presenta un método con la signatura especificada, es decir, que devuelve el
tipo de dato tipo_retorno y tiene los parámetros indicados. De ese modo,
podemos declarar variables del tipo, como si de un tipo de datos cualquiera
se tratase:
NombreDelegado variableDelegado;
Para inicializar una variable de tipo delegate es necesario indicar un método
que tenga la misma signatura que dicho tipo delegate. Una vez inicializada, la
variable representa al método indicado en la inicialización. Por ejemplo:
delegate void UnDelegado(int g);
UnDelegado ud1 = new UnDelegado(Metodo1);
UnDelegado ud2 = new UnDelegado(Metodo2);
public void Metodo1(int k)
{
}
public void Metodo2(double l)
{
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 65/80
© FUOC • P08/B0036/01627 65 C#
}
La inicialización de la variable ud2 produce un error de compilación, porque
el método Metodo2 no cumple con la signatura definida por el delegate. Las
variables de tipo delegate se pueden utilizar para invocar el método asignado
en la inicialización, por ejemplo la instrucción siguiente:
ud1 (4);
Seria equivalente a esta otra:
Metodo1 (4);
Los tipos delegate se pueden utilizar también como parámetros de otros méto-
dos, por ejemplo el método OperarArray recibe un método que opera con
dos enteros (definido por el delegate OperacionInt), y lo aplica a todos los
elementos de dos arrays para formar un tercer array de enteros:
public delegate int OperacionInt(int i1, int i2);
public int [] OperarArray (int[] a1, int[] a2, OperacionInt op)
{
int [] a3 = new int [Math.Min (a1.Length, a2.Length)];
for (int count = 0;count < a1 &&&& count < a2;count++)
{
a3[count] = op(a1[count], a2[count]);
}
return a3;
}
Un ejemplo de utilización del método anterior podría ser mediante el método
SumarInt:
public static int SumarInt(int i1, int i2)
{
return i1 + i2;
}
La llamada al método OperarArray quedaría de la siguiente forma:
OperarArray(a1, a2, SumarInt);
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 66/80
© FUOC • P08/B0036/01627 66 C#
3.2.1. Eventos
Los eventos son circunstancias concretas en las que se producen cambios en el
estado de un objeto, es decir, se modifica el valor de alguna de sus propiedades.
Por ejemplo, la clase Coche puede definir el evento NivelCombustibleBajo,
que se produce cuando el valor de la propiedadNivelCombustible desciende
por debajo de una cierta cantidad.
Algunos objetos pueden estar interesados en saber cuándo se produce un de-
terminado evento en otro objeto, para realizar alguna determinada acción. Si-
guiendo con el ejemplo anterior, un objeto Conductor puede estar interesado
en saber cuándo se produce el evento NivelCombustibleBajo de su coche,
para así ejecutar la acción LlenarDeposito.
Para controlar los eventos de los diferentes objetos y avisar a los objetos inte-
resados cuando éstos se producen, el .NET Framework proporciona un meca-
nismo de gestión de eventos basado en el patrón observador. Este patrón de
diseño define un objeto Sujeto (el objeto que produce el evento), y uno o
más objetos Observador (los objetos interesados en el evento).
Los objetos Observador se registran en el objeto Sujeto para ser avisados en
caso de que se produzca un determinado evento en éste. Un Sujeto puede
producir más de un evento, y un Observador puede estar registrado en uno
o más eventos del Sujeto. Cuando se produce un evento en el Sujeto, éste
avisa a todos los Observadores registrados a ese evento llamando a un mé-todo del Observador que gestiona el evento producido (método de gestión
del evento).
En C# los eventos se definen como variables de un tipo delegate (dentro de la
clase que actúa como Sujeto), mediante la palabra clave event, por ejemplo:
public delegate void NivelCombustibleBajoHandler (int nivel);
public event NivelCombustibleBajoHandler NivelCombustibleBa-
jo;
La definición anterior crea un evento llamado NivelCombustibleBajo. El
delegado NivelCombustibleBajoHandler sobre el que se define este even-
to, indica la signatura que debe tener el método de gestión del evento en los
objetos Observador que se registren en el evento.
Para poder registrarse a un evento es necesario definir un método que gestione
el evento en la clase que actúa como Observador. Este método ha de tener
la misma signatura que el delegate asociado al evento correspondiente, por
ejemplo para el caso del evento NivelCombustibleBajo un posible método
de gestión podría ser:
private void gestionar_NivelCombustibleBajo (int nivel)
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 67/80
© FUOC • P08/B0036/01627 67 C#
{
LlenarDeposito (nivelmax - nivel);
}
Una vez definido el método de gestión del evento, podemos registrar el objeto
observador al evento de una instancia concreta del Sujeto, mediante la
operación +=, por ejemplo:
micoche.NivelCombustibleBajo += new NivelCombustibleBajoHandler
(gestionar_NivelCombustibleBajo);
Finalmente, debemos determinar en qué punto de la clase Sujeto se puede
producir el evento que hemos creado, en el ejemplo del coche el momento
en que puede verse modificado el nivel de combustible es en los métodos de
acceso de la propiedad NivelCombustible. En este punto, deberemos llamar
a los eventos de gestión de los diferentes Observadores registrados. Para ello,
utilizamos el nombre del evento como si fuese un método:
if (NivelCombustible < nivelBajo)
{
NivelCombustibleBajo (NivelCombustible);
}
La anterior instrucción genera, automáticamente, una llamada a cada uno de
los métodos de gestión del evento de todos los observadores registrados, y lespasa como parámetro los datos que se indiquen, en este caso el valor del nivel
de combustible actual. Si no hay ningún observador registrado, se produce
un error por valor nulo, por lo que se recomienda hacer una comprobación
adicional:
if (NivelCombustible < nivelBajo)
{
if (NivelCombustibleBajo !=null)
{
NivelCombustibleBajo (NivelCombustible);
}
}
El mecanismo de eventos es muy útil para pasar información entre capas de
una aplicación, sin necesidad de que las capas inferiores deban conocer direc-
tamente con qué elementos de las capas superiores deben de comunicarse (y
evitar así el acoplamiento entre capas). Un ejemplo claro de esto es el meca-
nismo utilizado por Visual Studio para la comunicación entre los elementos
de la interfaz gráfica de usuario (botones, campos de texto, listas, etc.), y la
capa de lógica de programa, que es donde se implementan los métodos que se
ejecutan cuando se produce un evento en alguno de estos elementos.
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 68/80
© FUOC • P08/B0036/01627 68 C#
Actividad 18
Ejecutad las siguientes acciones:
• Añadid el evento NivelAceiteBajo en la clase Coche. La signatura
del método de gestión del evento debe aceptar un parámetro de tipo
entero (el nivel de aceite), y debe devolver un booleano (true si se
decide continuar, o false si se quiere parar el coche para corregir
el problema).
• Cread una nueva clase Conductor, y cread en ella el método de
gestión del evento.
• Registrad una instancia de Conductor al evento de una instancia
de la clase Coche en el programa principal.
• Cread el método ComprobarNivelAceite en la clase Coche que
desencadene el evento.
• Llamad al método anterior desde el programa principal, y compro-
bad que se gestiona el evento producido.
3.2.2. Métodos anónimos
Los métodos anónimos permiten especificar la implementación del método
que gestiona un evento, sin necesidad de crear un método adicional, simple-
mente especificando las instrucciones a ejecutar directamente al registrarse al
evento, por ejemplo:
micoche.NivelCombustibleBajo += delegate
{
LlenarDeposito ();
};
En este caso, hemos ignorado el parámetro int de la definición del delegate,
pero también se puede añadir si se necesita ese parámetro en las instrucciones
del método:
micoche.NivelCombustibleBajo += delegate (int nivel)
{
LlenarDeposito (nivelmax - nivel);
};
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 69/80
© FUOC • P08/B0036/01627 69 C#
3.3. Atributos
Los atributos son tags adicionales que se añaden a la definición de un elemen-
to del programa, modificando su comportamiento, provocando alguna acción
especial en el compilador sobre ese elemento o proporcionando información
adicional sobre el elemento que se pueda consultar a posteriori con las utilida-
des de Reflection del .NET Framework. Para aplicar un atributo a un elemento
del programa, añadimos la siguiente instrucción en la línea anterior a la defi-
nición del elemento:
[nombreAtributo( parametros)]
El .NET Framework define varios atributos con diferentes funcionalidades. En
la siguiente tabla se muestran los más comunes:
Atributo Aplicable�a Descripción
Conditional métodos Si el símbolo indicado como parámetro está definidoen tiempo de ejecución, el método se ejecuta normal-mente; si no, no se ejecuta.
DllImport métodos Indica que el método esta implementado en una libre-ría de código no administrado. El nombre de la libreríase pasa como parámetro.
DefaultPro-
perty
clases Especifica la propiedad por defecto del componente
DefaultVa-lue
propiedades Indica que la propiedad es el valor por defecto de uncomponente
Description propiedades, even-tos
Permite indicar una descripción del elemento, que apa-recerá en la ventana de propiedades del diseñador de Visual Studio
A continuación, veremos algunos ejemplos de utilización de estos atributos:
• Conditional
[Conditional ("DEBUGGING")]
public static void Metodo()
{
...
}
El método anterior sólo se ejecutará si el símbolo DEBUGGING está defi-
nido. Para definir un símbolo, debemos utilizar la siguiente instrucción
del preprocesador5
de C#:
#define DEBUGGING
• DllImport
[DllImport("libreria.dll", EntryPoint="Metodo")]
(5)Las instrucciones del preproce-
sador se tratan antes de compilar el código fuente. Estas instruccio-nes permiten modificar el compor-tamiento del compilador a la ho-ra de tratar el código fuente. Paramás información sobre las instruc-ciones del preprocesador, consul-
tar la ayuda de Visual Studio
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 70/80
© FUOC • P08/B0036/01627 70 C#
public static extern int Metodo(parametros);
La anterior definición de método indica que la implementación del mismo
se encuentra en la librería libreria.dll y se corresponde con el método
de nombre método (EntryPoint).
• DefaultProperty
[DefaultProperty("Property")]
public class Class1
Permite definir la propiedad por defecto de una clase.
• DefaultValue y Description
[DefaultValue(0)]
[Description("Esta propiedad sirve para ...")]
public int Property
{
get { return prop; }
set { prop = value; }
}
El atributo DefaultValue permite definir el valor por defecto del elemen-
to; en este caso, el valor 0. El atributo Description permite especificar
una descripción de la propiedad. En los componentes visuales, es la des-
cripción que aparece en la ventana de propiedades del diseñador de Visual
Studio.
3.3.1. Atributos personalizados
Aparte de los atributos definidos en el .NET Framework, podemos crear otros
atributos personalizados que almacenen cierta información acerca de un ele-
mento del programa.
Para definir un nuevo atributo, deberemos crear una clase que herede de la
clase System.Atribute, o de alguna clase derivada de la anterior. Además,
deberemos indicar mediante un atributo especial llamado AttributeUsage,
los elementos a los cuales se aplicará el atributo que estamos definiendo:
[AttributeUsage(<i>elementos_aplicables</i>)]
public class UnAtributo: System.Attribute
{ ... }
Los elementos aplicables se especifican mediante la enumeraciónAttribute-
Targets, que está compuesta por los siguientes valores, que corresponden con
los diferentes elementos sobre los que se puede aplicar un atributo: Class,
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 71/80
© FUOC • P08/B0036/01627 71 C#
Constructor, Delegate, Enum, Event, Field, Interface, Method,
Module, Parameter, Property, ReturnValue, Struct, Assembly,
All (para cualquier elemento).
Por ejemplo, para especificar que el atributo se aplicará sólo a métodos, utiliza-
remos el elemento AttributeTargets.Method de la enumeración. Si el atri-
buto se aplica sobre más de un elemento, debemos especificar los elementos de
la enumeración que correspondan, separados por el símbolo '|', por ejemplo:
AttributeTargets.Method | AttributeTargets.Property, indica que
el atributo se puede aplicar a métodos o a propiedades.
Veamos un ejemplo de atributo personalizado que permite almacenar infor-
mación acerca de los cambios que se han producido en el método. En concre-
to, almacenaremos los desarrolladores que han modificado el método y en qué
fechas. Para ello, creamos una clase que herede de la clase Attribute:
[AttributeUsage(AttributeTargets.Method, AllowMultiple=true)]
public class InfoDesarrolloAttribute: System.Attribute
{
string desarrollador;
string fecha;
public InfoDesarrolloAttribute (string d, string f)
{
desarrollador = d;
fecha = f;
}
public string Desarrollador
{
get { return desarrollador; }
set { desarrollador = value; }
}
public string Fecha
{
get { return fecha; }
set { fecha = value; }
}
}
El parámetro adicional AllowMultiple=true indica que puede ser que un mé-
todo tenga más de un atributo de este tipo (ya que un mismo método puede
ser modificado varias veces o por varios desarrolladores). Una vez definido el
atributo, podemos utilizarlo como los atributos predefinidos:
[InfoDesarrolloAttribute("David", "10-01-2005")]
[InfoDesarrolloAttribute("Juan", "12-01-2005")]
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 72/80
© FUOC • P08/B0036/01627 72 C#
[InfoDesarrolloAttribute("David", "02-02-2005")]
public void método ()
{ ... }
La información proporcionada en los atributos se añade en los metadatos del
ensamblado en el que se encuentra el elemento en el cual se definen los atri-
butos. Esta información se puede consultar en tiempo de ejecución mediante
el mecanismo de Reflection. No trataremos Reflection directamente, pe-
ro veremos a continuación un ejemplo de cómo acceder a la información que
hemos añadido mediante el atributo InfoDesarrolloAttribute:
// recuperamos la información de todos los miembros de
// la clase que queramos inspeccionar (en este caso
// Class1)
System.Reflection.MemberInfo[] memberInfoArray ;
memberInfoArray = typeof(Class1).GetMembers();
// para cada elemento MemberInfo...
foreach (MemberInfo mi in memberInfoArray)
{
// recuperamos los atributos del tipo
// InfoDesarrolloAttribute existentes
// y para cada uno de ellos...
foreach (InfoDesarrolloAttribute ida in
mi.GetCustomAttributes (typeof(InfoDesarrolloAttribute), false))
{
// mostramos por pantalla el nombre del método, el nombre
// del desarrollador, y la fecha de modificación
Console.WriteLine (mi.Name + " " + ida.Desarrollador + " " + ida.Fecha);
}
}
}
El trozo de código anterior aplicado a la clase que contiene el método en el
que hemos definido los atributos, genera el siguiente resultado por pantalla:
método David 10-01-2005
método Juan 12-01-2005
método David 02-02-2005
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 73/80
© FUOC • P08/B0036/01627 73 C#
4. Novedades de C# 3.0
La versión actual del lenguaje C# es la 2.0. La nueva versión 3.0 vendrá inclui-
da dentro del .NET Framework 3.5, junto con la aparición de Visual Studio
2008. En este apartado, veremos algunas de las novedades más importantes
que incorpora esta nueva versión.
4.1. Variables locales de tipo implícito
La nueva versión de C# permite declarar variables locales mediante la palabra
clave var, de forma que no es necesario especificar el tipo de datos de la mis-
ma; se infiere automáticamente de la expresión que se utiliza para inicializar
la variable. Por ejemplo, en las siguientes inicializaciones la variable i será de
tipo entero, y la variable b de tipo booleano:
var i = 1;
var b = b1 || b2;
La palabra clave var se puede utilizar también con variables de tipo array, por
ejemplo la siguiente instrucción crea una variable a de tipo array de caracteres:
var a = new[] { 'a', 'b', 'c' };
Una vez inicializada una variable de tipo implícito, no es posible modificar
su tipo de dato. Por otro lado, es necesario inicializar la variable en la misma
línea en la que se declara (no se puede declarar e inicializar por separado), y
no puede inicializarse al valor null.
4.2. Tipos anónimos
Gracias a la característica de tipos anónimos de la nueva versión de C#, es
posible crear objetos, sin necesidad de declarar una clase que defina su estruc-
tura. Por ejemplo, la siguiente instrucción crea un objeto con dos campos de
tipo entero:
var c = new { x = 4, y = 5 };
Como se puede comprobar en el ejemplo, se utiliza una variable de tipo im-
plícito para crear la instancia de tipo anónimo, ya que no existe ninguna clase
definida para dicha instancia. En realidad, lo que hace el compilador es crear
una clase automáticamente a partir de los datos proporcionados en la inicia-
lización; en el caso anterior, la clase creada tendría una estructura equivalente
a la siguiente:
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 74/80
© FUOC • P08/B0036/01627 74 C#
public class __Anonymous1
{
private int x;
private int y;
public int X { get { return x; } set { x = value; }}
public int Y { get { return y; } set { y = value; }}
}
Como se puede comprobar, el compilador crea automáticamente métodos de
acceso para los campos del tipo anónimo. Por otro lado, si se detectan dos o
más tipos anónimos con la misma estructura, el compilador reutiliza la misma
clase generada automáticamente, de forma que todos ellos son compatibles
entre sí.
4.3. Métodos de extensión
Los métodos de extensión permiten extender tipos de datos ya existentes con
métodos estáticos adicionales. Estos métodos sólo se pueden definir dentro
de clases estáticas, y se identifican por la palabra clave this que precede al
primer parámetro del método. El siguiente ejemplo añade el método estático
Sumar al tipo de dato int:
public static int Sumar(this int i1, int i2)
{
return i1 + i2;
}
De esta forma, se extiende la clase Int32 con el método Sumar, que se pue-
de utilizar como cualquiera de sus métodos por defecto (siempre y cuando la
clase en la que está definida el método Sumar se haya importado mediante la
correspondiente instrucción using), por ejemplo:
int i = 5;
i = i.Sumar(4);
4.4. Inicializadores de objetos
Otra de las novedades de la nueva versión de C# permite inicializar los campos
de un objeto al inicializarlo, sin necesidad de crear un constructor explícito
en la clase correspondiente. La sintaxis es parecida a la de la creación de los
tipos anónimos, aunque en este caso sí que indicamos el nombre de la clase
a la cual pertenece el objeto:
Coordenada c = new Coordenada { x = 4, y = 5 };
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 75/80
© FUOC • P08/B0036/01627 75 C#
Es importante remarcar que la clase Coordenada no tiene porque tener nin-
gún constructor definido (a parte del constructor por defecto).
4.5. Expresiones lambda
Las expresiones lambda provienen del paradigma de programación funcional.
Dicho paradigma se basa en cálculos funcionales llamados "Lambda calculus".
Las expresiones lambda en la nueva versión de C# se pueden utilizar en vez
de los delegate y métodos anónimos para reducir el tamaño del código fuente.
Por ejemplo, el siguiente fragmento de código que veíamos en el subapartado
de eventos:
micoche.NivelCombustibleBajo += new NivelCombustibleBajo (gestionar_NivelCombustibleBajo);
private void gestionar_NivelCombustibleBajo (int nivel)
{
LlenarDeposito ();
}
Se puede sustituir por el siguiente, utilizando métodos anónimos:
micoche.NivelCombustibleBajo += delegate
{
LlenarDeposito ();
};
Con expresiones lambda, el código equivalente sería mucho más simple:
micoche.NivelCombustibleBajo += (int nivel) => LlenarDeposito
(nivel);
La sintaxis general de una expresión lambda es la siguiente:
(parametros) => <expresión o bloque de código entre {}>
Las expresiones lambda también se pueden asignar a una variable o pasarse
como parámetro de otro método:
Func<int> f = () => { return 0; };
l.Select (c => c.Length < 4);
El tipo de dato genérico Func utilizado en la primera instrucción permite crear
variables que contengan expresiones lambda. En la segunda instrucción, la
variable l es una lista de string. La expresión lambda que se pasa como pa-
rámetro del método Select devuelve cierto si la longitud del string es in-
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 76/80
© FUOC • P08/B0036/01627 76 C#
ferior a 4. El método Select aplica dicha expresión lambda a cada uno de
los elementos de la lista, de forma que se devuelven todos los string con
longitud inferior a 4.
4.6. Expresiones de consulta
Una de las mejoras más importantes de la nueva versión del lenguaje C# es la
sintaxis de LINQ (Language Integration Query). El siguiente ejemplo muestra
un avance de lo que esta nueva característica permite realizar:
var cochesAzules =
from c in coches
where c.color == "azul"
select (c);
En el ejemplo anterior coches es una colección que contiene objetos de tipo
Coche. El resultado instrucción anterior almacena en la variable cochesAzu-
les todos los coches de la lista coches que tienen color azul.
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 77/80
© FUOC • P08/B0036/01627 77 C#
Actividades
1.�Ejecutad�las�siguientes�acciones:
• Cread la enumeración Meses que represente los meses del año.
• Declarad una variable de tipo Meses y asignadle como valor el mes Agosto.
2.�Ejecutad�las�siguientes�acciones:• Escribid la estructura Producto con los siguientes atributos: nombre, precio, categoría.
• Cread un Producto con los siguientes valores: "Tomate", 1.5, "Verdura".
• Copiad el Producto "Tomate" en el Producto "Zanahoria", y modificad el nombre y elprecio a "2".
3.�Ejecutad�las�siguientes�acciones:
• Cread una cadena que contenga la frase "La lluvia en Sevilla es una maravilla".
• Buscad la primera y la última posición en que aparece la subcadena "ll", y almacenadlasen dos variables de tipo entero.
• Obtened la subcadena que va desde la primera hasta la última posición en la que aparecela subcadena "ll" (ambas posiciones incluidas). ¿Cuál es la subcadena resultado?
• Pasad la cadena del apartado anterior a mayúsculas.
4.�Ejecutad�las�siguientes�acciones:
• Declarad un array de tipo Producto.• Cread e inicializad los elementos de un array de 4 posiciones de tipo Meses en una misma
línea.
• Cread una matriz de enteros de 4 X 4 e inicializad los valores de cada posición, uno auno, con los números del 1 al 16.
5.�Ejecutad�las�siguientes�acciones:
• Cread el tipo genérico Par con dos elementos de tipos diferentes. Cread diferentes ins-tancias de dicho tipo, con diferentes tipos de datos para cada elemento.
• Cread el método genérico Comparar que compare los elementos de dos arrays de tipogenérico y devuelva un array de enteros con los resultados de cada comparación.
6.�Ejecutad�las�siguientes�acciones:
• Escribid una instrucción condicional que en función del precio de un Producto p, es-criba por pantalla "Barato" (de 0 a 1 €), "Normal" (de 1 a 4), o caro (más de 4). En caso
de que sea negativo, escribid por pantalla "Error".• Escribid una instrucción condicional que en función del valor de una variable m de tipo
Meses muestra por pantalla el nombre del mes correspondiente.
7.�Ejecutad�las�siguientes�acciones:
• Cread un bucle que realice la suma de dos matrices de enteros a1 y a2. Sabemos que lasdos matrices tienen el mismo número de filas y de columnas, pero no sabemos cuántasexactamente. El resultado debe almacenarse en una tercera matriz a, que hay que declarare inicializar.
• Inicializad una variable i a 0, mostrad por pantalla el valor de i e incrementad i en uno,mientras i sea menor que N (N>=0). Mostrad todas las formas posibles de implementareste bucle.
• Buscad todas las posiciones en las que se encuentra la subcadena "ll" dentro de la cadena"La lluvia en Sevilla es una maravilla", y mostradlas por pantalla. No se puedemodificar la cadena, ni utilizar variables string auxiliares. Mostrad todas las formasposibles de implementar este bucle.
8.�Ejecutad�las�siguientes�acciones:
• Cread la clase Coche.
9.�Ejecutad�las�siguientes�acciones:
• Cread una instancia de la clase Coche.
10.�Ejecutad�las�siguientes�acciones:
• Cread la subclase CocheGasolina de la clase Coche.
• Cread una interfaz llamada Vehiculo.
• Haced que la clase Coche herede de la interfaz Vehiculo.
11.�Ejecutad�las�siguientes�acciones:
•Añadid el campo
caballosde tipo entero a la clase
Cochey el campo carburante detipo string a la clase CocheGasolina.
• Comprobad que no se pueden añadir campos o constantes a la interfaz Vehiculo.
• Haced que el campo caballos de la clase Coche sea accesible desde la clase CocheGa-
solina.
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 78/80
© FUOC • P08/B0036/01627 78 C#
• Comprobad que se puede acceder al campo caballos desde CocheGasolina, pero nose puede acceder al campo carburante desde la clase Coche.
12.�Ejecutad�las�siguientes�acciones:
• Cread el método MarchaAtras en la clase Coche con el parámetro metros de tipo en-tero, que devuelva un booleano.
• Declarad el método anterior en la interfaz Vehiculo.
• Cread el método estático ActualizarCoches en la clase Coche sin valor de retorno, conel parámetro fichero de tipo string.
• Llamad a los métodos anteriores desde el programa principal.
13.�Ejecutad�las�siguientes�acciones:
• Añadid la propiedad Matricula de tipo string a la clase Coche.
• Cread una instancia de Coche y asignadle un valor a la propiedad anterior.
• Asignad el valor de la propiedad anterior a una variable.
14.�Ejecutad�las�siguientes�acciones:
• Añadid a la clase Coche un constructor que acepte la matricula correspondiente comoparámetro.
• Cread una instancia de la clase Coche utilizando el nuevo constructor.
15.�Ejecutad�las�siguientes�acciones:
•Sobrescribid el operador + para la clase Coche.
• Utilizad el operador creado con dos instancias de la clase Coche.
16.�Ejecutad�las�siguientes�acciones:
• Cread un indexador de tipo booleano en la clase Coche Cada posición N del indexadordebe indicar si el asiento N-essimo está ocupado (true) o no (false).
• Consultad una de las posiciones del indexador.
• Cambiad una de las posiciones del indexador.
17.�Ejecutad�las�siguientes�acciones:
• Cread la excepción NoHayGasolinaException .
• Cread el método ComprobarNivelGasolina en la clase CocheGasolina que lance laexcepción NoHayGasolinaException .
• Llamad al método anterior desde el programa principal, capturando la excepción.
18.�Ejecutad�las�siguientes�acciones:• Añadid el evento NivelAceiteBajo en la clase Coche. La signatura del método de ges-
tión del evento debe aceptar un parámetro de tipo entero (el nivel de aceite), y debedevolver un booleano (true si se decide continuar, o false si se quiere parar el cochepara corregir el problema).
• Cread una nueva clase Conductor, y cread en ella el método de gestión del evento.
• Registrad una instancia de Conductor al evento de una instancia de la clase Coche enel programa principal.
• Cread el método ComprobarNivelAceite en la clase Coche que desencadene el evento.
• Llamad al método anterior desde el programa principal, y comprobad que se gestionael evento producido.
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 79/80
© FUOC • P08/B0036/01627 79 C#
Bibliografía
Nagel, C.; Evjen, B.; Glynn, J. y otros (2005). Professional C# with 3.0. Wrox.
Microsoft Visual C# Delevoper Center . http://msdn.microsoft.com/vcsharp/
5/11/2018 4-C# - slidepdf.com
http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 80/80