sábado, 29 de enero de 2022

Curso avanzado de C#: Tipos de datos


C # divide los tipos de valor en dos categorías distintas conocidas como estructuras y enumeraciones. Las estructuras se dividen a su vez en subcategorías llamadas tipos numéricos (tipos enteros, tipos decimales de coma flotante y decimales), tipos booleanos y estructuras definidas por el usuario. Las enumeraciones se forman como un conjunto de tipos declarados usando la palabra clave enum. 

Curso avanzado de C#: Tipos de datos


Tipos de valores predefinidos

Los tipos de valor identifican valores específicos de datos. C# incluye tipos de datos intrínsecos comunes a muchos otros lenguajes de programación y se usan para almacenar valores simples.  Los tipos de datos intrínsecos de C # tienen asignaciones directas a los tipos de .NET. Los nombres listados en la columna de tipos de la siguiente tabla son conocidos como alias para los tipos .NET. Todos los tipos de valores derivan de System.ValueType. 
La Tabla enumera los tipos de valores básicos que admite C#, incluido el rango de los valores de datos que estos soportan.

Tipo
Valor
Tamaño
Tipo .Net
bool
true/false
1 byte
System.Boolean
byte
Signed 32-bit
1 byte
System.Byte
char
0000–FFFF Unicode
16 bit
System.Char
decimal
±1 0 × 10−28  a ±7 9 × 1028
28-20 digitos significativos
System.Decimal
double
±5 0 × 10−324 a ±1 7 × 10308
15-16 digitos
System.Double
enum
Colección de constantes definidas por el usuario


float
±1 5 × 10−45 to ±3 4 × 1038
7 digitos
System.Single
int
–2,147,483,648  a 2,147,483,647
Signed 32-bit
System.Int32
long
9,223,372,036,854,775,808 a 9,223,372,036,854,775,807
Signed 64-bit
System.Int64
sbyte
–128 a 127
Signed 8-bit
System.SByte
short
–32,768 a 32,767
Signed 16-bit
System.Int16
struct
Incluye los tipos numéricos listados en esta tabla, incluyendo el boolean y  estructuras definidas por el usuario


uint
0 a 4,294,967,295
Unsigned 32-bit
System.UInt32
ulong
0 a 18,446,744,073,709,551,615
Unsigned 64-bit
System.UInt64
ushort
0 a 65,535
Unsigned 16-bit
System.Uint16

Para trabajar con los tipos de datos, declaramos una variable del tipo que deseamos. Después almacenamos en ella un valor del tipo declarado a través de una asignación. La asignación también se puede incluir como parte de la declaración. El siguiente código demuestra ambas opciones:

// declaramos una variable integer
int miEntero;
// Y le asignamos un valor
miEntero = 3;
// Declaramos y signamos en la misma instrucción
int miSegundoEntero = 50;
        
La palabra clave int se utiliza para indicar que la variable será de tipo entero, que es el alias para el tipo System.Int32. Como resultado, podrá contener cualquier valor desde 2,147,482,648 negativo a 2,147,482,647 positivo. 

Nota: No confundir los tipos de datos de C#  con nombres similares que se encuentran en los conceptos matemáticos. Por ejemplo, el tipo de datos int, que es la abreviatura de entero, no es lo mismo que el concepto de entero matemático. Los enteros en matemáticas pueden contener valores desde menos infinito hasta infinito positivo. Sin embargo, los tipos de datos en C # dependen de la cantidad de bits utilizados para contener el tipo de datos. En este caso, int tiene 32 bits ; 2 elevado a la potencia de 32  proporciona un máximo de 4,294,967,296. Quitamos 1 bit para usar para el signo, y tendremos los valores listados en la tabla anterior para int. 

Hay que tener en cuenta un par de restricciones con los tipos. No podemos derivar un nuevo tipo de un tipo de valor, y los tipos de valor no pueden contener un valor nulo. Ahora, aquí es donde el uso del alias para un tipo de valor y el tipo de sistema .NET difieren. Intentando usar un alias con una variable no asignada en código, resultará que Visual Studio generará un error sobre el uso de una variable no asignada. Cada tipo de valor tiene un tipo correspondiente de .NET en el espacio de nombres del sistema, y podemos utilizar una versión de este tipo sin firmar. Esto es posible porque los tipos de sistema son clases (tipos de referencia), se crean mediante el uso del nuevo operador y contienen un valor predeterminado. 

Al final de cada ejemplo pondremos la instrucción

Console.ReadLine(); 

Para que la pantalla se detenga y podamos leer el resultado. Saldremos de esta pantalla pulsando Enter.

Comparación de tipos de valor y sus alias [value_type_alias]


// crea una variable para mantener un tipo por valor
// utilizando el formulario de alias
 // pero no asigna la variable
 int miEntero;
 int miNuevoEntero = new int();
 // crea una variable para un tipo .NET 
 // Este tipo es la versión .NET alias de la forma int
 // el uso de new, crea un objeto de la clase System.Int32
int miEntero32 = new System.Int32();

// tendremos que comentar esta primera declaración de //Console.WriteLine pues .Net mostrará un error sobre el uso de una variable no asignada
// Esto se hace para evitar el uso de un valor que se //almacenó en la ubicación de la memoria antes de la //creación de esta variable

Console.WriteLine(miEntero);

// Imprime el valor por defecto asignado a la variable int
// que no tenía un valor asignado previamente //Console.WriteLine(miNuevoEntero);
// Esta instrucción imprimirá el valor por defecto de este //tipo de datos, por defecto el 0
 
Console.WriteLine(miEntero32);
Console.ReadLine(); 

En el ejemplo de código anterior, la variable miEntero32 se crea como un nuevo objeto del tipo System.Int32 .NET. No se proporciona un valor en la declaración System.Int32

int miEntero32 = new System.Int32(); 

Como resultado, Visual Studio llama al constructor predeterminado para este objeto y le asigna el valor predeterminado. Se crea una variable llamada miNuevoEntero usando la palabra clave new. .NET Framework reconoce que esta forma de declaración de variable es la misma que al usar el estilo de variable System.Int32. Aunque la declaración de

 int miEntero;

no permite generar el valor de esta variable si no se ha asignado, la declaración de

int miNuevoEntero = new int();

Permite generar la variable no asignada. Sin embargo, esta segunda versión no se usa a menudo cuando se trata de tipos simples, aunque nada impide usarla. 

.NET Framework proporciona valores predeterminados para todos los tipos de valores del sistema creados de esta manera. Los valores predeterminados para todos los tipos numéricos son equivalentes a cero. Cualquiera de los tipos de coma flotante como decimal, double o float será 0.0. El valor predeterminado para boolean es false, char es '\ 0',  enum es (E) 0 y las estructuras se establecen en nulo. Otro aspecto importante que debe entenderse sobre los tipos de valor es la forma en que se administran los valores. .NET Framework almacena los tipos de valor en la pila (stack) en lugar de en el montón (heap). El resultado es que si asignamos un tipo de valor a otro, se copiará el valor del primero al segundo. Los tipos por referencia copian una referencia (dirección de memoria) en oposición a los valores reales. El siguiente código de ejemplo muestra la creación de dos variables enteras. Se asigna un valor a una de las variables y luego se asigna una variable a otra. 

// asignando un tipo de valor a otro
int miEntero;
int segundoEntero;
//se asigna 2 a miEntero
miEntero = 2;
// segundoEntero contendrá el valor 2
segundoEntero = miEntero;
// Mostramos el valor de las variables
          
Console.WriteLine(miEntero);
Console.WriteLine(segundoEntero);
Console.WriteLine();

Aunque en los ejemplos anteriores sólo se ha mostrado el tipo de datos enteros, trabajar con los otros tipos de valores simples es similar. 

Usando value types [using_value_types]

// declaramos algunos tipos de dato numéricos
int miInt;
double miDouble;
byte miByte;
char miChar;
decimal miDecimal;
float miFloat;
long miLong;
short miShort;
bool miBool;
// asignamos  valores a estos tipos 
// los imprimimos en la consola
// utilizamos sizeOf para determinar
// el numero de bytes que ocupa cada tipo
miInt = 5000;
Console.WriteLine("Integer");
Console.WriteLine(miInt);
Console.WriteLine(miInt.GetType());
Console.WriteLine(sizeof(int));
Console.WriteLine();
miDouble = 5000.0;
Console.WriteLine("Double");
Console.WriteLine(miDouble);
Console.WriteLine(miDouble.GetType());
Console.WriteLine(sizeof(double));
Console.WriteLine();
miByte = 254; Console.WriteLine("Byte");
Console.WriteLine(miByte);
Console.WriteLine(miByte.GetType());
Console.WriteLine(sizeof(byte));
Console.WriteLine();
miChar = 'r';
Console.WriteLine("Char");
Console.WriteLine(miChar);
Console.WriteLine(miChar.GetType());
Console.WriteLine(sizeof(byte));
Console.WriteLine();
miDecimal = 20987.89756M;
Console.WriteLine("Decimal");
Console.WriteLine(miDecimal);
Console.WriteLine(miDecimal.GetType());
Console.WriteLine(sizeof(byte));
Console.WriteLine();
miFloat = 254.09F;
Console.WriteLine("Float");
Console.WriteLine(miFloat);
Console.WriteLine(miFloat.GetType());
Console.WriteLine(sizeof(byte));
Console.WriteLine();
miLong = 2544567538754;
Console.WriteLine("Long");
Console.WriteLine(miLong);
Console.WriteLine(miLong.GetType());
Console.WriteLine(sizeof(byte));
Console.WriteLine();
miShort = 3276;
Console.WriteLine("Short");
Console.WriteLine(miShort);
Console.WriteLine(miShort.GetType());
Console.WriteLine(sizeof(byte));
Console.WriteLine(); miBool = true;
Console.WriteLine("Boolean");
Console.WriteLine(miBool);
Console.WriteLine(miBool.GetType());
Console.WriteLine(sizeof(byte));
Console.WriteLine();
Console.ReadLine();  //esta última línea es la más importante pues para la pantalla y nos permite leer el resultado

Análisis

Este código declara variables de varios tipos de valores que son intrínsecos a C #. Luego, cada variable se usa en un conjunto repetido de declaraciones de código es:

Asigna un valor a la variable 
Muestra una línea en la consola que indica el tipo de valor 
Muestra el valor asignado 
Muestra el tipo de sistema asociado al tipo de valor 
Muestra el tamaño del tipo de valor en bytes 

Para obtener una comprensión completa de estos tipos, cambiaremos los valores en las declaraciones de asignación a diferentes tipos o fuera del rango y veremos lo qué devuelve el compilador para los mensajes de error. Es necesario comprender estos tipos simples para representar los datos que sus aplicaciones utilizarán para representar problemas del mundo real. 

Mejores Prácticas: Eficiencia de código

Los desarrolladores que escriben código hoy pasan cada vez menos tiempo pensando en la eficiencia del código y los tipos de datos utilizados, principalmente debido a la potencia y la capacidad de almacenamiento de los ordenadores que se usan en la actualidad. En los primeros días de la computadora personal, la memoria era una prioridad, y todo el código escrito se hacía para conservar el uso de la memoria de la aplicación. Comprender el tamaño de los datos le ayuda a elegir el tipo de datos adecuado para nuestras necesidades de almacenamiento. Un tipo de datos demasiado grande puede desperdiciar recursos, mientras que un rango de tipos de datos demasiado pequeño puede causar problemas de desbordamiento y, a veces, problemas generales en los que al incrementar un valor int con signo (que puede ir de 32.767 a –32.767)  puede causar errores difíciles de detectar y localizar.