Introdução: Por que juntar strings não é apenas "somar"
\n\nImagine: você está escrevendo um serviço web em C# que recebe uma lista de tags do usuário e precisa enviá-las ao banco de dados como uma única string separada por vírgulas. Ou você está gerando um relatório CSV para exportação. Ou registrando um conjunto de identificadores. Situação familiar? A pergunta "Creating a comma separated list from IList
Parece simples, não? Pegue uma lista de strings, percorra com um loop, adicione vírgulas. Mas na prática, essa tarefa esconde várias armadilhas: desde vírgulas extras no final até uma queda catastrófica de desempenho em grandes volumes de dados. Neste artigo, não vamos apenas mostrar soluções prontas, mas vamos olhar por baixo dos panos de cada uma delas. Você aprenderá como funcionam string.Join, StringBuilder e até LINQ Aggregate, e saberá escolher a ferramenta certa para sua tarefa.
Vamos nessa!
\n\nO Problema: Uma pergunta simples, dificuldades não óbvias
\n\nSuponha que temos uma coleção de strings:
\n\nIEnumerable<string> fruits = new List<string> { "Apple", "Banana", "Cherry", "Date" };\n\nQueremos obter: "Apple,Banana,Cherry,Date". A primeira ideia de um iniciante é escrever um loop:
string result = "";\nbool first = true;\nforeach (var fruit in fruits)\n{\n if (!first)\n result += ",";\n result += fruit;\n first = false;\n}\n\nEsse código funciona, mas o faz de forma péssima. Por quê? Porque strings em C# são imutáveis. Cada operação += cria uma nova string na memória, copiando o conteúdo da anterior. Com 4 elementos é imperceptível, mas com 10.000 strings você terá complexidade quadrática O(n²) e um consumo gigantesco de memória. Internamente, são milhares de alocações e coletas de lixo.
Obviamente, precisamos de uma abordagem mais inteligente. E é aqui que entram os protagonistas.
\n\nMétodo 1: string.Join — o rei do desempenho
\n\nA maneira mais correta e eficiente é usar o método estático string.Join:
string csv = string.Join(",", fruits);\nConsole.WriteLine(csv); // Apple,Banana,Cherry,Date\n\nUma linha de código e pronto. Vamos olhar por baixo dos panos do string.Join (no .NET Core / .NET 5+).
O que acontece internamente?
\n- \n
- Cálculo do comprimento: O método primeiro percorre toda a coleção (se for
IEnumerable<T>) para saber o número total de caracteres. Se a coleção for um array ouIList, ele obtém o comprimento diretamente viaCount. \n - Alocação do buffer: Um grande array de caracteres (
char[]) é alocado com o tamanho necessário: soma dos comprimentos de todas as strings + (quantidade de strings - 1) * comprimento do separador. \n - Cópia: Todas as strings e separadores são copiados para esse buffer sem uma única alocação extra. \n
- Criação da string: A partir do buffer, é criada uma única string final. \n
Resultado: uma alocação de memória (para a string final) + alocação do buffer temporário. Nenhuma string intermediária. Complexidade estritamente O(n).
\n\nPara IList<string>, o método é ainda mais rápido, pois pode obter imediatamente o Count e o comprimento de cada elemento através do indexador, sem chamar GetEnumerator.
Método 2: StringBuilder — o velho amigo
\n\nAntes do string.Join otimizado (ou se você estiver preso a uma versão antiga do .NET Framework), a solução padrão era o StringBuilder:
var sb = new StringBuilder();\nbool first = true;\nforeach (var fruit in fruits)\n{\n if (!first)\n sb.Append(',');\n sb