在下面的代码中使用加号时,将创建多少个String对象?
String result = "1" + "2" + "3" + "4";
如果如下所示,我会说三个String对象:“ 1”,“ 2”,“ 12”。
String result = "1" + "2";
我也知道String对象被缓存在String Intern Pool / Table中以提高性能,但这不是问题。
在下面的代码中使用加号时,将创建多少个String对象?
String result = "1" + "2" + "3" + "4";
如果如下所示,我会说三个String对象:“ 1”,“ 2”,“ 12”。
String result = "1" + "2";
我也知道String对象被缓存在String Intern Pool / Table中以提高性能,但这不是问题。
Answers:
令人惊讶的是,这取决于。
如果您使用以下方法执行此操作:
void Foo() {
String one = "1";
String two = "2";
String result = one + two + "34";
Console.Out.WriteLine(result);
}
那么编译器似乎使用String.Concat
@Joachim回答(顺便说一句)发出代码。
如果将它们定义为常量,例如:
const String one = "1";
const String two = "2";
const String result = one + two + "34";
或作为文字,如原始问题所示:
String result = "1" + "2" + "3" + "4";
然后编译器将优化这些+
符号。等效于:
const String result = "1234";
此外,编译器将删除无关的常量表达式,仅在使用或公开它们时才发出它们。例如,该程序:
const String one = "1";
const String two = "1";
const String result = one + two + "34";
public static void main(string[] args) {
Console.Out.WriteLine(result);
}
仅生成一个字符串-常量result
(等于“ 1234”)。 one
并且two
不会出现在最终的IL中。
请记住,运行时可能会有进一步的优化。我只是按照产生的IL进行分析。
最后,关于interning,常量和常量是interintern,但是interned的值是IL中所得的常量值,而不是常量。这意味着您可能会得到比预期更少的字符串对象,因为多个相同定义的常量或文字实际上将是同一对象!如下所示:
public class Program
{
private const String one = "1";
private const String two = "2";
private const String RESULT = one + two + "34";
static String MakeIt()
{
return "1" + "2" + "3" + "4";
}
static void Main(string[] args)
{
string result = "1" + "2" + "34";
// Prints "True"
Console.Out.WriteLine(Object.ReferenceEquals(result, MakeIt()));
// Prints "True" also
Console.Out.WriteLine(Object.ReferenceEquals(result, RESULT));
Console.ReadKey();
}
}
如果将字符串串联连接(或以动态方式串联),则每个串联最终会多出一个字符串。例如,以下代码创建12个字符串实例:2个常量+ 10次迭代,每个迭代产生一个新的String实例:
public class Program
{
static void Main(string[] args)
{
string result = "";
for (int i = 0; i < 10; i++)
result += "a";
Console.ReadKey();
}
}
但是(同样令人惊讶的是),编译器将多个连续的串联组合为单个多字符串串联。例如,该程序也只产生12个字符串实例!这是因为“ 即使在一个语句中使用多个+运算符,字符串内容也只会被复制一次。 ”
public class Program
{
static void Main(string[] args)
{
string result = "";
for (int i = 0; i < 10; i++)
result += "a" + result;
Console.ReadKey();
}
}
string s = ""; for (int i = 0; i < n; i++) s += "a";
克里斯·沙恩(Chris Shain)的回答非常好。作为编写字符串串联优化器的人,我只想添加两个有趣的观点。
第一个是,串联优化器可以安全地同时忽略括号和左关联性。假设您有一个返回字符串的方法M()。如果你说:
string s = M() + "A" + "B";
则编译器将加法运算符保留为关联关系,因此与以下内容相同:
string s = ((M() + "A") + "B");
但是这个:
string s = "C" + "D" + M();
是相同的
string s = (("C" + "D") + M());
因此,这是常量字符串 "CD"
与的串联M()
。
实际上,串联优化器意识到字符串串联是关联的,并String.Concat(M(), "AB")
为第一个示例生成,即使这违反了左关联性。
您甚至可以这样做:
string s = (M() + "E") + ("F" + M()));
并且我们仍然会生成String.Concat(M(), "EF", M())
。
第二个有趣的点是,空字符串和空字符串已被优化。因此,如果您这样做:
string s = (M() + "") + (null + M());
你会得到 String.Concat(M(), M())
然后提出了一个有趣的问题:那呢?
string s = M() + null;
我们无法将其优化到
string s = M();
因为M()
可能返回null,但是String.Concat(M(), null)
如果返回null,则将返回一个空字符串M()
。所以我们要做的是减少
string s = M() + null;
至
string s = M() ?? "";
从而证明字符串连接实际上根本不需要调用String.Concat
。
有关此主题的更多信息,请参见
("C" + "D") + M())
生成String.Concat("CD", M())
,而不是String.Concat(M(), "AB")
。再往下走,(M() + "E") + (null + M())
应该产生String.Concat(M(), "E", M())
,而不是String.Concat(M(), M())
。
我在MSDN上找到了答案。之一。
串联是将一个字符串附加到另一字符串末尾的过程。当使用+运算符连接字符串文字或字符串常量时,编译器将创建一个字符串。没有运行时串联发生。但是,字符串变量只能在运行时连接。在这种情况下,您应该了解各种方法对性能的影响。
只有一个。C#编译器将折叠字符串常量,因此实际上将其编译为
String result = "1234";
第一,由于它们是静态的,因此编译器将能够在编译时将其优化为单个字符串。
如果它们是动态的,它们将被优化为对String.Concat(string,string,string,string)的单个调用。