Ya hemos visto un ejemplo de cómo definir una enumeración. Sin embargo, la sintaxis completa que se puede usar para definirlas es:
enum <nombreEnumeración> : <tipoBase> {
<literales> }
En realidad una enumeración es un tipo especial de estructura (luego System.ValueType será tipo padre de ella) que sólo puede tener como miembros campos públicos constantes y estáticos. Esos campos se indican en <literales>, y como sus modificadores son siempre los mismos no hay que especificarlos (de hecho, es erróneo hacerlo)
El tipo por defecto de las constantes que forman una enumeración es int, aunque puede dárseles cualquier otro tipo básico entero (byte, sbyte, short, ushort, uint, int, long o ulong) indicándolo en <tipoBase>
Si no se especifica valor inicial para cada constante, el compilador les dará por defecto valores que empiecen desde 0 y se incrementen en una unidad para cada constante según su orden de aparición en la definición de la enumeración. Así, el ejemplo del principio del tema es equivalente ha:
enum Tamaño:int { Pequeño = 0, Mediano = 1, Grande = 2 }
Es posible alterar los valores iniciales de cada constante indicándolos explícitamente como en el código recién mostrado. Otra posibilidad es alterar el valor base a partir del cual se va calculando el valor de las siguientes constantes como en este otro ejemplo: enum Tamaño { Pequeño, Mediano = 5, Grande }
En este último ejemplo el valor asociado a Pequeño será 0, el asociado a Mediano será 5, y el asociado a Grande será 6 ya que como no se le indica explícitamente ningún otro se considera que este valor es el de la constante anterior más 1.
Obviamente, el nombre que se de a cada constante ha de ser diferente al de las demás de su misma enumeración y el valor que se de a cada una ha de estar incluido en el rango de valores admitidos por su tipo base. Sin embargo, nada obliga a que el valor que se de a cada constante tenga que ser diferente al de las demás, y de hecho puede especificarse el valor de una constante en función del valor de otra como muestra este ejemplo:
enum Tamaño {
Pequeño,
Mediano = Pequeño, Grande = Pequeño + Mediano }
En realidad, lo único que importa es que el valor que se dé a cada literal, si es que se le da alguno explícitamente, sea una expresión constante cuyo resultado se encuentre en el rango admitido por el tipo base de la enumeración y no provoque definiciones circulares. Por ejemplo, la siguiente definición de enumeración es incorrecta ya que en ella los literales Pequeño y Mediano se han definido circularmente:
enum TamañoMal {
El lenguaje de programación C# Tema 14: Enumeraciones
Mediano = Pequeño, Grande
}
Nótese que la siguiente definición de enumeración también sería incorrecta ya que en ella el valor de B depende del de A implícitamente (sería el de A más 1):
enum EnumMal { A = B, B }
Uso de enumeraciones
Las variables de tipos enumerados se definen como cualquier otra variable (sintaxis <nombreTipo> <nombreVariable>) Por ejemplo:
Tamaño t;
El valor por defecto para un objeto de una enumeración es 0, que puede o no corresponderse con alguno de los literales definidos para ésta. Así, si la t del ejemplo fuese un campo su valor sería Tamaño.Pequeño. También puede dársele otro valor al definirla, como muestra el siguiente ejemplo donde se le da el valor Tamaño.Grande:
Tamaño t = Tamaño.Grande; // Ahora t vale Tamaño.Grande
Nótese que a la hora de hacer referencia a los literales de una enumeración se usa la sintaxis <nombreEnumeración>.<nombreLiteral>, como es lógico si tenemos en cuenta que en realidad los literales de una enumeración son constantes publicas y estáticas, pues es la sintaxis que se usa para acceder a ese tipo de miembros. El único sitio donde no es necesario preceder el nombre del literal de <nombreEnumeración>. es en la propia definición de la enumeración, como también ocurre con cualquier constante estática. En realidad los literales de una enumeración son constantes de tipos enteros y las variables de tipo enumerado son variables del tipo entero base de la enumeración. Por eso es posible almacenar valores de enumeraciones en variables de tipos enteros y valores de tipos enteros en variables de enumeraciones. Por ejemplo:
int i = Tamaño.Pequeño; // Ahora i vale 0
Tamaño t = (Tamaño) 0; //Ahora t vale Tamaño.Pequeño (=0)
t = (Tamaño) 100; // Ahora t vale 100, que no se corresponde con ningún literal Como se ve en el último ejemplo, también es posible darle a una enumeración valores enteros que no se correspondan con ninguno de sus literales.
Dado que los valores de una enumeración son enteros, es posible aplicarles muchos de las operaciones que se pueden aplicar a los mismos: ==,!=,<, >,<=,>=,+,-, ^,&,|,~, ++, -- y sizeof. Sin embargo, hay que concretar que los operadores binarios + y – no pueden aplicarse entre dos operandos de enumeraciones, sino que al menos uno de ellos ha de ser un tipo entero; y que |, & y ^ sólo pueden aplicarse entre enumeraciones.
La clase System.Enum
Todos los tipos enumerados derivan de System.Enum, que deriva de System.ValueType y ésta a su vez deriva de la clase primigenia System.Object. Aparte de los métodos heredados de estas clases padres y ya estudiados, toda enumeración también dispone de otros métodos heredados de System.Enum, siendo los principales de ellos:
• static Type getUnderlyingType(Type enum): Devuelve un objeto System.Type
con información sobre el tipo base de la enumeración representada por el objeto System.Type que se le pasa como parámetro13.
• static object[] GetValues(Type enum): Devuelve una tabla con los valores de
todos los literales de la enumeración representada por el objeto System.Type que se le pasa como parámetro. Por ejemplo:
object[] tabla = Enum.GetValues(typeof(Tamaño));
Console.WriteLine(tabla[0]); // Muestra 0, pues Pequeño = 0 Console.WriteLine(tabla[1]); // Muestra 1, pues Mediano = 1
Console.WriteLine(tabla[2]); // Muestra 1, pues Grande = Pequeño+Mediano
• static string GetName(Type enum, object valor): Devuelve una cadena con el
nombre del literal de la enumeración representada por enum que tenga el valor especificado en valor. Por ejemplo, este código muestra Pequeño por pantalla: Console.WriteLine(Enum.GetName(typeof(Tamaño), 0)); //Imprime Pequeño
Si la enumeración no contiene ningún literal con ese valor devuelve null, y si tuviese varios con ese mismo valor devolvería sólo el nombre del último. Si se quiere obtener el de todos es mejor usar GetNames(), que se usa como GetName() pero devuelve un string[] con los nombres de todos los literales que tengan el valor indicado ordenados según su orden de definición en la enumeración.
• static bool isDefined (Type enum, object valor): Devuelve un booleano que indica si algún literal de la enumeración indicada tiene el valor indicado.
13 Recuérdese que para obtener el System.Type de un tipo de dato basta usar el operador typeof pasándole
El lenguaje de programación C# Tema 15: Interfaces
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 que sólo cuenten con 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 tiene muchas diferencias con éstas:
• Es posible definir tipos que deriven de más de una interfaz. Esto se debe a que los problemas que se pueden presentar a la hora de crear tipos que hereden de varios tipos son debidos a que pueden haber conflictos difíciles de resolver y si un tipo hereda más de una versión de un mismo método procedentes de padres diferentes y con códigos distintos. Sin embargo, con las interfaces las interfaces se permite la herencia múltiple porque esto nunca puede ocurrir debido a que las interfaces no incluyen código.
• Aunque las estructuras no pueden heredar clases, sí pueden hacerlo de 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.
• Las interfaces sólo pueden tener como miembros métodos normales, eventos, propiedades e indizadores; pero no pueden incluir definiciones de campos, operadores, constructores o destructores.