138

I have the following

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

The problem with this is that I am using it in a loop and there will be a trailing comma. What is the best way to remove the trailing comma?

Do I have to change data to a string and then substring it?

8
  • 12
    string.Join(",", yourCollection)? Edit: added as answer. Commented Jun 20, 2013 at 13:36
  • 1
    did you try stackoverflow.com/questions/5701163/…? Commented Jun 20, 2013 at 13:37
  • 1
    @Chris: this way you don't need a StringBuilder at all. Commented Jun 20, 2013 at 13:37
  • maybe you can avoid adding the comma instead of removing it afterwards. See: stackoverflow.com/questions/581448/… (Jon Skeet's answer) Commented Jun 20, 2013 at 13:38
  • @Vlad Yeah sorry, I misread that; I thought you were offering it as a suggestion to alter the final-built string, not as a replacement for his loop altogether. (I thought I deleted my comment in time, guess not!) Commented Jun 20, 2013 at 13:38

13 Answers 13

304

The simplest and most efficient way is to perform this command:

data.Length--;

by doing this you move the pointer (i.e. last index) back one character but you don't change the mutability of the object. In fact, clearing a StringBuilder is best done with Length as well (but do actually use the Clear() method for clarity instead because that's what its implementation looks like):

data.Length = 0;

again, because it doesn't change the allocation table. Think of it like saying, I don't want to recognize these bytes anymore. Now, even when calling ToString(), it won't recognize anything past its Length, well, it can't. It's a mutable object that allocates more space than what you provide it, it's simply built this way.

Sign up to request clarification or add additional context in comments.

3 Comments

re data.Length = 0;: that's exactly what StringBuilder.Clear does, so it's preferable to use StringBuilder.Clear for clarity of intention.
@ErenErsönmez, fair enough friend, I should have more clearly stated that's what Clear() does, but funny thing. That's the first line of the Clear() method. But, did you know the interface actually then issues a return this;. Now that's the thing that kills me. Setting the Length = 0 changes the reference you already have, why return yourself?
I think it's for being able to use in a "fluent" manner. Append returns itself as well.
56

Just use

string.Join(",", yourCollection)

This way you don't need the StringBuilder and the loop.




Long addition about async case. As of 2019, it's not a rare setup when the data are coming asynchronously.

In case your data are in async collection, there is no string.Join overload taking IAsyncEnumerable<T>. But it's easy to create one manually, hacking the code from string.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();
        }
    }
}

If the data are coming asynchronously but the interface IAsyncEnumerable<T> is not supported (like the mentioned in comments SqlDataReader), it's relatively easy to wrap the data into an 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]);
}

and use it:

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

In order to use Select, you'll need to use nuget package System.Interactive.Async. Here you can find a compilable example.

Comments

18

How about this..

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

Comments

14

Use the following after the loop.

.TrimEnd(',')

or simply change to

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

2 Comments

He is using StringBuilder not string. Moreover it is quite ineffective: first conversion to string then trimming.
or string.Join(",", input)
12

I prefer manipulating the length of the stringbuilder:

data.Length = data.Length - 1;

4 Comments

Why not simply data.Length-- or --data.Length?
I usually use data.Length-- but in one case I had to move back 2 characters because of a blank value after the character I wanted to remove. Trim did not work in that case either so data.Length = data.Length - 2; worked.
Trim returns a new instance of a string, it does not alter the contents of the stringbuilder object
@GoneCoding Visual Basic .NET doesn't support -- or ++ You can use data.Length -= 1 though, or this answer will work too.
6

Gotcha!!

Most of the answers on this question won't work if you use AppendLine like below:

var builder = new StringBuilder();
builder.AppendLine("One,");
builder.Length--; // Won't work
Console.Write(builder.ToString());

builder = new StringBuilder();
builder.AppendLine("One,");
builder.Length += -1; // Won't work
Console.Write(builder.ToString());

builder = new StringBuilder();
builder.AppendLine("One,");
Console.Write(builder.TrimEnd(',')); // Won't work

Fiddle Me

WHY??? @(&**(&@!!

The issue is simple but took me a while to figure it out: Because there are 2 more invisible characters at the end CR and LF (Carriage Return and Line Feed). Therefore, you need to take away 3 last characters:

var builder = new StringBuilder();
builder.AppendLine("One,");
builder.Length -= 3; // This will work
Console.WriteLine(builder.ToString());

In Conclusion

Use Length-- or Length -= 1 if the last method you called was Append. Use Length -= 3 if you the last method you called AppendLine.

1 Comment

This will work only on non-Unix platforms, because new line characters are platform dependent. builder.Length -= 1 + Environment.NewLine.Length; has this covered.
3

I recommend, you change your loop algorithm:

  • Add the comma not AFTER the item, but BEFORE
  • Use a boolean variable, that starts with false, do suppress the first comma
  • Set this boolean variable to true after testing it

1 Comment

This is probably the least efficient of all suggestions (and requires more code).
3

You should use the string.Join method to turn a collection of items into a comma delimited string. It will ensure that there is no leading or trailing comma, as well as ensure the string is constructed efficiently (without unnecessary intermediate strings).

Comments

3

The most simple way would be to use the Join() method:

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

If you really need the StringBuilder, trim the end comma after the loop:

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

2 Comments

data.ToString().TrimEnd(','); is inefficient
Also, you may not want to convert the StringBuilder object to String as it may have multiple lines ending with ","
2

Yes, convert it to a string once the loop is done:

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

2 Comments

it is quite ineffective: first conversion to string then trimming.
@Garath If you meant "inefficient", I wouldn't disagree. But it would be effective.
2

You have two options. First one is very easy use Remove method it is quite effective. Second way is to use ToString with start index and end index (MSDN documentation)

Comments

2

Similar SO question here.

I liked the using a StringBuilder extension method.

RemoveLast Method

Comments

0

Simply shortens the stringbuilder length by 1;

 StringBuilder sb = new StringBuilder();
 sb.Length--;

i know this is not the effective way as it translates to sb = sb-1;

Alternative Effective solution

sb.Remove(starting_index, how_many_character_to_delete);

for our case it would be

sb.Remove(sb.length-1,1)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.