Meilleur moyen de supprimer le dernier caractère d'une chaîne construite avec stringbuilder

90

J'ai ce qui suit

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

Le problème avec ceci est que je l'utilise en boucle et qu'il y aura une virgule d'essai. Quelle est la meilleure façon de supprimer la virgule de fin?

Dois-je changer les données en une chaîne de la sous-chaîne?

Wesley Skeen
la source
10
string.Join(",", yourCollection)? Edit: ajouté comme réponse.
Vlad
1
avez-vous essayé stackoverflow.com/questions/5701163/... ?
andreister
@Chris: de cette façon, vous n'avez pas du tout besoin d'un StringBuilder.
Vlad
peut-être que vous pouvez éviter d'ajouter la virgule au lieu de la supprimer par la suite. Voir: stackoverflow.com/questions/581448/… (réponse de Jon Skeet)
Paolo Falabella
@Vlad Ouais désolé, j'ai mal lu cela; Je pensais que vous proposiez cela comme une suggestion pour modifier la chaîne finale construite, pas comme un remplacement pour sa boucle. (Je pensais avoir supprimé mon commentaire à temps, je suppose que non!)
Chris Sinclair

Réponses:

222

Le moyen le plus simple et le plus efficace consiste à exécuter cette commande:

data.Length--;

en faisant cela, vous déplacez le pointeur (c'est-à-dire le dernier index) en arrière d'un caractère mais vous ne changez pas la mutabilité de l'objet. En fait, il StringBuildervaut mieux effacer a Lengthaussi (mais utilisez plutôt la Clear()méthode pour plus de clarté car c'est à cela que ressemble son implémentation):

data.Length = 0;

encore une fois, car cela ne change pas la table d'allocation. Pensez-y comme si vous disiez que je ne veux plus reconnaître ces octets. Maintenant, même en appelant ToString(), il ne reconnaîtra rien après son Length, eh bien, il ne le peut pas. C'est un objet mutable qui alloue plus d'espace que ce que vous lui fournissez, il est simplement construit de cette façon.

Mike Perrenoud
la source
2
re data.Length = 0;: c'est exactement ce que StringBuilder.Clearfait, il est donc préférable d'utiliser StringBuilder.Clearpour la clarté de l'intention.
Eren Ersönmez
@ ErenErsönmez, assez juste ami, j'aurais dû dire plus clairement que c'est ce que Clear()fait, mais c'est drôle. C'est la première ligne de la Clear()méthode. Mais saviez-vous que l'interface émet alors un fichier return this;. Voilà ce qui me tue. En définissant les Length = 0modifications de la référence que vous avez déjà, pourquoi revenir vous-même?
Mike Perrenoud
12
Je pense que c'est pour pouvoir utiliser de manière "fluide". Appendse retourne également.
Eren Ersönmez
43

Juste utiliser

string.Join(",", yourCollection)

De cette façon, vous n'avez pas besoin de StringBuilderla boucle et.




Ajout long sur le cas asynchrone. À partir de 2019, ce n'est pas une configuration rare lorsque les données arrivent de manière asynchrone.

Dans le cas où vos données sont dans une collection asynchrone, il n'y a pas de string.Joinprise de surcharge IAsyncEnumerable<T>. Mais il est facile d'en créer un manuellement, en piratant le code à partir 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();
        }
    }
}

Si les données arrivent de manière asynchrone mais que l'interface IAsyncEnumerable<T>n'est pas prise en charge (comme mentionné dans les commentaires SqlDataReader), il est relativement facile d'encapsuler les données dans 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]);
}

et utilisez-le:

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

Pour l'utiliser Select, vous devrez utiliser le package nuget System.Interactive.Async. Ici vous pouvez trouver un exemple compilable.

Vlad
la source
12
La meilleure réponse n'est pas celle qui résout le problème, mais celle qui l'empêche.
LastTribunal
1
@Seabizkit: Bien sûr! Au fait, toute la question concerne C #.
Vlad
1
@Vlad je comprends cela, ne vérifiant que deux fois car je fais un test simple comme un test brut et ils ne produisent pas la même chose. string.Join(",", yourCollection)a encore un ,à la fin. donc ce qui précède string.Join(",", yourCollection)est inefficace et ne le supprime pas tout seul.
Seabizkit
2
@Seabizkit: Très étrange! Pouvez-vous poster un exemple? Dans mon code, cela fonctionne parfaitement: ideone.com/aj8PWR
Vlad
2
Des années d'utilisation d'une boucle pour créer un générateur de chaîne, puis supprimer la virgule de fin et j'aurais pu simplement l'utiliser. Merci pour le conseil!
Caverman le
11

Utilisez ce qui suit après la boucle.

.TrimEnd(',')

ou simplement changer pour

string commaSeparatedList = input.Aggregate((a, x) => a + ", " + x)
Sam Leach
la source
4
Il utilise StringBuilder et non string. De plus c'est assez inefficace: première conversion en chaîne puis rognage.
Piotr Stapp
oustring.Join(",", input)
Tvde1
10

Que dis-tu de ça..

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

Je préfère manipuler la longueur du stringbuilder:

data.Length = data.Length - 1;
bastos.sergio
la source
4
Pourquoi pas simplement data.Length--ou --data.Length?
Fini le codage le
J'utilise habituellement data.Length - mais dans un cas, j'ai dû reculer de 2 caractères à cause d'une valeur vide après le caractère que je voulais supprimer. Trim ne fonctionnait pas non plus dans ce cas donc data.Length = data.Length - 2; travaillé.
Caverman le
Trim renvoie une nouvelle instance d'une chaîne, il ne modifie pas le contenu de l'objet
stringbuilder
@GoneCoding Visual Basic .NET ne prend pas en charge --ou ++ vous pouvez utiliser data.Length -= 1cependant, ou cette réponse fonctionnera également.
Jason S
3

Je recommande, vous changez votre algorithme de boucle:

  • Ajoutez la virgule non APRÈS l'élément, mais AVANT
  • Utilisez une variable booléenne, qui commence par false, supprimez la première virgule
  • Définissez cette variable booléenne sur true après l'avoir testée
Eugen Rieck
la source
2
C'est probablement la moins efficace de toutes les suggestions (et nécessite plus de code).
Fini le codage le
1
Regardez la réponse @Vlad
Noctis
3

Vous devez utiliser la string.Joinméthode pour transformer une collection d'éléments en une chaîne délimitée par des virgules. Cela garantira qu'il n'y a pas de virgule de début ou de fin, ainsi que la construction efficace de la chaîne (sans chaînes intermédiaires inutiles).

Servy
la source
2

Oui, convertissez-le en chaîne une fois la boucle terminée:

String str = data.ToString().TrimEnd(',');
DonBoitnott
la source
3
c'est assez inefficace: première conversion en chaîne puis rognage.
Piotr Stapp
2
@Garath Si vous vouliez dire "inefficace", je ne serais pas en désaccord. Mais ce serait efficace.
DonBoitnott
2

Vous avez deux options. La première est une Removeméthode d' utilisation très facile , elle est assez efficace. La deuxième méthode consiste à utiliser ToStringavec l'index de début et l'index de fin ( documentation MSDN )

Piotr Stapp
la source
1

Le moyen le plus simple serait d'utiliser la méthode Join ():

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

Si vous avez vraiment besoin de StringBuilder, coupez la virgule de fin après la boucle:

data.ToString().TrimEnd(',');
studert
la source
4
data.ToString().TrimEnd(',');est inefficace
bastos.sergio
1
De plus, vous ne voudrez peut-être pas convertir l'objet StringBuilder en String car il peut avoir plusieurs lignes se terminant par ","
Fandango68