Módulo 06: Cadenas de texto

Objetivos del módulo

  • Dominar el tipo string y sus métodos más útiles
  • Entender la inmutabilidad de los strings
  • Usar interpolación y formateo de cadenas
  • Trabajar con StringBuilder para rendimiento
  • Conocer expresiones regulares básicas

1. El tipo string en profundidad

Ya conocemos string del módulo de tipos de datos. Ahora vamos a profundizar en todo lo que puedes hacer con él.

📘 Concepto: Un string en C# es inmutable: una vez creado, no se puede modificar. Cada vez que “modificas” un string, en realidad se crea uno nuevo en memoria. El original queda intacto.

string nombre = "Ana";
string nombreMayus = nombre.ToUpper();  // Se crea un NUEVO string

Console.WriteLine(nombre);       // "Ana"   (el original no cambia)
Console.WriteLine(nombreMayus);  // "ANA"   (es una copia nueva)

2. Métodos de string más utilizados

Búsqueda y comprobación

string texto = "Hola, bienvenido al curso de C#";

// Longitud
Console.WriteLine($"Longitud: {texto.Length}");  // 31

// Contiene
Console.WriteLine(texto.Contains("curso"));     // True
Console.WriteLine(texto.Contains("Python"));    // False

// Empieza con / Termina con
Console.WriteLine(texto.StartsWith("Hola"));    // True
Console.WriteLine(texto.EndsWith("C#"));        // True

// Posición de un substring
int pos = texto.IndexOf("curso");
Console.WriteLine($"'curso' está en posición {pos}");  // 22

int noExiste = texto.IndexOf("Java");
Console.WriteLine($"'Java' está en posición {noExiste}");  // -1 (no encontrado)

// Está vacío o nulo
string vacio = "";
string? nulo = null;
Console.WriteLine(string.IsNullOrEmpty(vacio));      // True
Console.WriteLine(string.IsNullOrEmpty(nulo));       // True
Console.WriteLine(string.IsNullOrWhiteSpace("  ")); // True (solo espacios)

Transformación

string texto = "  Hola, Mundo  ";

// Mayúsculas y minúsculas
Console.WriteLine(texto.ToUpper());    // "  HOLA, MUNDO  "
Console.WriteLine(texto.ToLower());    // "  hola, mundo  "

// Eliminar espacios
Console.WriteLine($"[{texto.Trim()}]");       // "[Hola, Mundo]"
Console.WriteLine($"[{texto.TrimStart()}]");  // "[Hola, Mundo  ]"
Console.WriteLine($"[{texto.TrimEnd()}]");    // "[  Hola, Mundo]"

// Reemplazar
string nuevo = texto.Trim().Replace("Mundo", "C#");
Console.WriteLine(nuevo);  // "Hola, C#"

// Insertar y eliminar
string base1 = "Hola Mundo";
Console.WriteLine(base1.Insert(5, "Buen "));  // "Hola Buen Mundo"
Console.WriteLine(base1.Remove(5));            // "Hola " (elimina desde índice 5)
Console.WriteLine(base1.Remove(5, 3));         // "Hola do" (elimina 3 chars desde índice 5)

Extraer partes (Substring)

string email = "usuario@correo.com";

// Substring(inicio): desde la posición hasta el final
string dominio = email.Substring(email.IndexOf('@') + 1);
Console.WriteLine(dominio);  // "correo.com"

// Substring(inicio, longitud)
string usuario = email.Substring(0, email.IndexOf('@'));
Console.WriteLine(usuario);  // "usuario"

// Forma moderna con rangos
string dominio2 = email[(email.IndexOf('@') + 1)..];
string usuario2 = email[..email.IndexOf('@')];

Dividir y unir (Split/Join)

// Split: dividir un string en un array
string csv = "Ana,Luis,María,Pedro,Carmen";
string[] nombres = csv.Split(',');

foreach (string nombre in nombres)
{
    Console.WriteLine(nombre);
}
// Ana
// Luis
// María
// Pedro
// Carmen

// Join: unir un array en un string
string unido = string.Join(" - ", nombres);
Console.WriteLine(unido);  // "Ana - Luis - María - Pedro - Carmen"

// Split con múltiples separadores
string datos = "nombre=Ana; edad=25; ciudad=Madrid";
string[] pares = datos.Split(new[] { ';', '=' }, StringSplitOptions.TrimEntries);
// { "nombre", "Ana", "edad", "25", "ciudad", "Madrid" }

// Split por líneas
string multilinea = "línea 1\nlínea 2\nlínea 3";
string[] lineas = multilinea.Split('\n');

Rellenar y alinear (PadLeft/PadRight)

// PadLeft: rellenar por la izquierda
string numero = "42";
Console.WriteLine(numero.PadLeft(5, '0'));   // "00042"
Console.WriteLine(numero.PadLeft(8));        // "      42" (rellena con espacios)

// PadRight: rellenar por la derecha
string nombre = "Ana";
Console.WriteLine($"|{nombre.PadRight(10)}|");  // "|Ana       |"

// Útil para tablas alineadas
string[] items = { "Manzana", "Pan", "Leche" };
double[] precios = { 1.50, 0.80, 1.20 };

for (int i = 0; i < items.Length; i++)
{
    Console.WriteLine($"{items[i].PadRight(12)} {precios[i],6:C2}");
}
// Manzana       1,50 €
// Pan           0,80 €
// Leche         1,20 €

3. Interpolación de cadenas

La interpolación es la forma moderna y recomendada de construir strings con variables:

string nombre = "Ana";
int edad = 25;

// Concatenación clásica (NO recomendada)
string msg1 = "Hola, " + nombre + ". Tienes " + edad + " años.";

// Interpolación (RECOMENDADA)
string msg2 = $"Hola, {nombre}. Tienes {edad} años.";

// Puedes poner expresiones dentro de { }
string msg3 = $"El doble de tu edad es {edad * 2}";
string msg4 = $"En mayúsculas: {nombre.ToUpper()}";

// Formato numérico dentro de interpolación
double precio = 1234.5;
Console.WriteLine($"Precio: {precio:C2}");     // Moneda: 1.234,50 €
Console.WriteLine($"Precio: {precio:N2}");     // Número: 1.234,50
Console.WriteLine($"Precio: {precio:F2}");     // Fixed: 1234,50
Console.WriteLine($"Porcentaje: {0.256:P1}");  // 25,6 %

// Alineación
Console.WriteLine($"|{"Izquierda",-15}|{"Derecha",15}|");
// |Izquierda      |       Derecha|

Formatos numéricos

Formato Descripción Ejemplo (1234.5)
C / C2 Moneda 1.234,50 €
N / N2 Número con separadores 1.234,50
F / F2 Decimal fijo 1234,50
P / P1 Porcentaje 123.450,0 %
E Notación científica 1,234500E+003
D5 Entero con ceros a la izquierda 01234
X Hexadecimal 4D2

Formatos de fecha

DateTime ahora = DateTime.Now;

Console.WriteLine($"Completa: {ahora}");
Console.WriteLine($"Solo fecha: {ahora:d}");          // 30/03/2026
Console.WriteLine($"Fecha larga: {ahora:D}");         // lunes, 30 de marzo de 2026
Console.WriteLine($"Solo hora: {ahora:t}");           // 14:30
Console.WriteLine($"Personalizado: {ahora:dd/MM/yyyy HH:mm}"); // 30/03/2026 14:30

4. Cadenas verbatim y raw strings

Cadenas verbatim (@)

El prefijo @ permite escribir strings sin que las barras invertidas se interpreten como secuencias de escape:

// Sin @: necesitas escapar las barras
string ruta1 = "C:\\Users\\Ana\\Documentos\\archivo.txt";

// Con @: las barras se escriben directamente
string ruta2 = @"C:\Users\Ana\Documentos\archivo.txt";

// También permite multilínea
string multilinea = @"Esta es la primera línea.
Esta es la segunda línea.
Esta es la tercera línea.";

Raw string literals (C# 11+)

Las raw strings usan triple comilla """ y son la forma más limpia de escribir texto complejo:

// Raw string literal
string json = """
    {
        "nombre": "Ana",
        "edad": 25,
        "ciudad": "Madrid"
    }
    """;

Console.WriteLine(json);

// Raw string con interpolación
string nombre = "Ana";
int edad = 25;
string jsonDinamico = $"""
    {{
        "name": "{nombre}",
        "age": {edad}
    }}
    """;

💡 Consejo: Usa raw strings (""") cuando necesites escribir JSON, XML, HTML, SQL u otro texto con muchos caracteres especiales. Son mucho más legibles que escapar cada carácter.


5. Comparación de strings

string a = "hola";
string b = "Hola";

// Comparación sensible a mayúsculas (por defecto)
Console.WriteLine(a == b);  // False

// Comparación ignorando mayúsculas
Console.WriteLine(a.Equals(b, StringComparison.OrdinalIgnoreCase));  // True
Console.WriteLine(string.Equals(a, b, StringComparison.OrdinalIgnoreCase));  // True

// Comparar para ordenar
int resultado = string.Compare(a, b, StringComparison.OrdinalIgnoreCase);
// 0 = iguales, < 0 = a va antes, > 0 = a va después

6. StringBuilder: rendimiento con muchas concatenaciones

📘 Concepto: Como los strings son inmutables, cada concatenación crea un nuevo string en memoria. Si haces muchas concatenaciones (por ejemplo, en un bucle), esto es muy ineficiente. StringBuilder resuelve esto al modificar el texto en el mismo lugar en memoria.

using System.Text;  // Necesario para StringBuilder

// MAL: concatenación en bucle (crea 1000 strings intermedios)
string resultado = "";
for (int i = 0; i < 1000; i++)
{
    resultado += i + ", ";  // Cada += crea un nuevo string
}

// BIEN: StringBuilder (modifica el mismo objeto)
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
{
    sb.Append(i);
    sb.Append(", ");
}
string resultadoFinal = sb.ToString();

Métodos de StringBuilder

StringBuilder sb = new StringBuilder();

sb.Append("Hola");           // Añadir al final
sb.Append(" mundo");
sb.AppendLine("!");          // Añadir + salto de línea
sb.AppendLine("¿Qué tal?");

sb.Insert(5, ", buen");      // Insertar en posición
sb.Replace("mundo", "mundo maravilloso");  // Reemplazar

Console.WriteLine(sb.ToString());
Console.WriteLine($"Longitud: {sb.Length}");

sb.Clear();  // Vaciar todo

💡 Consejo: Regla general: si concatenas strings menos de 5-10 veces, usa + o interpolación. Si concatenas en un bucle o más de 10 veces, usa StringBuilder.


7. Expresiones regulares básicas

Las expresiones regulares (regex) permiten buscar patrones complejos en texto:

using System.Text.RegularExpressions;

string texto = "Mi email es ana@correo.com y mi teléfono es 612345678";

// Buscar un patrón de email
bool tieneEmail = Regex.IsMatch(texto, @"[\w.]+@[\w.]+\.\w+");
Console.WriteLine($"¿Tiene email? {tieneEmail}");  // True

// Extraer el email
Match match = Regex.Match(texto, @"[\w.]+@[\w.]+\.\w+");
Console.WriteLine($"Email encontrado: {match.Value}");  // ana@correo.com

// Buscar números de teléfono (9 dígitos)
Match telefono = Regex.Match(texto, @"\d{9}");
Console.WriteLine($"Teléfono: {telefono.Value}");  // 612345678

// Validar formato
string email = "usuario@ejemplo.com";
bool emailValido = Regex.IsMatch(email, @"^[\w.]+@[\w.]+\.\w{2,}$");
Console.WriteLine($"¿Email válido? {emailValido}");  // True

// Reemplazar con regex
string censurado = Regex.Replace(texto, @"\d{9}", "***-***-***");
Console.WriteLine(censurado);

Patrones regex comunes

Patrón Significado Ejemplo
\d Un dígito (0-9) \d{3} → tres dígitos
\w Letra, dígito o _ \w+ → una o más letras
\s Espacio en blanco \s+ → uno o más espacios
. Cualquier carácter a.c → “abc”, “a1c”, etc.
+ Uno o más \d+ → uno o más dígitos
* Cero o más \d* → cero o más dígitos
? Cero o uno colou?r → “color” o “colour”
^ Inicio de cadena ^Hola → empieza con “Hola”
$ Fin de cadena com$ → termina con “com”
[abc] Cualquiera de estos caracteres [aeiou] → vocal
{n} Exactamente n veces \d{4} → exactamente 4 dígitos

8. Ejercicios

Ejercicio 1: Contador de vocales

Pide una frase al usuario y cuenta cuántas vocales tiene (sin distinguir mayúsculas/minúsculas).

Ejercicio 2: Palíndromo

Comprueba si una palabra es un palíndromo (se lee igual al derecho y al revés): “ana”, “radar”, “reconocer”.

Pista:

string invertida = new string(palabra.Reverse().ToArray());

Ejercicio 3: Cifrado César

Implementa un cifrado César simple: desplaza cada letra 3 posiciones en el alfabeto.

  • “abc” → “def”
  • “xyz” → “abc” (vuelve al inicio)

Ejercicio 4: Validador de contraseñas

Crea un programa que valide si una contraseña cumple:

  • Al menos 8 caracteres
  • Al menos una mayúscula
  • Al menos una minúscula
  • Al menos un número
  • Al menos un carácter especial (!@#$%^&*)

Ejercicio 5: Analizador de texto

Pide un texto al usuario y muestra:

  • Número de caracteres
  • Número de palabras
  • Número de frases (terminan en ., ! o ?)
  • Palabra más larga
  • Palabra más frecuente

Resumen

Método/Concepto Descripción
str.Length Longitud del string
str.ToUpper() / ToLower() Cambiar mayúsculas/minúsculas
str.Trim() Eliminar espacios al inicio y final
str.Contains("x") ¿Contiene este texto?
str.StartsWith("x") ¿Empieza con…?
str.IndexOf("x") Posición de la primera ocurrencia (-1 si no existe)
str.Replace("a", "b") Reemplazar texto
str.Split(',') Dividir en array
string.Join("-", array) Unir array en string
str.Substring(i, n) Extraer parte del string
$"Hola {var}" Interpolación
@"C:\ruta" Cadena verbatim
"""texto""" Raw string literal
StringBuilder Concatenación eficiente en bucles
Regex Búsqueda y validación con patrones