使用StringBuilder构建的字符串中删除最后一个字符的最佳方法

127

我有以下内容

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

问题在于我正在循环中使用它,会有一个尾随逗号。有什么最好的方法可以删除尾随逗号吗?

我是否需要将数据更改为字符串,然后对其进行子字符串操作?


12
string.Join(",", yourCollection) 的意思是将 yourCollection 集合中的元素用逗号连接成一个字符串。 - Vlad
1
你尝试过访问https://dev59.com/zW025IYBdhLWcg3w9quQ吗? - andreister
1
@Chris:这样你根本不需要使用 StringBuilder。 - Vlad
也许你可以避免在后面删除逗号,而是在连接字符串时就不要添加它。参考:https://dev59.com/L3RB5IYBdhLWcg3wl4EP(Jon Skeet的回答) - Paolo Falabella
@Vlad 对不起,我看错了;我以为你是建议修改最终生成的字符串,而不是完全替换他的循环。(我以为我及时删除了我的评论,看来没有!) - Chris Sinclair
显示剩余3条评论
13个回答

291

最简单且最有效率的方法是执行此命令:

data.Length--;

通过这样做,您将指针(即最后一个索引)向后移动一个字符,但不会更改对象的可变性。事实上,清除 StringBuilder 最好也使用 Length(但是为了清晰起见,请确实使用 Clear() 方法,因为它的实现看起来像这样):

data.Length = 0;

再次说明,因为它不会改变分配表。可以将其视为说,“我不想再识别这些字节了”。现在,即使调用ToString(),它也不会识别其长度后面的任何内容,实际上它也做不到。它是一个可变对象,为其分配比提供给它的空间更多的空间,它就是这样建造的。


2
关于data.Length = 0;:这正是StringBuilder.Clear所做的事情,因此为了表达意图更清晰,最好使用StringBuilder.Clear - Eren Ersönmez
@ErenErsönmez,朋友,说得好。我应该更清楚地说明Clear()的作用,但有趣的是,那是Clear()方法的第一行。但是,你知道接口实际上会发出return this;吗?现在这就是让我困扰的事情。将Length = 0设置为更改您已经拥有的引用,为什么要返回自己呢? - Mike Perrenoud
12
我认为这是为了能够“流畅”地使用。 Append方法也会返回其自身。 - Eren Ersönmez

54

只需使用

string.Join(",", yourCollection)

这种方法不需要使用StringBuilder和循环。




关于异步情况的长篇添加。截至2019年,数据以异步方式到来并不罕见。

如果您的数据在异步集合中,则没有重载string.Join接受IAsyncEnumerable<T>。但是可以轻松地手动创建一个,通过黑客攻击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();
        }
    }
}
如果数据是异步到达的,但界面不支持IAsyncEnumerable<T>(例如评论中提到的SqlDataReader),那么将数据封装到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]);
}

并使用它:

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

为了使用Select,您需要使用NuGet包System.Interactive.Async此处提供一个可编译的示例。


15

这个怎么样?

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

13

在循环后使用以下内容。

.TrimEnd(',')

或者简单地更改为
string commaSeparatedList = input.Aggregate((a, x) => a + ", " + x)

6
他正在使用 StringBuilder 而不是 string。此外,这种方法相当低效:首先将其转换为字符串,然后再修剪。 - Piotr Stapp
or string.Join(",", input) - Tvde1

11

我更喜欢操纵 StringBuilder 的长度:

data.Length = data.Length - 1;

4
为什么不直接使用 data.Length----data.Length - iCollect.it Ltd
1
我通常使用 data.Length--,但在某些情况下,由于要删除的字符后面有一个空值,我不得不向后移动2个字符。在这种情况下,Trim 也无法起作用,所以使用 data.Length = data.Length - 2; 就可以了。 - Caverman
Trim方法返回一个新的字符串实例,不会更改StringBuilder对象的内容。 - bastos.sergio
1
@GoneCoding Visual Basic .NET 不支持 --++。不过你可以使用 data.Length -= 1,或者这个答案也可以。 - Jason S

4

注意!

如果您使用以下代码中的 AppendLine,本主题上大多数答案都将无效:

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

点我试试

为什么??? @(&**(&@!!

问题很简单,但我花了一些时间才弄清楚:因为在结尾处还有两个不可见字符CRLF(回车符和换行符)。因此,您需要去掉最后3个字符:

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

总结

如果你最后调用的方法是Append,则使用 Length-- 或者 Length -= 1。如果你最后调用的方法是AppendLine,则使用Length =- 3


3
最简单的方法是使用Join()方法:
public static void Trail()
{
    var list = new List<string> { "lala", "lulu", "lele" };
    var data = string.Join(",", list);
}

如果你真的需要使用StringBuilder,在循环后删除末尾的逗号:
data.ToString().TrimEnd(',');

4
data.ToString().TrimEnd(',');不够高效。 - bastos.sergio
1
此外,您可能不想将StringBuilder对象转换为String,因为它可能有多行以","结尾。 - Fandango68

3
我建议您更改循环算法:
  • 在项目之前添加逗号,而不是之后
  • 使用布尔变量,其初始值为false,以抑制第一个逗号
  • 在测试后将此布尔变量设置为true

2
这可能是所有建议中效率最低的一个(并且需要更多的代码)。 - iCollect.it Ltd

3
你应该使用string.Join方法将一组项目转换为逗号分隔的字符串。它将确保没有前导或尾随逗号,并确保字符串的构建高效(不会产生不必要的中间字符串)。

2

是的,等循环结束后将其转换为字符串:

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

3
转换为字符串再去除空格方法相当低效。 - Piotr Stapp
3
如果你的意思是"inefficient",我不会反对。但它仍然能够起到作用。 - DonBoitnott

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接