Best way to remove the last character from a string built with stringbuilder

后端 未结 12 2297
-上瘾入骨i
-上瘾入骨i 2020-12-23 14:30

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 t

相关标签:
12条回答
  • 2020-12-23 14:41

    Gotcha!!

    Most of the answers on this thread 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.

    0 讨论(0)
  • 2020-12-23 14:45

    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.

    0 讨论(0)
  • 2020-12-23 14:49

    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)

    0 讨论(0)
  • 2020-12-23 14:49

    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(',');
    
    0 讨论(0)
  • 2020-12-23 14:53

    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.

    0 讨论(0)
  • Similar SO question here.

    I liked the using a StringBuilder extension method.

    RemoveLast Method

    0 讨论(0)
提交回复
热议问题