Melhor maneira de remover o último caractere de uma string construída com stringbuilder

90

Eu tenho o seguinte

data.AppendFormat("{0},",dataToAppend);

O problema com isso é que estou usando-o em um loop e haverá uma vírgula de teste. Qual é a melhor maneira de remover a vírgula final?

Eu tenho que alterar os dados para uma string a substring dela?

Wesley Skeen
fonte
10
string.Join(",", yourCollection)? Editar: adicionado como resposta.
Vlad de
1
você tentou stackoverflow.com/questions/5701163/… ?
andreister
@Chris: desta forma, você não precisa de um StringBuilder.
Vlad de
talvez você possa evitar adicionar a vírgula em vez de removê-la depois. Veja: stackoverflow.com/questions/581448/… (resposta de Jon Skeet)
Paolo Falabella
@Vlad Sim, desculpe, eu interpretei mal; Achei que você estava oferecendo isso como uma sugestão para alterar a corda finalizada, não como um substituto para o laço completamente. (Achei que tivesse excluído meu comentário a tempo, acho que não!)
Chris Sinclair

Respostas:

222

A maneira mais simples e eficiente é executar este comando:

data.Length--;

ao fazer isso, você move o ponteiro (isto é, o último índice) um caractere para trás, mas não altera a mutabilidade do objeto. Na verdade, StringBuilderé melhor limpar um Lengthtambém (mas use o Clear()método para maior clareza, porque é assim que sua implementação se parece):

data.Length = 0;

novamente, porque não muda a tabela de alocação. Pense nisso como dizer: não quero mais reconhecer esses bytes. Agora, mesmo quando chama ToString(), ele não reconhece nada além de seu Length, bem, ele não pode. É um objeto mutável que aloca mais espaço do que o fornecido por você; ele é simplesmente construído dessa maneira.

Mike Perrenoud
fonte
2
re data.Length = 0;: isso é exatamente o que StringBuilder.Clearfaz, por isso é preferível usar StringBuilder.Clearpara maior clareza de intenção.
Eren Ersönmez de
@ErenErsönmez, justo amigo, eu deveria ter afirmado mais claramente que é o que Clear()faz, mas coisa engraçada. Essa é a primeira linha do Clear()método. Mas, você sabia que a interface realmente apresenta a return this;. Agora é isso que me mata. Definindo as Length = 0alterações da referência que você já possui, por que voltar você mesmo?
Mike Perrenoud de
12
Acho que é por poder usar de maneira "fluente". Appendretorna a si mesmo também.
Eren Ersönmez
43

Apenas use

string.Join(",", yourCollection)

Dessa forma, você não precisa do StringBuildere do loop.




Longa adição sobre o caso assíncrono. A partir de 2019, não é uma configuração rara quando os dados chegam de forma assíncrona.

No caso de seus dados estarem em coleta assíncrona, não há string.Joinsobrecarga IAsyncEnumerable<T>. Mas é fácil criar um manualmente, hackeando o código destring.Join :

public static class StringEx
{
    public static async Task<string> JoinAsync<T>(string separator, IAsyncEnumerable<T> seq)
    {
        if (seq == null)
            throw new ArgumentNullException(nameof(seq));

        await using (var en = seq.GetAsyncEnumerator())
        {
            if (!await en.MoveNextAsync())
                return string.Empty;

            string firstString = en.Current?.ToString();

            if (!await en.MoveNextAsync())
                return firstString ?? string.Empty;

            // Null separator and values are handled by the StringBuilder
            var sb = new StringBuilder(256);
            sb.Append(firstString);

            do
            {
                var currentValue = en.Current;
                sb.Append(separator);
                if (currentValue != null)
                    sb.Append(currentValue);
            }
            while (await en.MoveNextAsync());
            return sb.ToString();
        }
    }
}

Se os dados estão chegando de forma assíncrona, mas a interface IAsyncEnumerable<T>não é compatível (como o mencionado nos comentários SqlDataReader), é relativamente fácil agrupar os dados em IAsyncEnumerable<T>:

async IAsyncEnumerable<(object first, object second, object product)> ExtractData(
        SqlDataReader reader)
{
    while (await reader.ReadAsync())
        yield return (reader[0], reader[1], reader[2]);
}

e usá-lo:

Task<string> Stringify(SqlDataReader reader) =>
    StringEx.JoinAsync(
        ", ",
        ExtractData(reader).Select(x => $"{x.first} * {x.second} = {x.product}"));

Para usar Select, você precisará usar o pacote nuget System.Interactive.Async. Aqui você pode encontrar um exemplo compilável.

Vlad
fonte
12
A melhor resposta não é aquela que aborda o problema, mas aquela que o evita.
Último Tribunal de
1
@Seabizkit: Claro! A propósito, toda a questão é sobre C #.
Vlad
1
@Vlad eu entendo isso, apenas verificando duas vezes, pois estou fazendo um teste simples como o teste bruto e eles não produziram o mesmo. string.Join(",", yourCollection)ainda tem um ,no final. portanto, o exemplo acima string.Join(",", yourCollection)é ineficiente e não o remove por conta própria.
Seabizkit
2
@Seabizkit: Muito estranho! Você poderia postar um exemplo? No meu código funciona perfeitamente: ideone.com/aj8PWR
Vlad
2
Anos usando um loop para construir um construtor de string e, em seguida, remover a vírgula final e eu poderia simplesmente ter usado isso. Obrigado pela dica!
Caverman
11

Use o seguinte após o loop.

.TrimEnd(',')

ou simplesmente mude para

string commaSeparatedList = input.Aggregate((a, x) => a + ", " + x)
Sam Leach
fonte
4
Ele está usando StringBuilder, não string. Além disso, é bastante ineficaz: primeiro a conversão para string e depois o corte.
Piotr Stapp de
oustring.Join(",", input)
Tvde1
10

Que tal agora..

string str = "The quick brown fox jumps over the lazy dog,";
StringBuilder sb = new StringBuilder(str);
sb.Remove(str.Length - 1, 1);
Pankaj
fonte
7

Eu prefiro manipular o comprimento do construtor de cordas:

data.Length = data.Length - 1;
bastos.sergio
fonte
4
Por que não simplesmente data.Length--ou --data.Length?
Gone Coding
Eu normalmente uso data.Length - mas em um caso eu tive que voltar 2 caracteres por causa de um valor em branco após o caractere que eu queria remover. Trim não funcionou nesse caso, então data.Length = data.Length - 2; funcionou.
Caverman
Trim retorna uma nova instância de uma string, ele não altera o conteúdo do objeto
stringbuilder
@GoneCoding Visual Basic .NET não oferece suporte --ou ++ você pode usar data.Length -= 1embora, ou esta resposta também funcionará.
Jason S
3

Eu recomendo, você muda seu algoritmo de loop:

  • Adicione a vírgula não DEPOIS do item, mas ANTES
  • Use uma variável booleana, que começa com false, suprima a primeira vírgula
  • Defina esta variável booleana como verdadeira após testá-la
Eugen Rieck
fonte
2
Esta é provavelmente a menos eficiente de todas as sugestões (e requer mais código).
Gone Coding
1
Veja a resposta de @Vlad
Noctis
3

Você deve usar o string.Joinmétodo para transformar uma coleção de itens em uma string delimitada por vírgulas. Isso garantirá que não haja vírgulas à esquerda ou à direita, bem como garantirá que a string seja construída de forma eficiente (sem strings intermediárias desnecessárias).

Servy
fonte
2

Sim, converta-o em uma string assim que o loop for concluído:

String str = data.ToString().TrimEnd(',');
DonBoitnott
fonte
3
é bastante ineficaz: primeiro a conversão para string e depois o corte.
Piotr Stapp de
2
@Garath Se você quisesse dizer "ineficiente", eu não discordo. Mas seria eficaz.
DonBoitnott de
2

Você tem duas opções. O primeiro é o Removemétodo de uso muito fácil , é bastante eficaz. A segunda maneira é usar ToStringcom índice inicial e índice final ( documentação MSDN )

Piotr Stapp
fonte
1

A maneira mais simples seria usar o método Join ():

public static void Trail()
{
    var list = new List<string> { "lala", "lulu", "lele" };
    var data = string.Join(",", list);
}

Se você realmente precisa do StringBuilder, apare a vírgula final após o loop:

data.ToString().TrimEnd(',');
Studert
fonte
4
data.ToString().TrimEnd(',');é ineficiente
bastos.sergio
1
Além disso, você pode não querer converter o objeto StringBuilder em String, pois ele pode ter várias linhas terminando com ","
Fandango68