4-C#

80
 C#  David Gañán Jiménez  P08/B0036/01627

Transcript of 4-C#

Page 1: 4-C#

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

Page 2: 4-C#

5/11/2018 4-C# - slidepdf.com

http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 2/80

Page 3: 4-C#

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

 

Page 4: 4-C#

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

Page 5: 4-C#

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.

Page 6: 4-C#

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#.

Page 7: 4-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

{ ... }

Page 8: 4-C#

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

Page 9: 4-C#

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).

Page 10: 4-C#

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:

Page 11: 4-C#

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>

Page 12: 4-C#

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;

Page 13: 4-C#

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.

Page 14: 4-C#

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

Page 15: 4-C#

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 (&lt;i&gt;string&lt;/i&gt;).

// 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:

Page 16: 4-C#

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.

Page 17: 4-C#

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

Page 18: 4-C#

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

Page 19: 4-C#

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:

Page 20: 4-C#

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

Page 21: 4-C#

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:

Page 22: 4-C#

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

}

Page 23: 4-C#

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:

Page 24: 4-C#

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

Page 25: 4-C#

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.

Page 26: 4-C#

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:

Page 27: 4-C#

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

Page 28: 4-C#

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; }

Page 29: 4-C#

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; }

}

Page 30: 4-C#

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):

Page 31: 4-C#

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.

Page 32: 4-C#

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

Page 33: 4-C#

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.

Page 34: 4-C#

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.

Page 35: 4-C#

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++)

{

Page 36: 4-C#

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)

{

Page 37: 4-C#

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:

Page 38: 4-C#

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>

Page 39: 4-C#

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;

Page 40: 4-C#

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.

Page 41: 4-C#

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.

Page 42: 4-C#

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;

Page 43: 4-C#

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

Page 44: 4-C#

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

{

}

Page 45: 4-C#

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.

Page 46: 4-C#

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

Page 47: 4-C#

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.

Page 48: 4-C#

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.

Page 49: 4-C#

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-

Page 50: 4-C#

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.

Page 51: 4-C#

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-

Page 52: 4-C#

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 ()

{

...

}

Page 53: 4-C#

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

Page 54: 4-C#

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);

Page 55: 4-C#

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).

Page 56: 4-C#

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:

Page 57: 4-C#

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 { ... }

Page 58: 4-C#

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.

Page 59: 4-C#

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

}

Page 60: 4-C#

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.

Page 61: 4-C#

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

Page 62: 4-C#

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.

Page 63: 4-C#

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.

Page 64: 4-C#

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)

{

Page 65: 4-C#

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);

Page 66: 4-C#

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)

Page 67: 4-C#

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.

Page 68: 4-C#

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);

};

Page 69: 4-C#

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

Page 70: 4-C#

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(&lt;i&gt;elementos_aplicables&lt;/i&gt;)]

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,

Page 71: 4-C#

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")]

Page 72: 4-C#

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

Page 73: 4-C#

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:

Page 74: 4-C#

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 };

Page 75: 4-C#

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-

Page 76: 4-C#

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.

Page 77: 4-C#

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.

Page 78: 4-C#

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.

Page 79: 4-C#

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/

Page 80: 4-C#

5/11/2018 4-C# - slidepdf.com

http://slidepdf.com/reader/full/4-c5571fdb3497959916999ba40 80/80