1. Tipo y jerarquía de objetos Los tipos a los que se refiere CTS no son sólo aquellos utilizables al declarar una variable, en realidad incluyen todo aquello que se pueda instanciar en el código. Es decir, los tipos son realmente objetos, e incluyen todos los objetos utilizables en el código, con lo que el CTS da instrucciones al CLR para la gestión de todo tipo de objetos. Todos los tipos heredan de la clase base System.Object, por lo que los métodos y atributos de esta estarán disponibles en cualquier tipo que definamos. Los tipos se dividen en dos categorías principales: tipos por valor y tipos por referencia, los cuales se diferencian en la forma en la que se gestionan en memoria. Los tipos por valor se almacenan en la memoria rápida o pila (Stack), los tipos por referencia guardan en la pila la dirección de memoria lenta o montón (Heap) en la que realmente están situados los datos.
2. Tipo y jerarquía de objetos Esta diferenciación viene debida a que la memoria rápida no es muy grande, con lo que los datos ahí almacenados tienen una limitación de tamaño. Por lo tanto los tipos por valor siempre serán datos con poca ocupación de memoria, en cambio los tipos por referencia pueden tener cualquier tamaño, no sufriendo de las mismas limitaciones. Las variables de tipos por referencia pueden ser varias apuntando a la misma dirección de memoria, por lo que si se ejecuta alguna modificación en los datos mediante una de ellas, se estará afectando a todas ellas, por existir sólo una copia de los datos. Las variables de tipos por valor, en cambio, no sufren de éste problema, al tener, cada una de ellas, su propia copia de los datos. El esquema de organización de los tipos en .NET Framework es el siguiente: Tipos por valor Integrados o Nativos Enumeraciones Definidos por el programador Tipos por referencia Auto-descritos Arrays o Matrices Delegados Clases Clases del programador Punteros Embalados (Boxing) Interfaces
3. Tipo y jerarquía de objetos Un ejemplo de utilización de variables de los dos tipos, para que puedas apreciar las diferencias antes enumeradas: class ClasePrueba { public int Valor = 0; } class Program { static void Main(string[] args) { int valorA = 0; int valorB = valorA; valorB = 999; ClasePrueba refA = new ClasePrueba(); ClasePrueba refB = refA; refB.Valor = 999; Console.WriteLine("Val: {0}, {1}", valorA, valorB); Console.WriteLine("Ref: {0}, {1}", refA.Valor, refB.Valor); Console.ReadLine(); } } El resultado sería: Valores: 0, 999 Referencias: 999, 999
4. Tipo y jerarquía de objetos La definición de tipos por parte del programador es intrínseca al módulo en el que se realice, es decir, si defines dos tipos diferentes, pero con el mismo nombre, en diferentes módulos de ejecución, el sistema los tratará como tipos distintos y no habrá ningún problema. Para definir un nuevo tipo siempre se partirá de un tipo existente, aunque sólo sea System.Object, y será necesario especificar su contenido: Su nombre, que ha de ser una cadena de caracteres. El tipo del que hereda, uno y sólo uno, ya que la herencia múltiple no está soportada por el entorno. Como mínimo, System.Object, si ningún otro tipo puede servirte de base para el que estés definiendo. Sus atributos, o Metadatos adicionales proporcionados por el usuario. Su visibilidad, public, protected, private, internaloassembly, ya se verá más adelante en detalle. Las interfaces implementadas, sin limitación de número, pero deberán implementarse todas las del tipo base o heredado. La definición de cada uno de sus miembros (eventos, campos, tipos anidados, métodos y propiedades), incluyendo su firma única.
5. Tipo y jerarquía de objetos Boxing (Embalaje) y Unboxing (desembalaje) La operación de Boxingo embalaje sólo se aplica a los tipos por valor, consiste en la conversión de un tipo por valor a una variable Object almacenada en el Stack y una copia del tipo por valor almacenado en el Heap, al que apuntará la nueva variable de tipo Object. La operación opuesta Unboxing o desembalaje, consiste en pasar de un tipo Object a un tipo por valor. static void Main(string[] args) { // Tipo por valor int Importe = 1500; // Embalaje - BOXING: object OtroImporte = Importe; /* Si cambiamos el tipo por valor, la variable Object mantiene el valor original ya que los valores de ambas variables son copias independientes */ Importe = 999; /* Desembalaje - UNBOXING: Crear un nuevo tipo por valor y asignarle el tipo Object */ int NuevoImporte = (int)OtroImporte; Console.WriteLine("Valor: {0}, Referencia: {1}, Nuevo: {2}", Importe, OtroImporte, NuevoImporte); Console.ReadLine(); } Valor: 999, Referencia: 1500, Nuevo: 1500
6. Tipo y jerarquía de objetos Tipos por valor predefinidos más utilizados: Aunque hay unos 300 más.
7. Tipo y jerarquía de objetos Tipos por referencia predefinidos más utilizados: Aunque hay unos 2.500 más.
8. Tipo y jerarquía de objetos Tipos genéricos: Son la solución aportada para solventar los problemas de boxing y unboxing antes mencionados en el paso de parámetros a las clases, estructuras, interfaces y métodos que definamos, ya que permiten retrasar la definición del tipo concreto de dato a utilizar hasta la declaración e instanciación del objeto por parte del código cliente. También nos permiten la definición de código no dependiente del tipo de dato, evitando la duplicidad de clases con la única diferencia del tipo de datos recibidos y/o devueltos. La sintaxis específica es la de nombreClase<T>, en la que T es el nombre del tipo de dato que deberá suministrarse en el momento de definir o crear el objeto. El IntelliSense integrado en el IDE nos facilitará una validación inicial del uso de nuestras clases genéricas. Se pueden especificar restricciones a los tipos de datos que se admitan como argumentos.
9. Tipo y jerarquía de objetos Nullable<bool> b = null; bool? b = null; if (b.HasValue) Console.WriteLine("b is {0}.", b.Value); else Console.WriteLine("b is not set."); Tipos nullables: Son aquellos tipos por valor que pueden contener un valor nulo (null). Su sintaxis es Nullable<T> o T?
10. Tipo y jerarquía de objetos Tipos definidos por el usuario - Estructuras: Son tipos de dato por valor, con lo que contendrán directamente sus valores, almacenándose en el stack, siendo esto lo que las diferencia de las clases. Las estructuras se componen de otros tipos de dato, combinados para facilitar su manipulación. Un ejemplo sencillo es System.Drawing.Point, la cual contiene unas propiedades enteras, X e Y, que definen la posición horizontal y vertical de una coordenada espacial. Esta estructura dispone también de un constructor y unos miembros, para facilitar su utilización. Using System.drawing; // Creamos un punto System.Drawing.Point p = new System.Drawing.Point(20, 30); // Movemos el punto diagonalmente p.Offset(-1, -1); Console.WriteLine("Point X {0}, Y {1}", p.X, p.Y);
11. Tipo y jerarquía de objetos Para definir una estructura hemos de utilizar la palabra clave struct. Las estructuras se pueden convertir de tipos por valor a tipos por referencia cambiando esta palabra clave por class. Aunque las estructuras suelen ser más eficientes que las clases, siempre y cuando cumplan las siguientes condiciones: Representen un valor lógico único. El tamaño de instancia no sea superior a 16 bytes. Que no cambie frecuentemente después de la creación. Que no se transforme a tipo por referencia mediante casting.
12. Tipo y jerarquía de objetos Tipos definidos por el usuario - Enumeraciones: Las enumeraciones son tipos que podemos definir para almacenar listas de valores fijos relacionados. Los valores almacenados serán una lista de enteros, aunque nos referiremos a ellos, y visualizaremos sus valores, mediante sus símbolos correspondientes. Evitando de esta manera los habituales errores de índice en nuestra programación. Unos ejemplos de enumeraciones podrían ser los siguientes: La lista de meses. Los días de la semana. Los mensajes de error. ¿Alguno más? Para declarar una enumeración deberemos utilizar la palabra clave enum, seguida de su nombre y de la lista de los símbolos correspondientes a sus valores. enum diaSemana {lunes, martes, miércoles, jueves, viernes, sébado, domingo};
13. Tipo y jerarquía de objetos Tipos por referencia – String y StringBuilder Los tipos, además de contener los datos correspondientes, suministran los medios para su manipulación y almacenamiento, en caso necesario, mediante los diferentes miembros definidos. Por ejemplo: string cadena = "Este es el texto almacenado"; cadena = cadena.Replace("almacenado","sustituido"); Console.WriteLine(cadena"); Este es el texto sustituido El problema con este tipo de operaciones es que los objetos de tipo string son inmutables, es decir, la sustitución anterior no se efectuará sobre los datos iniciales de la instancia. En su lugar se creará una segunda copia de la cadena inicial, con el texto sustituido y se cambiará el puntero del objeto al nuevo contenido, quedando los datos anteriores sin referencia, a expensas de ser eliminados por el Garbage Collector.
14. Tipo y jerarquía de objetos Tipos por referencia – String y StringBuilder Este comportamiento es, por supuesto, transparente al programador, por lo que uno se puede preguntar cuál pueda ser la repercusión real de este comportamiento. Se puede fácilmente imaginar una larga lista de valores de cadena de caracteres a la que se deba aplicar una modificación, concatenando un texto adicional o añadiendo una fecha, … Cada modificación que se realice sobre cada una de las cadenas de texto creará una nueva cadena. Dada la velocidad actual de proceso de los ordenadores, la realización de la operación anterior puede llegar a procesar grandes cantidades de datos en muy poco tiempo. Lo cual, inicialmente, es lo adecuado. El problema es que esa cantidad de variables descartadas y pendientes de limpieza, puede llegar a saturar la memoria del ordenador, obligando a efectuar paginaciones y operaciones que harán más lenta la operativa.
15. Tipo y jerarquía de objetos Tipos por referencia – String y StringBuilder Como alternativa a este comportamiento del tipo string, disponemos del tipo stringBuilder, el cual ha sido optimizado para este tipo de manipulaciones y efectúa las modificaciones sobre la misma instancia de datos, evitándose de esta forma la saturación antes comentada. Adicionalmente, el tipo stringBuilder dispone de una sobrecarga de operadores que simplifica la operativa con ellos:
16. Tipo y jerarquía de objetos // Declaración del Array Int[] tabla = {3, 1, 2}; // Ordena los valores mediante el método del tipo Array.Sort(tabla); // Muestra el resultado Console.Writeline("{0}, {1}, {2}", tabla[0], tabla[1], tabla[2]); 1, 2, 3 Tipos por referencia – Arrays Permiten almacenar colecciones de valores en un único objeto, accesibles mediante índice. Se declaran utilizando los corchetes y separando sus elementos mediante comas. Se pueden ordenar mediante su método sort:
17. Tipo y jerarquía de objetos Tipos por referencia – Streams Permiten el acceso de lectura y/o escritura a dispositivos, tanto discos, como comunicaciones. La clase System.IO.Stream es la clase base para los streams específicos para cada tipo de acceso. Disponiéndose también de streams de acceso a redes en el espacio de nombres System.Network.Sockets y de streams encriptados en System.Security.Criptography. Los streams básicos más comunes son: