返回String或StringBuilder值?

20
如果我在一个方法中使用StringBuilder对象构建字符串,那么返回StringBuilder对象并让调用代码调用ToString(),这样做是否有意义?
return sb;

或者通过调用 ToString() 方法自己返回字符串。

return sb.ToString();

我猜这会因为返回的字符串大小不同而有所差别。在每种情况下,什么是更合适的?谢谢。

编辑: 我不打算在调用代码中进一步修改字符串,但Colin Burnett提出了一个好观点。

主要的问题是:返回StringBuilder对象还是字符串更有效率?字符串的引用将被返回还是拷贝?

11个回答

24

如果您要进一步修改字符串,则返回StringBuilder,否则返回字符串。这是一个关于API的问题。

就效率而言,由于这是一个没有具体细节的模糊/一般性问题,因此我认为可变性比性能更重要。可变性是一个API问题,允许您的API返回可修改的对象。字符串长度与此无关。

话虽如此。如果您查看Reflector中的StringBuilder.ToString:

public override string ToString()
{
    string stringValue = this.m_StringValue;
    if (this.m_currentThread != Thread.InternalGetCurrentThread())
    {
        return string.InternalCopy(stringValue);
    }
    if ((2 * stringValue.Length) < stringValue.ArrayLength)
    {
        return string.InternalCopy(stringValue);
    }
    stringValue.ClearPostNullChar();
    this.m_currentThread = IntPtr.Zero;
    return stringValue;
}

如果你使用StringBuilder进行修改,那么它将会在进行一次复制操作,这也是我认为m_currentThread的作用——在执行Append时会检查该值,如果与当前线程不匹配,则会进行复制操作。

总之,如果你不对StringBuilder进行修改,那么字符串不会被复制,而且长度对效率无影响(除非你触发了第二个if语句)。

更新

System.String是一个类,这意味着它是一个引用类型(与值类型相对)。因此,“string foo;”本质上是一个指针。(当你将一个字符串传递给一个方法时,它会传递指针而不是副本。)mscorlib中的System.String是可变的,但是在mscorlib之外就是不可变的,这也是StringBuilder可以操作字符串的原因。

因此,当调用ToString()方法时,它通过引用返回其内部字符串对象。此时,您不能修改它,因为您的代码不在mscorlib中。通过将m_currentThread字段设置为零,任何进一步的StringBuilder操作都会导致它复制字符串对象以便进行修改,而不会修改ToString()方法返回的字符串对象。考虑以下示例:

StringBuilder sb = new StringBuilder();
sb.Append("Hello ");

string foo = sb.ToString();

sb.Append("World");

string bar = sb.ToString();
如果 StringBuilder 没有进行复制,那么最终 foo 将变成 "Hello World",因为 StringBuilder 修改了它。但由于它进行了复制,所以 foo 仍然是 "Hello",而 bar 是 "Hello World"。
这是否澄清了整个返回值/引用的问题?

@SkippyFire 正在询问效率问题。 - bruno conde
1
Nick,你在纠缠细节。如果这是一个类中的私有方法,你可能会将StringBuilder传递给许多方法来构建最终字符串。你也可以使用每个方法返回一个字符串并将它们连接起来的方式来做同样的事情。SkippyFire在如何使用它方面没有明确说明。很简单:如果需要可变性,就返回可变对象;如果不需要可变性,就不要返回可变对象。 - Colin Burnett
我觉得你把这个问题搞得比必要的复杂了。所以...返回StringBuilder对象会返回一个引用,这应该是快速和简单的。但是如果在返回语句中调用.ToString()会发生什么呢?到底返回了什么,以及如何返回?我有点难以理解返回"系统"的工作原理。那么实际的字符串被返回了吗,还是返回了字符串的副本?就像当你将一个字符串作为参数传递给一个方法时,传递一个字符串会为该方法创建一个字符串的副本,对吗? - John B
@SkippyFire,返回字符串将返回堆中字符串的引用,而不是副本。它们是不可变的,但是修改字符串将导致在堆上创建一个全新的字符串,并创建一个新的引用。您应该能够安全地将它们视为值类型,即使它们不是。System.String的实现旨在为您提供最佳的两个世界。 - Michael Meadows
啊,我现在明白了。我理解了字符串的工作原理,只是不知道如何返回它们。现在完全明白了。谢谢! - John B
显示剩余4条评论

5
我认为性能不应该成为这个问题的因素。无论哪种方式,都将调用sb.ToString(),所以您将在某个地方受到影响。
更重要的问题是方法的意图和目的。如果此方法是构建器的一部分,则可以返回StringBuilder。否则,我会返回一个字符串。
如果这是公共API的一部分,我倾向于返回字符串而不是构建器。

那么,效率问题就在于,如果您要修改字符串的次数超过一次,那么最好将其以可变形式(即StringBuilder)存储,以避免复制到新的StringBuilder中进行修改。仅仅因为在某个时候您会调用ToString并不意味着您不想避免中间复制。 - Colin Burnett
1
@Colin Burnett,就性能而言,如果需要可变性,则返回 StringBuilder 是一个实用的解决方案,但不是一个好的解决方案。最好重写调用者来支持一种模式,该模式完全构建对象并将字符串的构建延迟到单个调用中。返回 StringBuilder 有两个缺点,首先,它将您与实现细节耦合在一起。其次,它回避了启用良好的面向对象编程方法,这将妨碍解决方案作为 API 的可行性(并将您锁定在过程化实现中)。 - Michael Meadows
迈克尔,我在那条评论中的唯一观点是,“有人会调用sb.ToString()”忽略了中间复制的重要性,这将影响性能。有时候,确实,你可能想要承受一些代价,以获得更好的API或者被激励去寻找除了“string或StringBuilder”之外的解决方案。 - Colin Burnett
@Colin,我认为你很少会发现字符串引起性能问题,当然你不应该让这种恐惧过早地推动你违反面向对象设计的原则(高内聚低耦合)。等待分析器告诉你它是一个问题,然后跨越性能桥梁。 - Michael Meadows
Michael,我理解你的想法,但这并不排除探索这个想法的可能性,是吗? - Colin Burnett
@Colin,就像我上面所说的,这是一个实用的解决方案,但你应该非常小心地限制这个实用决策的影响。高耦合(如果未得到缓解)就像恶性肿瘤;如果你不小心,它会扩散并在整个系统中创建可维护性问题。 - Michael Meadows

3

我认为该方法应返回sb.ToString()。如果以后围绕StringBuilder()对象的创建的逻辑发生改变,那么我觉得应该在该方法中更改,而不是在每个调用该方法并继续执行其他操作的场景中更改。


3

StringBuilder是您方法的实现细节。在性能成为问题之前,应返回字符串,此时您应该探索另一种模式(如访问者模式),以帮助您引入间接性并保护您免受内部实现决策的影响。

字符串始终存储在堆中,因此如果返回类型为字符串,则将返回引用。但是,您不能指望两个相同的字符串具有相同的引用。通常情况下,尽管它实际上是引用类型,但可以安全地将字符串视为值类型。


1

既然你不再需要修改它了

return sb.ToString();

应该是最有效的


1

这取决于您打算如何处理输出。我个人会返回一个字符串。这样,如果您需要在将来更改方法以不使用stringbuilder,则可以这样做,因为您不会被困在返回值中。

经过一段时间的思考,答案变得更加清晰。询问应该返回哪个对象的问题真正回答了这个问题。返回的对象应该是一个字符串。原因是,如果您问自己“是否有理由返回StringBuilder对象,而不是使用字符串?”那么答案是否定的。如果有理由,则返回字符串就不可能了,因为需要使用stringbuilder的方法和属性。


1
我认为这取决于字符串离开方法后的处理方式。如果你打算继续追加字符串,那么考虑返回一个 StringBuilder 对象以提高效率。如果你总是会调用 .ToString() 方法,那么最好在方法内部进行转换以实现更好的封装性。

1

在几乎所有情况下,尤其是当方法是公共 API 的一部分时,我会返回一个 string

唯一的例外是,如果您的方法只是更大的私有“构建器”过程中的一部分,并且调用代码将进行进一步操作。在这种情况下,我可能会考虑返回一个 StringBuilder


1

返回 sb.ToString()。在这个案例中,你的方法应该集中精力于手头的任务(构建字符串),而不是被返回以进一步操作。据我看来,你可能会遇到各种问题,比如没有被释放。


0

如果您需要向字符串附加更多内容并使用其他stringbuilder相关功能,请返回stringbuilder。 否则,如果您只是使用字符串本身,请返回该字符串。

还有其他更技术上的考虑,但这是最高级别的问题。


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