Tipos de datos en C#

En C#, los tipos de datos son fundamentales para trabajar con variables, constantes y expresiones. El lenguaje es fuertemente tipado, lo que significa que cada variable debe declararse con un tipo específico, y las operaciones entre diferentes tipos están reguladas.

Los tipos de datos en C# se dividen en tipos por valor y tipos por referencia.

Tipos de datos por Valor

Estos tipos almacenan directamente los valores en memoria y son más eficientes en términos de rendimiento. Se agrupan en:

  1. Numéricos Enteros.
  2. Numéricos de punto flotante.
  3. Carácter.
  4. Booleano.
  5. Especial.
1. Enteros (Int)

Los tipos de datos enteros tienen un rango específico basado en su tamaño en bits.

2. Punto Flotante (Float)

Almacenan números con decimales.

Tener presente que los literales de tipo float deben terminar en f y los de tipo decimal en m

3. Tipo de Carácter (Char)

Almacena un único carácter Unicode (UTF-16) y tiene un tamaño de 16 bits.

4. Booleano (Bool)

Representa valores lógicos (true o false) y tiene un tamaño de 1 bit.

5. Tipo Especial

El tipo void indica la ausencia de un valor. Se utiliza en métodos que no devuelven nada.

Tipos por Referencia

Estos tipos almacenan una referencia al valor en lugar del valor en sí.

1. Cadenas de Texto (string)

Representan texto como una secuencia de caracteres Unicode. Las cadenas son inmutables, lo que significa que no se pueden modificar después de crearse. Si realizas una operación en una cadena, se crea una nueva.

2. Referencia Nullables

Se pueden asignar a null, indicando la ausencia de un valor.

3. Arreglos

Almacenan colecciones de valores del mismo tipo.

4. Tipos Clases (class)

Representan objetos que contienen datos y comportamiento. Pueden tener métodos, propiedades, eventos y otros miembros. Cuando se copia una variable de tipo clase, ambas referencias apuntan al mismo objeto en memoria.

class Persona
{
    public string Nombre { get; set; }
}

5. Interfaces (interface)

Las interfaces definen un contrato que las clases o estructuras pueden implementar. Las variables de tipo interfaz son referencias a objetos que implementan esa interfaz.

interface ISaludo
{
    void Saludar();
}

6. Delegados (delegate)

Un delegado es un tipo que puede almacenar referencias a métodos. Se usan para implementar patrones como eventos o callbacks.

delegate void MiDelegado(string mensaje);

7. Objetos (object)

object es la clase base de todos los tipos en C#. Todos los tipos por referencia son instancias de object. Puedes almacenar cualquier tipo por referencia (e incluso tipos por valor, mediante boxing) en una variable de tipo object.

object obj = new Persona();

Nota Importante:

El comportamiento por referencia implica que los cambios realizados en el objeto a través de una referencia afectan a todas las demás referencias al mismo objeto. Sin embargo, si reasignas una variable, esta solo afecta a esa referencia, no al objeto subyacente.

8. Listas (List <T>)
  • Las listas en C# son parte del espacio de nombres System.Collections.Generic.
  • Son tipos por referencia, ya que están implementadas como clases.
  • Permiten almacenar colecciones dinámicas de elementos del tipo especificado.
  • Tienen métodos para añadir, eliminar y buscar elementos.

9. Tuplas (Tuple y ValueTuple)

En C# existen dos variantes de tuplas:

System.Tuple (clásico): Es un tipo por referencia porque es una clase. No es mutable, pero sigue siendo útil para agrupar valores.

Tuple<int, string> tupla = new Tuple<int, string>(1, "Uno");

ValueTuple (moderno): Introducido en C# 7.0. Es un tipo por valor, no por referencia, porque está implementado como una estructura (struct). Es más eficiente que Tuple.

(int Id, string Nombre) miTupla = (1, "Juan");
Console.WriteLine(miTupla.Nombre);

10. Diccionarios (Dictionary <Tkey, Tvalue>)
  • Los diccionarios son parte de System.Collections.Generic.
  • Son tipos por referencia porque están implementados como clases.
  • Permiten almacenar pares clave-valor donde cada clave es única.
  • Las claves (TKey) y los valores (TValue) pueden ser de cualquier tipo.

Soporta búsquedas rápidas de valores usando las claves.

using System.Collections.Generic;

Dictionary<int, string> diccionario = new Dictionary<int, string>();
diccionario.Add(1, "Manzana");
diccionario.Add(2, "Pera");

Nota Importante 2

Las Listas y diccionarios son tipos por referencia porque están implementados como clases en la biblioteca base de .NET (System.Collections.Generic).

Las Tuplas depende de la implementación:

  • System.Tuple por referencia (clase).
  • System.ValueTuple por valor (estructura).

Laboratorio Práctico

A fin de poner en práctica los conceptos aprendidos desarrollaremos una calculadora en C#.

Ojo, no olvides crear tu proyecto con: dotnet new console -o myapp

using System;

class Calculadora
{
    static void Main()
    {
        bool continuar = true; // Tipo por valor (bool) para controlar el ciclo
        while (continuar)
        {
            Console.WriteLine("Calculadora Básica");
            Console.WriteLine("-------------------");
            Console.WriteLine("Seleccione una operación:");
            Console.WriteLine("1. Suma");
            Console.WriteLine("2. Resta");
            Console.WriteLine("3. Multiplicación");
            Console.WriteLine("4. División");
            Console.WriteLine("5. Salir");
            
            // Tipo por referencia: lectura de entrada
            string opcion = Console.ReadLine(); 

            // Verificamos si el usuario quiere salir
            if (opcion == "5")
            {
                continuar = false;
                Console.WriteLine("¡Gracias por usar la calculadora!");
                break;
            }

            // Tipo por referencia: uso de arrays para almacenar números
            double[] numeros = ObtenerNumeros();

            double resultado = 0; // Tipo por valor (double) para el resultado

            // Switch para determinar la operación
            switch (opcion)
            {
                case "1":
                    resultado = numeros[0] + numeros[1];
                    Console.WriteLine($"Resultado de la suma: {numeros[0]} + {numeros[1]} = {resultado}");
                    break;
                case "2":
                    resultado = numeros[0] - numeros[1];
                    Console.WriteLine($"Resultado de la resta: {numeros[0]} - {numeros[1]} = {resultado}");
                    break;
                case "3":
                    resultado = numeros[0] * numeros[1];
                    Console.WriteLine($"Resultado de la multiplicación: {numeros[0]} * {numeros[1]} = {resultado}");
                    break;
                case "4":
                    if (numeros[1] != 0)
                    {
                        resultado = numeros[0] / numeros[1];
                        Console.WriteLine($"Resultado de la división: {numeros[0]} / {numeros[1]} = {resultado}");
                    }
                    else
                    {
                        Console.WriteLine("Error: No se puede dividir entre cero.");
                    }
                    break;
                default:
                    Console.WriteLine("Opción no válida. Inténtelo de nuevo.");
                    break;
            }

            Console.WriteLine(); // Salto de línea
        }
    }

    /// <summary>
    /// Método para obtener dos números del usuario.
    /// </summary>
    /// <returns>Un array de tipo double con dos números.</returns>
    static double[] ObtenerNumeros()
    {
        double[] numeros = new double[2]; // Tipo por referencia: Array

        for (int i = 0; i < 2; i++) // Tipo por valor (int) en el bucle
        {
            Console.Write($"Ingrese el número {i + 1}: ");
            while (!double.TryParse(Console.ReadLine(), out numeros[i])) // Validación robusta
            {
                Console.WriteLine("Entrada no válida. Por favor, ingrese un número.");
                Console.Write($"Ingrese el número {i + 1}: ");
            }
        }

        return numeros;
    }
}