我是否在破坏StringBuilder的效率?

dlr*_*as2 52 .net c# stringbuilder

我开始使用StringBuilder优先于直连接,但似乎它缺少一个关键的方法.所以,我自己实现了它,作为扩展:

public void Append(this StringBuilder stringBuilder, params string[] args)
{
    foreach (string arg in args)
        stringBuilder.Append(arg);
}
Run Code Online (Sandbox Code Playgroud)

这会导致以下混乱:

StringBuilder sb = new StringBuilder();
...
sb.Append(SettingNode);
sb.Append(KeyAttribute);
sb.Append(setting.Name);
Run Code Online (Sandbox Code Playgroud)

进入:

sb.Append(SettingNode, KeyAttribute, setting.Name);
Run Code Online (Sandbox Code Playgroud)

我可以使用sb.AppendFormat("{0}{1}{2}",...,但这似乎更不可取,并且仍然更难阅读.我的扩展是一个好方法,还是以某种方式破坏了它的好处StringBuilder?我不是试图过早地优化任何东西,因为我的方法更多的是关于可读性而不是速度,但我也想知道我不是在自己的脚下射击.

Jes*_*alm 69

我认为你的扩展没有问题.如果它适合你,那一切都很好.

我自己更喜欢:

sb.Append(SettingNode)
  .Append(KeyAttribute)
  .Append(setting.Name);
Run Code Online (Sandbox Code Playgroud)

  • +1这样做!如果他们把它写成"this",那么这就完全是设计师的意图. (24认同)
  • @kenny你问这个风格叫什么?AFAIK,它被称为"流畅的接口". (2认同)

Chr*_*ris 32

像这样的问题总是可以通过简单的测试用例来回答.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace SBTest
{
    class Program
    {
        private const int ITERATIONS = 1000000;

        private static void Main(string[] args)
        {
            Test1();
            Test2();
            Test3();
        }

        private static void Test1()
        {
            var sw = Stopwatch.StartNew();
            var sb = new StringBuilder();

            for (var i = 0; i < ITERATIONS; i++)
            {
                sb.Append("TEST" + i.ToString("00000"),
                          "TEST" + (i + 1).ToString("00000"),
                          "TEST" + (i + 2).ToString("00000"));
            }

            sw.Stop();
            Console.WriteLine("Testing Append() extension method...");
            Console.WriteLine("--------------------------------------------");
            Console.WriteLine("Test 1 iterations: {0:n0}", ITERATIONS);
            Console.WriteLine("Test 1 milliseconds: {0:n0}", sw.ElapsedMilliseconds);
            Console.WriteLine("Test 1 output length: {0:n0}", sb.Length);
            Console.WriteLine("");
        }

        private static void Test2()
        {
            var sw = Stopwatch.StartNew();
            var sb = new StringBuilder();

            for (var i = 0; i < ITERATIONS; i++)
            {
                sb.Append("TEST" + i.ToString("00000"));
                sb.Append("TEST" + (i+1).ToString("00000"));
                sb.Append("TEST" + (i+2).ToString("00000"));
            }

            sw.Stop();    
            Console.WriteLine("Testing multiple calls to Append() built-in method...");
            Console.WriteLine("--------------------------------------------");
            Console.WriteLine("Test 2 iterations: {0:n0}", ITERATIONS);
            Console.WriteLine("Test 2 milliseconds: {0:n0}", sw.ElapsedMilliseconds);
            Console.WriteLine("Test 2 output length: {0:n0}", sb.Length);
            Console.WriteLine("");
        }

        private static void Test3()
        {
            var sw = Stopwatch.StartNew();
            var sb = new StringBuilder();

            for (var i = 0; i < ITERATIONS; i++)
            {
                sb.AppendFormat("{0}{1}{2}",
                    "TEST" + i.ToString("00000"),
                    "TEST" + (i + 1).ToString("00000"),
                    "TEST" + (i + 2).ToString("00000"));
            }

            sw.Stop();
            Console.WriteLine("Testing AppendFormat() built-in method...");
            Console.WriteLine("--------------------------------------------");            
            Console.WriteLine("Test 3 iterations: {0:n0}", ITERATIONS);
            Console.WriteLine("Test 3 milliseconds: {0:n0}", sw.ElapsedMilliseconds);
            Console.WriteLine("Test 3 output length: {0:n0}", sb.Length);
            Console.WriteLine("");
        }
    }

    public static class SBExtentions
    {
        public static void Append(this StringBuilder sb, params string[] args)
        {
            foreach (var arg in args)
                sb.Append(arg);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在我的电脑上,输出是:

Testing Append() extension method...
--------------------------------------------
Test 1 iterations: 1,000,000
Test 1 milliseconds: 1,080
Test 1 output length: 29,700,006

Testing multiple calls to Append() built-in method...
--------------------------------------------
Test 2 iterations: 1,000,000
Test 2 milliseconds: 1,001
Test 2 output length: 29,700,006

Testing AppendFormat() built-in method...
--------------------------------------------
Test 3 iterations: 1,000,000
Test 3 milliseconds: 1,124
Test 3 output length: 29,700,006
Run Code Online (Sandbox Code Playgroud)

所以你的扩展方法只比Append()方法稍慢,并且比AppendFormat()方法略快,但在所有3种情况下,差异完全是无关紧要的.因此,如果您的扩展方法增强了代码的可读性,请使用它!

  • 我觉得每个附加的三个"TEST"+ i.ToString("00000")`会使将它附加到`StringBuilder`所需的时间相形见绌,而应该用常量字符串来运行,只是为了得到一个更好的图片. (2认同)

Jon*_*eet 9

这是创建额外阵列的一小部分开销,但我怀疑它是多少.你应该衡量

如果事实证明创建字符串数组的开销很大,可以通过多次重载来缓解它 - 一个用于两个参数,一个用于三个,一个用于四个等等...这样只有当你达到更高数量的需要创建数组的参数(例如六个或七个).重载将是这样的:

public void Append(this builder, string item1, string item2)
{
    builder.Append(item1);
    builder.Append(item2);
}

public void Append(this builder, string item1, string item2, string item3)
{
    builder.Append(item1);
    builder.Append(item2);
    builder.Append(item3);
}

public void Append(this builder, string item1, string item2,
                   string item3, string item4)
{
    builder.Append(item1);
    builder.Append(item2);
    builder.Append(item3);
    builder.Append(item4);
}

// etc
Run Code Online (Sandbox Code Playgroud)

然后使用一个最终的过载params,例如

public void Append(this builder, string item1, string item2,
                   string item3, string item4, params string[] otherItems)
{
    builder.Append(item1);
    builder.Append(item2);
    builder.Append(item3);
    builder.Append(item4);
    foreach (string item in otherItems)
    {
        builder.Append(item);
    }
}
Run Code Online (Sandbox Code Playgroud)

我当然希望这些(或者只是你原来的扩展方法)比使用更快AppendFormat- 毕竟需要解析格式字符串.

请注意,我没有让这些重载以伪递归方式相互调用 - 我怀疑它们是内联的,但是如果它们不是设置新堆栈帧的开销,那么最终可能会很重要.(我们假设数组的开销很大,如果我们到目前为止.)

  • @Lucas:使用多个参数的一些重载是为了避免每次使用带有`params`参数的方法时创建的数组的开销.是的,这可能是过度的 - 但这是一个性能问题,因此答案.(我在开始时声明它不会有太大的开销.)至于另一个调用 - 我想这不会伤害*如果*它被内联...但是否则它每次都是一个额外的堆栈帧. (3认同)