












Prepara tus exámenes y mejora tus resultados gracias a la gran cantidad de recursos disponibles en Docsity
Gana puntos ayudando a otros estudiantes o consíguelos activando un Plan Premium
Prepara tus exámenes
Prepara tus exámenes y mejora tus resultados gracias a la gran cantidad de recursos disponibles en Docsity
Prepara tus exámenes con los documentos que comparten otros estudiantes como tú en Docsity
Encuentra los documentos específicos para los exámenes de tu universidad
Estudia con lecciones y exámenes resueltos basados en los programas académicos de las mejores universidades
Responde a preguntas de exámenes reales y pon a prueba tu preparación
Consigue puntos base para descargar
Gana puntos ayudando a otros estudiantes o consíguelos activando un Plan Premium
Comunidad
Pide ayuda a la comunidad y resuelve tus dudas de estudio
Ebooks gratuitos
Descarga nuestras guías gratuitas sobre técnicas de estudio, métodos para controlar la ansiedad y consejos para la tesis preparadas por los tutores de Docsity
Domina los conceptos básicos del lenguaje de programación de propósito general usado para programar microcontroladores y sistemas operativos.
Tipo: Monografías, Ensayos
1 / 20
Esta página no es visible en la vista previa
¡No te pierdas las partes importantes!













El lenguaje de programación C# Tema 13: Estructuras
Sin embargo, la diferencia más importante entre los constructores de ambos tipos se encuentra en la implementación del constructor sin parámetros: como los objetos estructura no puede almacenar el valor por defecto null cuando se declaran sin usar constructor ya que ese valor indica referencia a posición de memoria dinámica indeterminada y los objetos estructura no almacenan referencias, toda estructura siempre tiene definido un constructor sin parámetros que lo que hace es darle en esos casos un valor por defecto a los objetos declarados. Ese valor consiste en poner a cero toda la memoria ocupada por el objeto, lo que tiene el efecto de dar como valor a cada campo el cero de su tipo^12. Por ejemplo, el siguiente código imprime un 0 en pantalla:
Punto p = new Punto(); Console.WriteLine(p.X);
Y el siguiente también:
using System;
struct Punto { public int X,Y; }
class EjemploConstructorDefecto { Punto p;
public static void Main() { Console.WriteLine(p.X); } }
Sin embargo, el hecho de que este constructor por defecto se aplique no implica que se pueda acceder a las variables locales sin antes inicializarlas con otro valor. Por ejemplo, el siguiente fragmente de código de un método sería incorrecto:
Punto p; Console.WriteLine(p.X); // X no inicializada
Sin embrago, como a las estructuras declaradas sin constructor no se les da el valor por defecto null , sí que sería válido:
Punto p; p.X = 2; Console.WriteLine(p.X);
Para asegurar un valor por defecto común a todos los objetos estructura, se prohíbe al programador dar una definición propia de su constructor sin parámetros. Mientras que en las clases es opcional implementarlo y si no se hace el compilador introduce uno por defecto, en las estructuras no es válido hacerlo. Además, aún en el caso de que se definan otros constructores, el constructor sin parámetros seguirá siendo introducido
(^12) O sea, cero para los campos de tipos numéricos, ‘\u0000’ para los de tipo char , false para los de tipo bool
y null para los de tipos referencia.
El lenguaje de programación C# Tema 13: Estructuras
automáticamente por el compilador a diferencia de cómo ocurría con las clases donde en ese caso el compilador no lo introducía.
Por otro lado, para conseguir que el valor por defecto de todos los objetos estructuras sea el mismo, se prohíbe darles una valor inicial a sus campos en el momento de declararlos, pues si no el constructor por defecto habría de tenerlos en cuenta y su ejecución sería más ineficiente. Por esta razón, los constructores definidos por el programador para una estructura han de inicializar todos sus miembros no estáticos en tanto que antes de llamarlos no se les da ningún valor inicial.
Nótese que debido a la existencia de un constructor por defecto cuya implementación escapa de manos del programador, el código de los métodos de una estructura puede tener que considerar la posibilidad de que se acceda a ellos con los valores resultantes de una inicialización con ese constructor. Por ejemplo, dado:
struct A { public readonly string S;
public A(string s) { if (s==null) throw (new ArgumentNullException()); this.s = S; } }
Nada asegura que en este código los objetos de clase A siempre se inicialicen con un valor distinto de null en su campo S, pues aunque el constructor definido para A comprueba que eso no ocurra lanzando una excepción en caso de que se le pase una cadena que valga null , si el programador usa el constructor por defecto creará un objeto en el que S valga null. Además, ni siquiera es válido especificar un valor inicial a S en su definición, ya que para inicializar rápidamente las estructuras sus campos no estáticos no pueden tener valores iniciales.
El lenguaje de programación C# Tema 14: Enumeraciones
detecte la incoherencia, un error en compilación o una excepción en ejecución. Sin embargo, si se hubiesen usado números mágicos del mismo tipo en vez de enumeraciones no se habría detectado nada, pues en ambos casos para el compilador y el CLR serían simples números sin ningún significado especial asociado.
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> :
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
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
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.
El lenguaje de programación C# Tema 14: Enumeraciones
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 { Pequeño = Mediano, 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 }
Las variables de tipos enumerados se definen como cualquier otra variable (sintaxis
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>.
El lenguaje de programación C# Tema 14: Enumeraciones
Console.WriteLine(t.ToString("X")); // Muestra 0 Console.WriteLine(t.ToString("G")); // Muestra Pequeño
En realidad, los valores de formato son insensibles a la capitalización y da igual si en vez de "G" se usa "g" o si en vez de "X" se usa "x".
Console.Write(Enum.Format(typeof(Tamaño), 0, "G"); // Muestra Pequeño
Si el valorLiteral indicado no estuviese asociado a ningún literal del tipo enumerador representado por enum, se devolvería una cadena con dicho número. Por el contrario, si hubiesen varios literales en la enumeración con el mismo valor numérico asociado, lo que se devolvería sería el nombre del declarado en último lugar al definir la enumeración.
Tamaño t = (Tamaño) Enum.Parse(typeof(Tamaño), "Pequeño"); Console.WriteLine(t) // Muestra Pequeño
Aparte de crear objetos a partir del nombre del literal que almacenarán, Parse() también permite crearlos a partir del valor numérico del mismo. Por ejemplo:
Tamaño t = (Tamaño) Enum.Parse(typeof(Tamaño), "0"); Console.WriteLine(t) // Muestra Pequeño
En este caso, si el valor indicado no se correspondiese con el de ninguno de los literales de la enumeración no saltaría ninguna excepción, pero el objeto creado no almacenaría ningún literal válido. Por ejemplo:
Tamaño t = (Tamaño) Enum.Parse(typeof(Tamaño), "255"); Console.WriteLine(t) // Muestra 255
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
El lenguaje de programación C# Tema 14: Enumeraciones
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.
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
El lenguaje de programación C# Tema 14: Enumeraciones
}
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.
El lenguaje de programación C# Tema 15: Interfaces
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:
La sintaxis general que se sigue a la hora de definir una interfaz es:
Los
El lenguaje de programación C# Tema 15: Interfaces
interface IC: IA, IB { event D EventoC; }
Nótese que aunque las interfaces padres de IC contienen un método común no hay problema alguno a la hora de definirlas. En el siguiente epígrafe veremos cómo se resuelven las ambigüedades que por esto pudiesen darse al implementar IC.
Para definir una clase o estructura que implemente una o más interfaces basta incluir los nombres de las mismas como si de una clase base se tratase -separándolas con comas si son varias o si la clase definida hereda de otra clase- y asegurar que la clase cuente con definiciones para todos los miembros de las interfaces de las que hereda -lo que se puede conseguir definiéndolos en ella o heredándolos de su clase padre.
Las definiciones que se den de miembros de interfaces han de ser siempre públicas y no pueden incluir override , pues como sus miembros son implícitamente abstract se sobreentiende. Sin embargo, sí pueden dársele los modificadores como virtual ó abstract y usar override en redefiniciones que se les den en clases hijas de la clase que implemente la interfaz.
Cuando una clase deriva de más de una interfaz que incluye un mismo miembro, la implementación que se le dé servirá para todas las interfaces que cuenten con ese miembro. Sin embargo, también es posible dar una implementación diferente para cada una usando una implementación explícita , lo que consiste en implementar el miembro sin el modificador public y anteponiendo a su nombre el nombre de la interfaz a la que pertenece seguido de un punto (carácter. )
Cuando un miembro se implementa explícitamente no puede dársele modificadores como en las implementaciones implícitas, ni siquiera virtual o abstract. Una forma de simular los modificadores que se necesiten consiste en darles un cuerpo que lo que haga sea llamar a otra función que sí cuente con esos modificadores.
El siguiente ejemplo muestra cómo definir una clase CL que implemente la interfaz IC:
class CL:IC { public int PropiedadA { get {return 5;} set {Console.WriteLine(“Asignado{0}a PropiedadA”, value);} }
void IA.Común(int x) { Console.WriteLine(“Ejecutado Común() de IA”); }
public int this[int índice] {
El lenguaje de programación C# Tema 15: Interfaces
get { return 1;} set { Console.WriteLine(“Asignado {0} a indizador”, value); } }
void IB.Común(int x) { Console.WriteLine(“Ejecutado Común() de IB”); }
public event D EventoC; }
Como se ve, para implementar la interfaz IC ha sido necesario implementar todos sus miembros, incluso los heredados de IA y IB, de la siguiente manera:
Otra utilidad de las implementaciones explícitas es que son la única manera de conseguir poder dar implementación a métodos ocultados en las definiciones de interfaces. Por ejemplo, si tenemos:
interface IPadre { int P{get;} } interface IHija:Padre { new int P(); }
La única forma de poder definir una clase donde se dé una implementación tanto para el método P() como para la propiedad P, es usando implementación explícita así:
class C: IHija { void IPadre.P {} public int P() {…} }
O así:
class C: IHija
El lenguaje de programación C# Tema 15: Interfaces
} }
Reimplementar un miembro de una interfaz de esta manera es parecido a redefinir los miembros reimplementados, sólo que ahora la redefinición sería sólamente accesible a través de variables del tipo de la interfaz Así, la salida del ejemplo anterior sería:
El F() de C El F() de C
Hay que tener en cuenta que de esta manera sólo pueden hacerse reimplementaciones de miembros si la clase donde se reimplementa hereda directamente de la interfaz implementada explícitamente o de alguna interfaz derivada de ésta. Así, en el ejemplo anterior sería incorrecto haber hecho:
class C2:C1 //La lista de herencias e interfaces implementadas por C2 sólo incluye a C { void IA.f(); // ERROR: Aunque C1 herede de IA, IA no se incluye directamente // en la lista de interfaces implementadas por C }
Es importante señalar que el nombre de interfaz especificado en una implementación explícita ha de ser exactamente el nombre de la interfaz donde se definió el miembro implementado, no el de alguna subclase de la misma. Por ejemplo:
interface I { void F() }
interface I {}
class C1:I { public void I2.F(); //ERROR: habría que usar I1.F() }
En el ejemplo anterior, la línea comentada contiene un error debido a que F() se definió dentro de la interfaz I1, y aunque también pertenezca a I2 porque ésta lo hereda de I1, a la hora de implementarlo explícitamente hay que prefijar su nombre de I1, no de I2.
Se puede acceder a los miembros de una interfaz implementados en una clase de manera no explícita a través de variables de esa clase como si de miembros normales de la misma se tratase. Por ejemplo, este código mostraría un cinco por pantalla:
CL c = new CL(); Console.WriteLine(c.PropiedadA);
Sin embargo, también es posible definir variables cuyo tipo sea una interfaz. Aunque no existen constructores de interfaces, estas variables pueden inicializarse gracias al
El lenguaje de programación C# Tema 15: Interfaces
polimorfismo asignándoles objetos de clases que implementen esa interfaz. Así, el siguiente código también mostraría un cinco por pantalla:
IA a = new CL(); Console.WriteLine(a.PropiedadA);
Nótese que a través de una variable de un tipo interfaz sólo se puede acceder a miembros del objeto almacenado en ella que estén definidos en esa interfaz. Es decir, los únicos miembros válidos para el objeto a anterior serían PropiedadA y Común()
En caso de que el miembro al que se pretenda acceder haya sido implementado explícitamente, sólo puede accederse a él a través de variables del tipo interfaz al que pertenece y no a través de variables de tipos que hereden de ella, ya que la definición de estos miembros es privada al no llevar modificador de acceso. Por ejemplo:
CL cl = new CL(); IA a = cl; IB b = cl; // Console.WriteLine(cl.Común()); // Error: Común() fue implementado explícitamente Console.WriteLine(a.Común()); Console.WriteLine(b.Común()); Console.WriteLine(((IA) cl).Común()); Console.WriteLine(((IB) cl).Común());
Cada vez que se llame a un método implementado explícitamente se llamará a la versión del mismo definida para la interfaz a través de la que se accede. Por ello, la salida del código anterior será:
Ejecutado Común() de IA Ejecutado Común() de IB Ejecutado Común() de IA Ejecutado Común() de IB
Se puede dar tanto una implementación implícita como una explícita de cada miembro de una interfaz. La explícita se usará cuando se acceda a un objeto que implemente esa interfaz a través de una referencia a la interfaz, mientras que la implícita se usará cuando el acceso se haga a través de una referencia del tipo que implementa la interfaz. Por ejemplo, dado el siguiente código:
interface I { object Clone(); }
class Clase:I { public object Clone() { Console.WriteLine(“Implementación implícita”); } public object IClonable.Clone() { Console.WriteLine(“Implementación explícita”); }
public static void Main()
El lenguaje de programación C# Tema 16: Instrucciones
Toda acción que se pueda realizar en el cuerpo de un método, como definir variables locales, llamar a métodos, asignaciones y muchas cosas más que veremos a lo largo de este tema, son instrucciones.
Las instrucciones se agrupan formando bloques de instrucciones , que son listas de instrucciones encerradas entre llaves que se ejecutan una tras otra. Es decir, la sintaxis que se sigue para definir un bloque de instrucciones es:
{
Toda variable que se defina dentro de un bloque de instrucciones sólo existirá dentro de dicho bloque. Tras él será inaccesible y podrá ser destruida por el recolector de basura. Por ejemplo, este código no es válido:
public void f(); { { int b; } b = 1; // ERROR: b no existe fuera del bloque donde se declaró. }
Los bloques de instrucciones pueden anidarse, aunque si dentro de un bloque interno definimos una variable con el mismo nombre que otra definida en un bloque externo se considerará que se ha producido un error, ya que no se podrá determinar a cuál de las dos se estará haciendo referencia cada vez que se utilice su nombre en el bloque interno.
En el Tema 7:Variables y tipos de datos ya se vió que las variables locales son variables que se definen en el cuerpo de los métodos y sólo son accesibles desde dichos cuerpos. Recuérdese que la sintaxis explicada para definirlas era la siguiente:
También se vió que podían definirse varias variables en una misma instrucción separando sus pares nombre-valor mediante comas. Por ejemplo:
int a=5, b, c=-1;
El lenguaje de programación C# Tema 16: Instrucciones
Una asignación es simplemente una instrucción mediante la que se indica un valor a almacenar en un dato. La sintaxis usada para ello es:
En temas previos ya se han dado numerosos ejemplos de cómo hacer esto, por lo que no es necesario hacer ahora mayor incapié en ello.
En el Tema 8: Métodos ya se explicó que una llamada a un método consiste en solicitar la ejecución de sus instrucciones asociadas dando a sus parámetros ciertos valores. Si el método a llamar es una método de objeto, la sintaxis usada para ello es:
Y si el método a llamar es un método de tipo, entonces la llamada se realiza con:
Recuérdese que si la llamada al método de tipo se hace dentro de la misma definición de tipo donde el método fue definido, la sección
La instrucción nula es una instrucción que no realiza nada en absoluto. Su sintaxis consiste en escribir un simple punto y coma para representarla. O sea, es:
;
Suele usarse cuando se desea indicar explícitamente que no se desea ejecutar nada. Usarla es útil para facilitar la legibilidad del código o, como veremos más adelante en el tema, porque otras instrucciones la necesitan para indicar cuándo en algunos de sus bloques de instrucciones componentes no se ha de realizar ninguna acción.
Las instrucciones condicionales son instrucciones que permiten ejecutar bloques de instrucciones sólo si se da una determinada condición. En los siguientes subapartados de este epígrafe se describen cuáles son las instrucciones condicionales disponibles en C#
La instrucción if permite ejecutar ciertas instrucciones sólo si de da una determinada condición. Su sintaxis de uso es la sintaxis: