在 C# 中将 StringBuilder 用于常量字符串?

Using StringBuilder for constant strings in C#?

在我当前的项目中,他们将每个 SQL 查询放在 "QueryList" Class 中,每个查询都是不同的 Get 方法。 我不明白的是,他们在这些方法中使用 StringBuilder 来构建常量字符串,其中没有使用任何操作或连接。当我对此提出质疑时,他们说这比使用串联更好。 据我所知,字符串文字的连接是由编译器解决的,对吗?编译器中是否也对这种StringBuilder代码进行了一些优化?

我在 Java (Stringbuilder for Constants) 中看到了这个类似的问题,对于 C# 应该是一样的吗?

public static class QueryList
{
    private static StringBuilder GetQuery
    {
        get
        {
            StringBuilder query = new StringBuilder();

            query.Append(" INSERT INTO ");
            query.Append("  MyTable  ");
            query.Append("  (column1, column2)  ");
            query.Append(" VALUES ");
            query.Append("  (@val1 , @val2) ");

            return query;
        }
    }
}

然后在里面叫做

string query = QueryList.GetQuery.ToString();

我做了一些统计,大约有 700 次调用了这些方法,所有调用之后都是“.ToString();” 我知道在进行实际的字符串连接时会更好,但只有 34 个调用需要它。 程序每秒大约有 200 个查询。

Is there some optimization for this kind of StringBuilder code in the compiler

没有。每次都会运行。

没有完美的方法可以在 C# 中嵌入 multi-line 字符串文字(不幸的是)。在这些选项中,这是我最喜欢的 SQL 查询:

        private static string SomeQuery
        {
            get
            {
                var query = @"
INSERT INTO
MyTable (column1, column2)
values (@val1, @val2)
";
                return query;
            }
        }

如果你想return一个常量 string,就去做吧:

    // I've removed "Get" from the name
    private const string Query = 
      @"INSERT INTO MyTable (
          column1, 
          column2)
        VALUES (
          @val1, 
          @val2)";

StringBuilder

不需要

这里没有优化 - 使用 StringBuilder 不仅没有意义,而且对代码的效率有害。有问题的 属性 将始终 return 以下字符串:

INSERT INTO   MyTable    (column1, column2)   VALUES   (@val1 , @val2)

也就是说,如果您连接多个字符串文字,任何现代 C# 编译器都会意识到它应该在编译时而不是运行时执行此操作。例如,考虑以下代码:

void Main()
{
    string a = "a" + "b" + "c";
    string b = "def";

    Console.WriteLine(a);
    Console.WriteLine(b);
}

使用 LINQpad 5,编译为以下 IL:

IL_0000:  nop         
IL_0001:  ldstr       "abc"
IL_0006:  stloc.0     // a
IL_0007:  ldstr       "def"
IL_000C:  stloc.1     // b
IL_000D:  ldloc.0     // a
IL_000E:  call        System.Console.WriteLine
IL_0013:  nop         
IL_0014:  ldloc.1     // b
IL_0015:  call        System.Console.WriteLine
IL_001A:  nop         
IL_001B:  ret 

请特别注意 "a" + "b" + "c""def" 都产生完全相同的 IL - 换句话说,该工具足够聪明,可以意识到 "a" + "b" + "c""abc".

现在,考虑这段代码:

void Main()
{
    var a = new StringBuilder();
    a.Append("a");
    a.Append("b");
    a.Append("c");
    string b = "def";

    Console.WriteLine(a.ToString());
    Console.WriteLine(b);
}

这转换为以下 IL:

IL_0000:  nop         
IL_0001:  newobj      System.Text.StringBuilder..ctor
IL_0006:  stloc.0     // a
IL_0007:  ldloc.0     // a
IL_0008:  ldstr       "a"
IL_000D:  callvirt    System.Text.StringBuilder.Append
IL_0012:  pop         
IL_0013:  ldloc.0     // a
IL_0014:  ldstr       "b"
IL_0019:  callvirt    System.Text.StringBuilder.Append
IL_001E:  pop         
IL_001F:  ldloc.0     // a
IL_0020:  ldstr       "c"
IL_0025:  callvirt    System.Text.StringBuilder.Append
IL_002A:  pop         
IL_002B:  ldstr       "def"
IL_0030:  stloc.1     // b
IL_0031:  ldloc.0     // a
IL_0032:  callvirt    System.Object.ToString
IL_0037:  call        System.Console.WriteLine
IL_003C:  nop         
IL_003D:  ldloc.1     // b
IL_003E:  call        System.Console.WriteLine
IL_0043:  nop         
IL_0044:  ret

这是 25 条指令,而不是 12 条 - 换句话说,所谓的 "better" 代码实际上导致了两倍多的 IL 和更大的内存消耗。

也就是说,只需使用文字 const string(正如其他一些答案已经指出的那样)。

这里 return 一个 StringBuilder 也很奇怪,除非他们打算稍后向字符串添加更多内容。如果他们不这样做,他们应该 return 一个字符串。或者,在这种情况下,字符串常量会更好,正如@David Browne 指出的那样。

这一点可能更具争议性,但将其设为 属性 而不是 public 常量有点毫无意义,只会增加不必要的开销。属性最终是方法调用的语法糖,而 const string 允许编译器在编译时进行文本替换,而不必进行运行时方法调用。

TL;DR 只使用 const string 效率要高得多,因为示例中显示的代码会强制您的程序在运行时执行可以轻松完成的操作在编译时。