CSharp
Tutorial CSharp: Tipos de datos
Aprende los tipos de datos en C#, diferencias entre valor y referencia, y cómo realizar conversiones básicas para un código eficiente y seguro.
Aprende CSharp y certifícateTipos primitivos
En C#, los tipos primitivos son los bloques fundamentales para representar datos simples. Estos tipos están integrados directamente en el lenguaje y permiten almacenar valores básicos como números, caracteres y valores lógicos.
Tipos numéricos enteros
C# ofrece varios tipos para representar números enteros, cada uno con diferentes rangos y tamaños en memoria:
- sbyte: Entero con signo de 8 bits (-128 a 127)
- byte: Entero sin signo de 8 bits (0 a 255)
- short: Entero con signo de 16 bits (-32,768 a 32,767)
- ushort: Entero sin signo de 16 bits (0 a 65,535)
- int: Entero con signo de 32 bits (-2,147,483,648 a 2,147,483,647)
- uint: Entero sin signo de 32 bits (0 a 4,294,967,295)
- long: Entero con signo de 64 bits (-9,223,372,036,854,775,808 a 9,223,372,036,854,775,807)
- ulong: Entero sin signo de 64 bits (0 a 18,446,744,073,709,551,615)
El tipo int es el más comúnmente utilizado para representar números enteros:
int edad = 25;
int temperatura = -5;
uint cantidadPositiva = 100; // Solo valores positivos
long poblacionMundial = 7_900_000_000; // Los guiones bajos mejoran la legibilidad
Tipos numéricos de punto flotante
Para representar números con decimales, C# proporciona tres tipos:
- float: Punto flotante de precisión simple (32 bits)
- double: Punto flotante de precisión doble (64 bits)
- decimal: Punto flotante de alta precisión (128 bits)
float altura = 1.75f; // Requiere el sufijo 'f'
double pi = 3.14159265359; // No requiere sufijo, es el tipo por defecto
decimal precio = 19.99m; // Requiere el sufijo 'm'
El tipo double ofrece mayor precisión que float y es el tipo por defecto para literales de punto flotante. El tipo decimal proporciona la mayor precisión y es ideal para cálculos financieros donde la exactitud decimal es crucial.
Tipo booleano
El tipo bool representa valores lógicos verdadero o falso:
bool esMayorDeEdad = true;
bool tieneDescuento = false;
// Uso común en estructuras de control
if (esMayorDeEdad)
{
Console.WriteLine("Puede acceder al contenido");
}
Tipo carácter
El tipo char representa un único carácter Unicode de 16 bits:
char letra = 'A';
char simbolo = '$';
char unicode = '\u00A9'; // Símbolo de copyright ©
Los caracteres se definen entre comillas simples, a diferencia de las cadenas que usan comillas dobles.
Literales y sufijos
Los literales son valores constantes en el código. Dependiendo del tipo, pueden requerir sufijos específicos:
int normal = 1000;
long grande = 3000000000L; // Sufijo 'L' para long
float decimal1 = 45.6f; // Sufijo 'f' para float
double decimal2 = 45.6; // Sin sufijo (por defecto)
decimal preciso = 45.6m; // Sufijo 'm' para decimal
Valores predeterminados
Cada tipo primitivo tiene un valor predeterminado que se asigna automáticamente cuando se declara una variable sin inicializar:
int enteroNoInicializado; // Valor predeterminado: 0
bool booleanoNoInicializado; // Valor predeterminado: false
char caracterNoInicializado; // Valor predeterminado: '\0'
float flotanteNoInicializado; // Valor predeterminado: 0.0f
Sin embargo, las variables locales deben inicializarse antes de usarse, o el compilador generará un error.
Rangos y precisión
Es importante elegir el tipo adecuado según los requisitos de la aplicación:
// Ejemplo de desbordamiento (overflow)
byte pequeño = 255; // Valor máximo para byte
// pequeño = pequeño + 1; // Esto causaría un desbordamiento
// Ejemplo de pérdida de precisión
float numeroGrande = 10000000.0f;
float pequeñoIncremento = 0.0001f;
float resultado = numeroGrande + pequeñoIncremento; // El incremento se pierde
Constantes
Para valores que no deben cambiar, se utiliza la palabra clave const:
const int DIAS_SEMANA = 7;
const double GRAVEDAD = 9.81;
// DIAS_SEMANA = 8; // Error: no se puede modificar una constante
Inferencia de tipos
C# permite usar la palabra clave var para que el compilador infiera el tipo automáticamente:
var edad = 25; // Infiere int
var nombre = "Ana"; // Infiere string
var pi = 3.14; // Infiere double
var activo = true; // Infiere bool
Es importante entender que var no hace que la variable sea de tipo dinámico; el tipo se determina en tiempo de compilación basado en el valor inicial.
Tipos numéricos sin signo
Los tipos sin signo (byte, ushort, uint, ulong) son útiles cuando se trabaja con valores que nunca serán negativos:
uint cantidadProductos = 500;
// cantidadProductos = -10; // Error: no puede ser negativo
// Útil para representar tamaños o cantidades
ulong tamañoArchivo = 3_500_000_000; // Más de 2 mil millones
Operaciones con tipos primitivos
Los tipos primitivos soportan operaciones aritméticas y lógicas básicas:
int a = 10;
int b = 3;
int suma = a + b; // 13
int resta = a - b; // 7
int multiplicacion = a * b; // 30
int division = a / b; // 3 (división entera)
int modulo = a % b; // 1 (resto)
bool comparacion = a > b; // true
bool igualdad = a == b; // false
Los tipos primitivos en C# son la base para construir tipos más complejos y estructuras de datos. Comprender sus características, rangos y comportamientos es fundamental para escribir código eficiente y libre de errores.
Valor vs referencia
En C#, la forma en que las variables almacenan y manipulan datos depende fundamentalmente de si son tipos de valor o tipos de referencia. Esta distinción es crucial para entender cómo se comportan las variables cuando se asignan o se pasan como parámetros.
Tipos de valor
Los tipos de valor almacenan directamente sus datos en la memoria asignada a la variable. Cuando declaras una variable de tipo valor, se reserva espacio en la pila (stack) para contener el valor completo. Los tipos primitivos que vimos anteriormente (int, float, bool, char, etc.) son todos tipos de valor.
int x = 10; // Se almacena el valor 10 directamente en la variable x
Además de los tipos primitivos, las estructuras (struct) también son tipos de valor:
struct Punto
{
public int X;
public int Y;
}
Punto p1;
p1.X = 5;
p1.Y = 10;
Comportamiento de los tipos de valor en asignaciones
Cuando asignas un tipo de valor a otra variable, se crea una copia independiente del valor:
int original = 10;
int copia = original; // Se crea una copia del valor
// Modificar la copia no afecta al original
copia = 20;
Console.WriteLine($"Original: {original}, Copia: {copia}");
// Muestra: "Original: 10, Copia: 20"
Este comportamiento se conoce como paso por valor. Cada variable mantiene su propia copia independiente de los datos.
Tipos de referencia
Los tipos de referencia funcionan de manera diferente. En lugar de almacenar directamente los datos, almacenan una referencia (esencialmente una dirección de memoria) que apunta a donde realmente se encuentran los datos en el montículo (heap).
Los tipos de referencia más comunes incluyen:
- Cadenas (string)
- Arrays
- Clases
// Las cadenas son tipos de referencia
string mensaje = "Hola mundo";
// Los arrays son tipos de referencia
int[] numeros = { 1, 2, 3, 4, 5 };
Comportamiento de los tipos de referencia en asignaciones
Cuando asignas un tipo de referencia a otra variable, ambas variables apuntan a los mismos datos en memoria:
int[] original = { 1, 2, 3 };
int[] referencia = original; // Ambas variables apuntan al mismo array
// Modificar a través de la referencia afecta al original
referencia[0] = 99;
Console.WriteLine($"Original[0]: {original[0]}, Referencia[0]: {referencia[0]}");
// Muestra: "Original[0]: 99, Referencia[0]: 99"
Este comportamiento se conoce como paso por referencia. Las variables comparten los mismos datos subyacentes.
Comparación visual
Para entender mejor la diferencia, podemos visualizarlo así:
Tipos de valor:
Variable x (en stack): [10]
Variable y (en stack): [10] // Copia independiente
Tipos de referencia:
Variable a (en stack): [Referencia] ---> [Datos reales] (en heap)
Variable b (en stack): [Referencia] --/
Ejemplo práctico con arrays
Los arrays son un buen ejemplo para ilustrar el comportamiento de referencia:
// Creamos un array (tipo de referencia)
int[] a = { 1, 2, 3 };
int[] b = a; // b y a apuntan al mismo array
// Modificamos a través de b
b[0] = 100;
// Verificamos que a también cambió
Console.WriteLine(a[0]); // Muestra: 100
El caso especial de string
Aunque string es un tipo de referencia, tiene un comportamiento especial. Las cadenas en C# son inmutables, lo que significa que no pueden modificarse después de crearse:
string s1 = "hola";
string s2 = s1;
// Esto no modifica s1, sino que crea una nueva cadena
s2 = s2 + " mundo";
Console.WriteLine(s1); // Muestra: "hola"
Console.WriteLine(s2); // Muestra: "hola mundo"
Esto puede dar la impresión de que las cadenas se comportan como tipos de valor, pero en realidad es debido a su inmutabilidad.
Implicaciones en el rendimiento
Esta distinción tiene importantes implicaciones en el rendimiento:
- Los tipos de valor son generalmente más eficientes para datos pequeños y simples, ya que evitan la sobrecarga de la asignación en el heap.
- Los tipos de referencia son más eficientes para datos grandes o complejos, ya que evitan copiar grandes cantidades de datos.
// Eficiente: tipo de valor para datos simples
int contador = 0;
// Eficiente: tipo de referencia para datos grandes
int[] granArray = new int[10000];
Paso de parámetros a métodos
El comportamiento de valor vs referencia también afecta cómo se pasan los parámetros a los métodos:
void ModificarValor(int x)
{
x = 100; // Modifica solo la copia local
}
void ModificarReferencia(int[] arr)
{
arr[0] = 100; // Modifica el array original
}
int numero = 5;
ModificarValor(numero);
Console.WriteLine(numero); // Sigue siendo 5
int[] miArray = { 1, 2, 3 };
ModificarReferencia(miArray);
Console.WriteLine(miArray[0]); // Ahora es 100
Modificadores ref y out
C# proporciona los modificadores ref y out para cambiar este comportamiento predeterminado:
// Con 'ref', podemos modificar el valor original
void DuplicarNumero(ref int n)
{
n = n * 2;
}
int valor = 10;
DuplicarNumero(ref valor);
Console.WriteLine(valor); // Muestra: 20
El modificador out es similar, pero no requiere que la variable esté inicializada antes de pasarla:
void ObtenerCoordenadas(out int x, out int y)
{
x = 10;
y = 20;
}
int coordX, coordY;
ObtenerCoordenadas(out coordX, out coordY);
Console.WriteLine($"X: {coordX}, Y: {coordY}"); // Muestra: "X: 10, Y: 20"
Tipos nulables
Los tipos de valor normalmente no pueden ser nulos. Sin embargo, C# permite crear tipos de valor nulables usando el operador ?
:
int? numeroNulable = null;
numeroNulable = 10;
if (numeroNulable.HasValue)
{
Console.WriteLine(numeroNulable.Value); // Muestra: 10
}
Entender la diferencia entre tipos de valor y referencia es fundamental para escribir código C# eficiente y predecible, especialmente cuando se trabaja con estructuras de datos complejas o se pasan parámetros entre métodos.
Conversiones básicas
En C# es común necesitar convertir datos de un tipo a otro durante el desarrollo de aplicaciones. Estas conversiones (también llamadas "casting" o "type casting") permiten transformar valores entre diferentes tipos de datos para adaptarlos a los requisitos específicos de nuestro código.
Conversiones implícitas
Las conversiones implícitas ocurren automáticamente cuando no hay riesgo de pérdida de datos. El compilador las realiza sin necesidad de código adicional cuando asignamos un valor de un tipo a una variable de otro tipo compatible.
int entero = 100;
long numeroLargo = entero; // Conversión implícita de int a long
float numeroFloat = 10.5f;
double numeroDouble = numeroFloat; // Conversión implícita de float a double
Las conversiones implícitas siguen una jerarquía lógica donde los tipos "más pequeños" pueden convertirse automáticamente a tipos "más grandes":
- byte → short → int → long → float → double → decimal
byte valorPequeno = 25;
int valorMedio = valorPequeno; // Implícita
double valorGrande = valorMedio; // Implícita
Conversiones explícitas
Cuando existe riesgo de pérdida de datos (como convertir de un tipo más grande a uno más pequeño), debemos realizar una conversión explícita utilizando el operador de casting:
double precio = 29.99;
int precioRedondeado = (int)precio; // Conversión explícita, se pierde la parte decimal
Console.WriteLine(precioRedondeado); // Muestra: 29
Es importante tener en cuenta que las conversiones explícitas pueden provocar pérdida de precisión o incluso resultados inesperados:
long numeroGrande = 9876543210;
int numeroTruncado = (int)numeroGrande; // Desbordamiento - pérdida de datos
Console.WriteLine(numeroTruncado); // Muestra un valor inesperado debido al desbordamiento
Métodos de conversión de la clase Convert
La clase Convert proporciona métodos para realizar conversiones más seguras entre tipos de datos:
string edadTexto = "25";
int edad = Convert.ToInt32(edadTexto); // Convierte string a int
bool activo = true;
int valorNumerico = Convert.ToInt32(activo); // true se convierte a 1, false a 0
Console.WriteLine(edad); // Muestra: 25
Console.WriteLine(valorNumerico); // Muestra: 1
La clase Convert ofrece métodos para la mayoría de los tipos primitivos:
// Conversiones comunes con la clase Convert
double valorDecimal = 123.45;
int enteroConvertido = Convert.ToInt32(valorDecimal); // Redondea a 123
string cadenaBooleana = "True";
bool valorBooleano = Convert.ToBoolean(cadenaBooleana); // Convierte a true
DateTime fecha = Convert.ToDateTime("2023-05-15"); // Convierte string a fecha
Métodos Parse y TryParse
Los tipos numéricos y algunos otros tipos proporcionan métodos Parse para convertir cadenas en valores tipados:
string numeroTexto = "42";
int numero = int.Parse(numeroTexto);
string precioTexto = "19.99";
double precio = double.Parse(precioTexto); // Nota: esto depende de la configuración regional
Sin embargo, Parse lanza una excepción si la conversión falla. Para conversiones más seguras, es recomendable usar TryParse:
string entrada = "abc";
int resultado;
if (int.TryParse(entrada, out resultado))
{
Console.WriteLine($"Conversión exitosa: {resultado}");
}
else
{
Console.WriteLine("La conversión falló"); // Se ejecutará esta línea
}
El método TryParse intenta realizar la conversión y devuelve un booleano indicando si tuvo éxito, sin lanzar excepciones:
// Ejemplo más completo de TryParse
string[] entradas = { "100", "12.5", "no es un número", "true" };
foreach (string entrada in entradas)
{
int valorEntero;
if (int.TryParse(entrada, out valorEntero))
{
Console.WriteLine($"'{entrada}' -> {valorEntero}");
}
else
{
Console.WriteLine($"'{entrada}' no se puede convertir a entero");
}
}
Conversión entre cadenas y otros tipos
La conversión entre cadenas y otros tipos es muy común en aplicaciones que interactúan con usuarios:
// De tipos primitivos a string
int cantidad = 42;
string cantidadTexto = cantidad.ToString();
double temperatura = 23.5;
string temperaturaTexto = temperatura.ToString("F1"); // "23.5" (1 decimal)
// Formateo específico
decimal precio = 1299.99m;
string precioFormateado = precio.ToString("C"); // "$1,299.99" (formato de moneda)
Conversiones con operadores "as" y "is"
Para tipos de referencia, C# proporciona los operadores as e is que son útiles para conversiones seguras:
object valor = "Hola mundo";
// Comprobación con "is"
if (valor is string)
{
Console.WriteLine("El valor es una cadena");
}
// Conversión con "as"
string texto = valor as string; // Si no es posible, devuelve null en lugar de lanzar excepción
if (texto != null)
{
Console.WriteLine(texto.ToUpper()); // "HOLA MUNDO"
}
El operador as solo funciona con tipos de referencia y tipos nulables, no con tipos de valor primitivos.
Conversiones con el patrón de coincidencia (pattern matching)
En versiones modernas de C#, el patrón de coincidencia simplifica las conversiones y comprobaciones de tipo:
object item = 123;
if (item is int numero)
{
// La variable 'numero' ya está convertida y disponible
Console.WriteLine($"El cuadrado es: {numero * numero}");
}
// También funciona en expresiones switch
switch (item)
{
case int i:
Console.WriteLine($"Es un entero: {i}");
break;
case string s:
Console.WriteLine($"Es una cadena: {s}");
break;
default:
Console.WriteLine("Tipo no reconocido");
break;
}
Conversiones entre tipos numéricos con verificación
Para realizar conversiones seguras entre tipos numéricos con verificación de desbordamiento, podemos usar las palabras clave checked y unchecked:
int valorMaximo = int.MaxValue; // 2,147,483,647
try
{
checked
{
int resultado = valorMaximo + 1; // Lanzará OverflowException
}
}
catch (OverflowException ex)
{
Console.WriteLine("Se produjo un desbordamiento: " + ex.Message);
}
// Sin verificación (comportamiento predeterminado)
unchecked
{
int resultado = valorMaximo + 1; // No lanza excepción, pero produce un resultado incorrecto
Console.WriteLine(resultado); // Muestra: -2,147,483,648
}
Conversiones entre tipos personalizados
También es posible definir conversiones personalizadas entre tipos definidos por el usuario mediante operadores de conversión:
public struct Celsius
{
public double Temperatura { get; }
public Celsius(double temperatura)
{
Temperatura = temperatura;
}
// Conversión implícita de double a Celsius
public static implicit operator Celsius(double temperatura)
{
return new Celsius(temperatura);
}
// Conversión explícita de Celsius a Fahrenheit
public static explicit operator Fahrenheit(Celsius celsius)
{
return new Fahrenheit(celsius.Temperatura * 9 / 5 + 32);
}
}
public struct Fahrenheit
{
public double Temperatura { get; }
public Fahrenheit(double temperatura)
{
Temperatura = temperatura;
}
}
Las conversiones entre tipos son una parte fundamental de la programación en C#, permitiéndonos trabajar con diferentes representaciones de datos según las necesidades de nuestra aplicación. Elegir el método de conversión adecuado es esencial para mantener la integridad de los datos y evitar errores en tiempo de ejecución.
Otros ejercicios de programación de CSharp
Evalúa tus conocimientos de esta lección Tipos de datos con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
CRUD en C# de modelo Customer sobre una lista
Arrays y listas
Objetos
Excepciones
Eventos
Lambdas
Diccionarios en C#
Variables y constantes
Tipos de datos
Herencia
Operadores
Uso de consultas LINQ
Clases y encapsulación
Uso de consultas LINQ
Excepciones
Control de flujo
Eventos
Diccionarios
Tipos de datos
Conjuntos, colas y pilas
Lambdas
Conjuntos, colas y pilas
Uso de async y await
Tareas
Constructores y destructores
Operadores
Arrays y listas
Polimorfismo
Polimorfismo
Variables y constantes
Proyecto colecciones y LINQ en C#
Clases y encapsulación
Creación de proyecto C#
Uso de async y await
Funciones
Delegados
Delegados
Constructores y destructores
Objetos
Control de flujo
Funciones
Tareas
Proyecto sintaxis en C#
Herencia C Sharp
OOP en C Sharp
Diccionarios
Introducción a C#
Todas las lecciones de CSharp
Accede a todas las lecciones de CSharp y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A C#
Introducción Y Entorno
Creación De Proyecto C#
Introducción Y Entorno
Variables Y Constantes
Sintaxis
Tipos De Datos
Sintaxis
Operadores
Sintaxis
Control De Flujo
Sintaxis
Funciones
Sintaxis
Estructuras De Control Iterativo
Sintaxis
Interpolación De Strings
Sintaxis
Estructuras De Control Condicional
Sintaxis
Manejo De Valores Nulos
Sintaxis
Clases Y Encapsulación
Programación Orientada A Objetos
Objetos
Programación Orientada A Objetos
Constructores Y Destructores
Programación Orientada A Objetos
Herencia
Programación Orientada A Objetos
Polimorfismo
Programación Orientada A Objetos
Genéricos
Programación Orientada A Objetos
Métodos Virtuales Y Sobrecarga
Programación Orientada A Objetos
Clases Abstractas
Programación Orientada A Objetos
Interfaces
Programación Orientada A Objetos
Propiedades Y Encapsulación
Programación Orientada A Objetos
Métodos De Extensión
Programación Orientada A Objetos
Clases Y Objetos
Programación Orientada A Objetos
Clases Parciales
Programación Orientada A Objetos
Miembros Estáticos
Programación Orientada A Objetos
Tuplas Y Tipos Anónimos
Programación Orientada A Objetos
Arrays Y Listas
Colecciones Y Linq
Diccionarios
Colecciones Y Linq
Conjuntos, Colas Y Pilas
Colecciones Y Linq
Uso De Consultas Linq
Colecciones Y Linq
Linq Avanzado
Colecciones Y Linq
Colas Y Pilas
Colecciones Y Linq
Conjuntos
Colecciones Y Linq
Linq Básico
Colecciones Y Linq
Delegados Funcionales
Programación Funcional
Records
Programación Funcional
Expresiones Lambda
Programación Funcional
Linq Funcional
Programación Funcional
Fundamentos De La Programación Funcional
Programación Funcional
Pattern Matching
Programación Funcional
Testing Unitario Con Xunit
Testing
Excepciones
Excepciones
Delegados
Programación Asíncrona
Eventos
Programación Asíncrona
Lambdas
Programación Asíncrona
Uso De Async Y Await
Programación Asíncrona
Tareas
Programación Asíncrona
En esta lección
Objetivos de aprendizaje de esta lección
- Identificar y utilizar los tipos primitivos en C# y sus características.
- Comprender la diferencia entre tipos de valor y tipos de referencia y su comportamiento en memoria.
- Aplicar conversiones implícitas y explícitas entre tipos de datos.
- Utilizar métodos seguros de conversión como Convert, Parse y TryParse.
- Entender el uso de modificadores ref y out, así como tipos nulables y conversiones personalizadas.