使用加号时将创建多少个 String 对象

本文关键字:多少 String 对象 创建 | 更新日期: 2023-09-27 18:22:19

在下面的代码中使用加号时将创建多少个 String 对象?

String result = "1" + "2" + "3" + "4";

如果如下所示,我会说三个字符串对象:"1"、"2"、"12"。

String result = "1" + "2";

我也知道字符串对象缓存在字符串实习生池/表中以提高性能,但这不是问题。

使用加号时将创建多少个 String 对象

令人惊讶的是,这取决于。

如果在某个方法中执行此操作:

void Foo() {
    String one = "1";
    String two = "2";
    String result = one + two + "34";
    Console.Out.WriteLine(result);
}

然后编译器似乎使用String.Concat发出代码,因为@Joachim回答(+1 给他顺便说一句(。

如果将它们定义为常量,例如:

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"(。 onetwo不会显示在生成的 IL 中。

请记住,运行时可能会有进一步的优化。 我只是按照 IL 的生产。

最后,关于实习,常量和文字被

暂留,但被寄存的值是 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();
    }
}

Chris Shain的回答非常好。作为编写字符串连接优化器的人,我只想添加两个有趣的点。

首先,串联优化器在可以安全地这样做时基本上忽略了括号和左关联性。假设您有一个返回字符串的方法 M((。如果你说:

string s = M() + "A" + "B";

然后编译器认为加法运算符是左关联的,因此这与

string s = ((M() + "A") + "B");

但是这个:

string s = "C" + "D" + M();

string s = (("C" + "D") + M());

所以这就是常量字符串M()的串联"CD"

事实上,串联优化器意识到字符串连接是关联的,并为第一个示例生成String.Concat(M(), "AB"),即使这违反了左关联性。

您甚至可以这样做:

string s = (M() + "E") + ("F" + M()));

我们仍然会生成String.Concat(M(), "EF", M()).

第二个有趣的点是 null 和空字符串被优化掉了。因此,如果您这样做:

string s = (M() + "") + (null + M());

你会得到String.Concat(M(), M())

然后提出了一个有趣的问题:这怎么办?

string s = M() + null;

我们不能将其优化为

string s = M();

因为M()可能会返回 null,但如果返回 null,String.Concat(M(), null)会返回空字符串M()。 所以我们所做的是减少

string s = M() + null;

string s = M() ?? "";

从而证明字符串串联实际上根本不需要调用String.Concat

有关此主题的进一步阅读,请参阅

为什么 String.Concat 没有针对 StringBuilder.Append 进行优化?

我在MSDN找到了答案。 一。

如何:连接多个字符串(C# 编程指南(

串联是将一个字符串附加到末尾的过程另一个字符串。连接字符串文本或字符串时常量 通过使用 + 运算符,编译器将创建一个字符串。不发生运行时串联。但是,字符串变量只能在运行时连接。在这种情况下,您应该了解各种方法的性能影响。

只有一个。 C# 编译器将折叠字符串常量,因此它基本上编译为

String result = "1234";

我怀疑这是任何标准或规范规定的。一个版本可能会做一些与另一个版本不同的事情。

首先,由于它们是静态的,编译器将能够在编译时将其优化为单个字符串。

如果它们是动态的,它们将被优化为对 String.Concat(string, string, string, string( 的单个调用。