Introducción: Por qué unir cadenas no es simplemente "sumar"
\n\nImagine: está escribiendo un servicio web en C# que recibe una lista de etiquetas de un usuario y debe enviarlas a la base de datos como una sola cadena separada por comas. O está generando un informe CSV para exportar. O está registrando un conjunto de identificadores. ¿Le suena familiar? La pregunta "Creating a comma separated list from IList
Podría parecer sencillo: tomar una lista de cadenas, recorrerla con un bucle y añadir comas. Pero en la práctica, esta tarea esconde varios escollos: desde comas sobrantes al final hasta una caída catastrófica del rendimiento con grandes volúmenes de datos. En este artículo no solo mostraremos soluciones listas, sino que exploraremos bajo el capó de cada una. Aprenderá cómo funcionan string.Join, StringBuilder e incluso LINQ Aggregate, y sabrá elegir la herramienta adecuada para su tarea.
¡Allá vamos!
\n\nEl problema: Una consulta simple, dificultades no evidentes
\n\nSupongamos que tenemos una colección de cadenas:
\n\nIEnumerable<string> fruits = new List<string> { "Apple", "Banana", "Cherry", "Date" };\n\nQueremos obtener: "Apple,Banana,Cherry,Date". Lo primero que se le ocurre a un principiante es escribir un bucle:
string result = "";\nbool first = true;\nforeach (var fruit in fruits)\n{\n if (!first)\n result += ",";\n result += fruit;\n first = false;\n}\n\nEste código funciona, pero lo hace terriblemente mal. ¿Por qué? Porque las cadenas en C# son inmutables. Cada operación += crea una nueva cadena en memoria, copiando el contenido de la anterior. Con 4 elementos es imperceptible, pero con 10,000 cadenas obtendrá una complejidad cuadrática O(n²) y un consumo de memoria gigantesco. Bajo el capó: miles de asignaciones y recolecciones de basura.
Obviamente, se necesita un enfoque más inteligente. Y aquí entran en escena los protagonistas principales.
\n\nMétodo 1: string.Join — el rey del rendimiento
\n\nLa forma más correcta y eficiente es usar el método estático string.Join:
string csv = string.Join(",", fruits);\nConsole.WriteLine(csv); // Apple,Banana,Cherry,Date\n\nUna línea de código y el trabajo está hecho. Echemos un vistazo bajo el capó de string.Join (en .NET Core / .NET 5+).
¿Qué sucede internamente?
\n- \n
- Cálculo de longitud: El método primero recorre toda la colección (si es
IEnumerable<T>) para conocer el número total de caracteres. Si la colección es un array o unIList, obtiene la longitud directamente medianteCount. \n - Asignación de búfer: Se asigna un único array grande de caracteres (
char[]) del tamaño necesario: suma de las longitudes de todas las cadenas + (número de cadenas - 1) * longitud del separador. \n - Copia: Todas las cadenas y separadores se copian en este búfer sin una sola asignación adicional. \n
- Creación de la cadena: A partir del búfer se crea una única cadena final. \n
Resultado: una asignación de memoria (para la cadena final) + asignación del búfer temporal. Sin cadenas intermedias. Complejidad estrictamente O(n).
\n\nPara IList<string>, el método funciona aún más rápido, ya que puede obtener directamente Count y la longitud de cada elemento mediante el indexador, sin llamar a GetEnumerator.
Método 2: StringBuilder — el viejo amigo
\n\nAntes de la aparición del optimizado string.Join (o si está atascado en una versión antigua de .NET Framework), la solución estándar era StringBuilder:
var sb = new StringBuilder();\nbool first = true;\nforeach (var fruit in fruits)\n{\n if (!first)\n sb.Append(',');\n sb