• No se han encontrado resultados

Muchas veces interesa dar como valores de los literales de una enumeración únicamente valores que sean potencias de dos, pues ello permite que mediante operaciones de bits &

y|se puede tratar los objetos del tipo enumerado como si almacenasen simultáneamente

varios literales de su tipo. A este tipo de enumeraciones las llamaremos enumeraciones

de flags, y un ejemplo de ellas es el siguiente:

enum ModificadorArchivo { Lectura = 1, Escritura = 2, Oculto = 4, Sistema = 8 }

Si queremos crear un objeto de este tipo que represente los modificadores de un archivo de lectura-escritura podríamos hacer:

ModificadorArchivo obj = ModificadorArchivo.Lectura | ModificadorArchivo.Escritura El valor del tipo base de la enumeración que se habrá almacenado en obj es 3, que es el resultado de hacer la operación OR entre los bits de los valores de los literales Lectura y Escritura. Al ser los literales de ModificadorArchivo potencias de dos sólo tendrán un único bit a 1 y dicho bit será diferente en cada uno de ellos, por lo que la única forma de generar un 3 (últimos dos bits a 1) combinando literales de ModificadorArchivo es combinando los literales Lectura (último bit a 1) y Escritura (penúltimo bit a 1) Por tanto, el valor de obj identificará unívocamente la combinación de dichos literales.

Debido a esta combinabilidad no se debe determinar el valor literal de los objetos ModificadorArchivo tal y como si sólo pudiesen almacenar un único literal, pues su valor numérico no tendría porqué corresponderse con el de ningún literal de la enumeración Por ejemplo:

bool permisoLectura = (obj == ModificadorArchivo.Lectura); // Almacena false

Aunque los permisos representados por obj incluían permiso de lectura, se devuelve

false porque el valor numérico de obj es 3 y el del ModificadorArchivo.Lectura es 1. Si lo

que queremos es comprobar si obj contiene permiso de lectura, entonces habrá que usar el operador de bits &para aislarlo del resto de literales combinados que contiene:

bool permisoLectura = (ModificadorArchivo.Lectura ==

(obj & ModificadorArchivo.Lectura)); // Almacena true O, lo que es lo mismo:

bool permisoLectura = ( (obj & ModificadorArchivo.Lectura) != 0); // Almacena true Asímismo, si directamente se intenta mostrar por pantalla el valor de un objeto de una enumeración que almacene un valor que sea combinación de literales, no se obtendrá el resultado esperado (nombre del literal correspondiente a su valor) Por ejemplo, dado:

Console.Write(obj); // Muestra 3

Se mostrará un 3 por pantalla ya que en realidad ningún literal de ModificadorArchivo tiene asociado dicho valor. Como lo natural sería que se desease obtener un mensaje de la forma Lectura, Escritura, los métodos ToString() y Format() de las enumeraciones ya vistos admiten un cuarto valor "F" para su parámetro formato (su nombre viene de flags) con el que se consigue lo anterior. Por tanto:

Console.Write(obj.ToString("F")); // Muestra Lectura, Escritura

Esto se debe a que cuando Format() detecta este indicador (ToString() también, pues para generar la cadena llama internamente a Format()) y el literal almacenado en el objeto no

se corresponde con ninguno de los de su tipo enumerado, entonces lo que hace es mirar uno por uno los bits a uno del valor numérico asociado de dicho literal y añadirle a la cadena a devolver el nombre de cada literal de la enumeración cuyo valor asociado sólo tenga ese bit a uno, usándo como separador entre nombres un carácter de coma.

Nótese que nada obliga a que los literales del tipo enumerado tengan porqué haberse definido como potencias de dos, aunque es lo más conveniente para que "F" sea útil, pues si la enumeración tuviese algún literal con el valor del objeto de tipo enumerado no se realizaría el proceso anterior y se devolvería sólo el nombre de ese literal.

Por otro lado, si alguno de los bits a 1 del valor numérico del objeto no tuviese el correspondiente literal con sólo ese bit a 1 en la enumeración no se realizaría tampoco el proceso anterior y se devolvería una cadena con dicho valor numérico.

Una posibilidad más cómoda para obtener el mismo efecto que con "F" es marcar la definición de la enumeración con el atributo Flags, con lo que ni siquiera sería necesario indicar formato al llamar a ToString() O sea, si se define ModificadorArchivo así:

[Flags]

enum ModificadorArchivo {

Lectura = 1, Escritura = 2,

Oculto = 4, Sistema = 8 }

Entonces la siguiente llamada producirá como salida Lectura, Escritura: Console.Write(obj); // Muestra Lectura, Escritura

Esto se debe a que en ausencia del modificador "F", Format() mira dentro de los metadatos del tipo enumerado al que pertenece el valor numérico a mostrar si éste dispone del atributo Flags. Si es así funciona como si se le hubiese pasado "F".

También cabe destacar que, para crear objetos de enumeraciones cuyo valor sea una combinación de valores de literales de su tipo enumerado, el método método Parse() de Enum permite que la cadena que se le especifica como segundo parámetro cuente con múltiples literales separados por comas. Por ejemplo, un objeto ModificadorArchivo que represente modificadores de lectura y ocultación puede crearse con:

ModificadorArchivo obj =

(ModificadorArchivo) Enum.Parse(typeof(ModificadorArchivo),"Lectura,Oculto")); Hay que señalar que esta capacidad de crear objetos de enumeraciones cuyo valor almacenado sea una combinación de los literales definidos en dicha enumeración es totalmente independiente de si al definirla se utilizó el atributo Flags o no.

TEMA 15: Interfaces

Concepto de interfaz

Una interfaz es la definición de un conjunto de métodos para los que no se da implementación, sino que se les define de manera similar a como se definen los métodos abstractos. Es más, una interfaz puede verse como una forma especial de definir clases abstratas que tan sólo contengan miembros abstractos.

Como las clases abstractas, las interfaces son tipos referencia, no puede crearse objetos de ellas sino sólo de tipos que deriven de ellas, y participan del polimorfismo. Sin embargo, también tienen numerosas diferencias con éstas:

Es posible definir tipos que deriven de más de una interfaz. Esto se debe a

que los problemas que se podrían presentar al crear tipos que hereden de varios padres se deben a la difícil resolución de los conflictos derivados de la herencia de varias implementaciones diferentes de un mismo método. Sin embargo, como con las interfaces esto nunca podrá ocurrir en tanto que no incluyen código, se permite la herencia múltiple de las mismas.

• Las estructuras no pueden heredar de clases pero sí de interfaces, y las interfaces no pueden derivar de clases, pero sí de otras interfaces.

• Todo tipo que derive de una interfaz ha de dar una implementación de todos los miembros que hereda de esta, y no como ocurre con las clases abstractas donde es posible no darla si se define como abstracta también la clase hija. De esta manera queda definido un contrato en la clase que la hereda que va a permitir poder usarla con seguridad en situaciones polimórficas: toda clase que herede una interfaz implementará todos los métodos de la misma. Por esta razón se suele denominar implementar una interfaz al hecho de heredar de ella.

Nótese que debido a esto, no suele convenir amplicar interfaces ya definidas e implementadas, puesto que cualquier añadido invalidará sus implementaciones hasta que se defina en las mismas un implementación para dicho añadido. Sin embargo, si se hereda de una clase abstracta este problema no se tendrá siempre que el miembro añadido a la clase abstracta no sea abstracto.

• Las interfaces sólo pueden tener como miembros métodos normales, eventos, propiedades e indizadores; pero no pueden incluir definiciones de campos, operadores, constructores, destructores o miembros estáticos. Además, todos los miembros de las interfaces son implícitamente públicos y no se les puede dar ningún modificador de acceso (ni siquiera public, pues se supone)