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


115

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

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

如果如下所示,我会说三个String对象:“ 1”,“ 2”,“ 12”。

String result = "1" + "2";

我也知道String对象被缓存在String Intern Pool / Table中以提高性能,但这不是问题。


仅当您显式调用String.Intern时才对字符串进行检查。
Joe White

7
@JoeWhite:是吗?
Igor Korkhov '02

13
不完全的。所有字符串文字都将自动插入。字符串运算的结果不是。
Stefan Paul Noack

更重要的是,在OP示例中,只有一个字符串常量,并且该字符串常量是被固定的。我将更新答案以进行说明。
克里斯·谢恩

+1。对于需要以这种方式编码字符串分类的现实示例,msdn.microsoft.com / zh-cn / library /…的“示例”部分提供了一个示例,如果编译器无法对其进行优化,则该示例将无法实现。由于分配给属性参数的值受到限制,因此不能使用单个常量。
ClickRick

Answers:


161

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

如果您使用以下方法执行此操作:

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结果=“ 1” +“ 2” +三+四;其中,将两个和三个声明为字符串三=“ 3”;字符串四=“ 4” ;?
The Light

即使那样会导致一个字符串。我只是通过LinqPad对其进行了仔细检查。
克里斯·沙恩

1
@Servy-评论似乎已更新。当您更改注释时,其未标记为已更改。
安全猎犬

1
为了完整性考虑的一种很好的情况是串联连接。例如,以下代码分配了多少个字符串对象:string s = ""; for (int i = 0; i < n; i++) s += "a";
Joren 2012年

1
我使用LINQPad(linqpad.net)或Reflector(reflector.net)。前者向您显示任意代码段的IL,后者将程序集反编译为IL,并可以从该IL重新生成等效的C#。还有一个称为ILDASM的内置工具(msdn.microsoft.com/en-us/library/f7dy01k1(v=vs.80).aspx)了解IL是一件棘手的事情-请参见codebetter.com/raymondlewallen/2005/
02/07

85

克里斯·沙恩(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

有关此主题的更多信息,请参见

为什么未将String.Concat优化为StringBuilder.Append?


我认为可能有一些错误。当然会("C" + "D") + M())生成String.Concat("CD", M()),而不是String.Concat(M(), "AB")。再往下走,(M() + "E") + (null + M())应该产生String.Concat(M(), "E", M()),而不是String.Concat(M(), M())
hammar '02

21
+1为起始段落。:)这样的回答总是让我对Stack Overflow感到惊讶。
brichins 2012年

23

我在MSDN上找到了答案。之一。

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

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


22

只有一个。C#编译器将折叠字符串常量,因此实际上将其编译为

String result = "1234";

我以为只要您使用“”,它都会创建一个String对象。
The Light

1
@威廉一般是的。但是不断折叠会消除不必要的中间步骤
JaredPar'2

13

我怀疑这是任何标准或规范的要求。一个版本可能会与另一个版本有所不同。


3
它的行为至少在Microsoft的VS 2008和2010的C#编译器中有记录(请参见@ David-Stratton的答案)。就是说,您是对的-就我快速浏览所知,C#规范未指定该细节,应该将其视为实现细节。
克里斯·沙恩

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.