Il modo migliore per rimuovere l'ultimo carattere da una stringa creata con stringbuilder


90

Ho il seguente

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

Il problema con questo è che lo sto usando in un ciclo e ci sarà una virgola di prova. Qual è il modo migliore per rimuovere la virgola finale?

Devo modificare i dati in una stringa la sottostringa?


10
string.Join(",", yourCollection)? Modifica: aggiunto come risposta.
Vlad


@ Chris: in questo modo non hai bisogno di uno StringBuilder.
Vlad

forse puoi evitare di aggiungere la virgola invece di rimuoverla in seguito. Vedi: stackoverflow.com/questions/581448/… (risposta di Jon Skeet)
Paolo Falabella

@Vlad Sì mi dispiace, l'ho letto male; Pensavo lo stessi offrendo come suggerimento per modificare la stringa finale, non come sostituto del tutto del ciclo. (Pensavo di aver cancellato il mio commento in tempo, immagino di no!)
Chris Sinclair il

Risposte:


222

Il modo più semplice ed efficiente è eseguire questo comando:

data.Length--;

in questo modo sposti il ​​puntatore (cioè l'ultimo indice) indietro di un carattere ma non modifichi la mutabilità dell'oggetto. In effetti, anche la cancellazione di a StringBuilderè meglio farlo Length(ma in realtà usa il Clear()metodo per chiarezza invece perché è così che appare la sua implementazione):

data.Length = 0;

di nuovo, perché non cambia la tabella di allocazione. Pensa come dire, non voglio più riconoscere questi byte. Ora, anche quando chiama ToString(), non riconoscerà nulla oltre il suo Length, beh, non può. È un oggetto mutevole che alloca più spazio di quello che gli fornisci, è semplicemente costruito in questo modo.


2
re data.Length = 0;: è esattamente ciò che StringBuilder.Clearfa, quindi è preferibile utilizzare StringBuilder.Clearper chiarezza di intenzione.
Eren Ersönmez

@ ErenErsönmez, amico abbastanza onesto, avrei dovuto dire più chiaramente che è quello che Clear()fa, ma cosa divertente. Questa è la prima riga del Clear()metodo. Ma, lo sapevate che l'interfaccia in realtà poi emette un return this;. Questa è la cosa che mi uccide. Impostando le Length = 0modifiche il riferimento che hai già, perché tornare tu stesso?
Mike Perrenoud

12
Penso che sia per essere in grado di utilizzare in modo "fluente". Appendritorna anche se stesso.
Eren Ersönmez

43

Basta usare

string.Join(",", yourCollection)

In questo modo non avrai bisogno StringBuilderdel ciclo e.




Aggiunta lunga sul caso asincrono. A partire dal 2019, non è raro che i dati arrivino in modo asincrono.

Nel caso in cui i tuoi dati siano in una raccolta asincrona, non si verifica alcun string.Joinsovraccarico IAsyncEnumerable<T>. Ma è facile crearne uno manualmente, hackerando il codice dastring.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 i dati arrivano in modo asincrono ma l'interfaccia IAsyncEnumerable<T>non è supportata (come menzionato nei commenti SqlDataReader), è relativamente facile racchiudere i dati in un 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 usalo:

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

Per Selectpoterlo utilizzare, dovrai utilizzare il pacchetto nuget System.Interactive.Async. Qui puoi trovare un esempio compilabile.


12
La risposta migliore non è quella che affronta il problema, ma quella che lo impedisce.
tribunale

1
@ Seabizkit: Certamente! L'intera domanda riguarda C #, tra l'altro.
Vlad

1
@Vlad ho capito, solo un doppio controllo mentre sto facendo un test semplice come il test grezzo e non hanno prodotto lo stesso. string.Join(",", yourCollection)ha ancora un ,alla fine. quindi il precedente ie string.Join(",", yourCollection)è inefficiente e non lo rimuove da solo.
Seabizkit

2
@ Seabizkit: molto strano! Potresti pubblicare un esempio? Nel mio codice funziona perfettamente: ideone.com/aj8PWR
Vlad

2
Anni di utilizzo di un ciclo per creare un generatore di stringhe e quindi rimuovere la virgola finale e avrei potuto usarlo. Grazie per il consiglio!
Caverman

11

Utilizzare quanto segue dopo il ciclo.

.TrimEnd(',')

o semplicemente passare a

string commaSeparatedList = input.Aggregate((a, x) => a + ", " + x)

4
Sta usando StringBuilder non string. Inoltre è abbastanza inefficace: prima conversione in corda poi rifilatura.
Piotr Stapp

oppurestring.Join(",", input)
Tvde1

10

Cosa ne pensi di questo..

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

7

Preferisco manipolare la lunghezza dello stringbuilder:

data.Length = data.Length - 1;

4
Perché non semplicemente data.Length--o --data.Length?
Gone Coding

Di solito uso data.Length, ma in un caso ho dovuto tornare indietro di 2 caratteri a causa di un valore vuoto dopo il carattere che volevo rimuovere. Trim non ha funzionato neanche in quel caso, quindi data.Length = data.Length - 2; lavorato.
Caverman

Trim restituisce una nuova istanza di una stringa, non altera il contenuto dell'oggetto
stringbuilder

@GoneCoding Visual Basic .NET non supporta --o ++ Puoi usarlo data.Length -= 1, o anche questa risposta funzionerà.
Jason S

3

Mi raccomando, cambia il tuo algoritmo di loop:

  • Aggiungi la virgola non DOPO l'elemento, ma PRIMA
  • Usa una variabile booleana, che inizia con false, sopprimi la prima virgola
  • Imposta questa variabile booleana su true dopo averla testata

2
Questo è probabilmente il meno efficiente di tutti i suggerimenti (e richiede più codice).
Gone Coding

1
Guarda @Vlad answer
Noctis

3

È necessario utilizzare il string.Joinmetodo per trasformare una raccolta di elementi in una stringa delimitata da virgole. Garantirà che non ci siano virgole iniziali o finali, oltre a garantire che la stringa sia costruita in modo efficiente (senza stringhe intermedie non necessarie).


2

Sì, convertilo in una stringa una volta terminato il ciclo:

String str = data.ToString().TrimEnd(',');

3
è abbastanza inefficace: prima conversione in corda e poi taglio.
Piotr Stapp

2
@ Garath Se intendevi "inefficiente", non sarei in disaccordo. Ma sarebbe efficace.
DonBoitnott

2

Hai due opzioni. Il primo è un Removemetodo di utilizzo molto semplice ed è abbastanza efficace. Il secondo modo è usare ToStringcon l'indice iniziale e l'indice finale ( documentazione MSDN )



1

Il modo più semplice sarebbe usare il metodo Join ():

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

Se hai davvero bisogno di StringBuilder, taglia la virgola finale dopo il ciclo:

data.ToString().TrimEnd(',');

4
data.ToString().TrimEnd(',');è inefficiente
bastos.sergio

1
Inoltre, potresti non voler convertire l'oggetto StringBuilder in String poiché potrebbe avere più righe che terminano con ","
Fandango68
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.