C# 在C中对常量字符串使用StringBuilder?
在我当前的项目中,他们将每个SQL查询放在一个QueryList类中,每个查询都是一个不同的Get方法。 我不明白的是,他们在这些方法中使用StringBuilder来构建常量字符串,而没有在其中使用任何操作或连接。当我对此提出质疑时,他们说这比使用串联更好。 据我所知,字符串文字的串联是由编译器解决的,对吗?编译器中是否也对这种StringBuilder代码进行了优化 我在Java中看到了类似的问题,C是否也应该如此 公共静态类查询列表 { 私有静态StringBuilder GetQuery { 收到 { StringBuilder查询=新建StringBuilder; 查询。追加插入到; 追加MyTable; 查询。追加第1列、第2列; 查询、追加值; query.Append@val1、@val2; 返回查询; } } } 然后在这里它被称为 string query=QueryList.GetQuery.ToString; 我进行了一些计数,有大约700个调用此类方法,所有调用后面都是一个.ToString; 我知道当进行实际的字符串连接时会更好,但是只有34个调用需要它。 程序每秒大约有200个查询 编译器中是否对这种StringBuilder代码进行了一些优化 不,每次都会运行 不幸的是,在C语言中没有完美的方法来嵌入多行字符串文字。在这些选项中,这是我最喜欢的SQL查询选项:C# 在C中对常量字符串使用StringBuilder?,c#,string,stringbuilder,C#,String,Stringbuilder,在我当前的项目中,他们将每个SQL查询放在一个QueryList类中,每个查询都是一个不同的Get方法。 我不明白的是,他们在这些方法中使用StringBuilder来构建常量字符串,而没有在其中使用任何操作或连接。当我对此提出质疑时,他们说这比使用串联更好。 据我所知,字符串文字的串联是由编译器解决的,对吗?编译器中是否也对这种StringBuilder代码进行了优化 我在Java中看到了类似的问题,C是否也应该如此 公共静态类查询列表 { 私有静态StringBuilder GetQuery
private static string SomeQuery
{
get
{
var query = @"
INSERT INTO
MyTable (column1, column2)
values (@val1, @val2)
";
return query;
}
}
如果要返回常量字符串,只需执行以下操作:
// I've removed "Get" from the name
private const string Query =
@"INSERT INTO MyTable (
column1,
column2)
VALUES (
@val1,
@val2)";
在StringBuilder中不需要优化—使用StringBuilder不仅毫无意义,而且对代码的效率有害。相关属性将始终返回以下字符串:
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
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
请特别注意,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: 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
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条——换句话说,据说更好的代码实际上会导致两倍多的IL和更大的内存消耗
也就是说,正如其他一些答案已经指出的那样,只需使用文本常量字符串
在这里返回StringBuilder也很奇怪,除非他们打算稍后向字符串添加更多内容。如果他们不这样做,他们应该返回一个字符串。或者,在本例中,字符串常量会更好,正如@David Browne所指出的
这一点可能有点争议,但将其作为属性而不是公共常量是毫无意义的,只会增加不必要的开销。属性最终是方法调用的语法糖,而conststring允许编译器在编译时执行文本替换,而不必执行运行时方法调用
TL;DR只使用常量字符串要有效得多,因为示例中显示的代码强制程序在运行时执行编译时可以轻松完成的操作。不要这样做。常量在编译时完成。这是不必要的创建stringbuilder和执行工作。stringbuilder允许您处理大量和/或大量连接,以解决不变性问题。另外,如果这些常量真的不成功,我今天已经看到了,而且你也不需要使用连接。如果需要维护换行符/格式,只需使用逐字字符串……我编辑了我的答案,其中包含了一个反例,说明了为什么他们假设的优化实际上比备选方案效率低得多——它在运行时做一些可以在编译时轻松完成的事情。我在回答中指出,当您在编译时连接字符串文本时,任何一个好的编译器都会足够聪明地意识到它应该在编译时而不是在运行时进行连接。事实上,使用StringBuilder而不是字符串文字会导致IL的长度是我的示例的两倍多。鉴于空格在SQL查询中无关紧要,我发现将SQL行缩进以对齐更为直观。我在回答中添加了一个生成IL的示例。在我的示例中,使用StringBuilder而不是像您建议的那样执行操作会导致IL行数比仅使用字符串文本多出一倍多。我同意。所讨论的代码应该直接使用const,而不必处理不必要的属性或Str ingBuilder,因为它们只是增加了开销,没有任何好处。