【问题标题】:How many String objects will be created when using a plus sign?使用加号时会创建多少个 String 对象?
【发布时间】:2012-02-26 06:55:32
【问题描述】:

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

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

如果是这样的话,我会说三个字符串对象:“1”、“2”、“12”。

String result = "1" + "2";

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

【问题讨论】:

  • 只有在显式调用 String.Intern 时才会对字符串进行实习。
  • @JoeWhite:是吗?
  • 不完全。所有字符串文字都是自动实习的。字符串运算的结果不是。
  • 更何况,在OP例子中,字符串常量只有一个,而且是interned。我会更新我的答案来说明。
  • +1。对于需要以该样式编写字符串连接的实际示例,msdn.microsoft.com/en-us/library/… 的示例部分有一个如果编译器无法将其优化为单个常量,则由于限制分配给属性参数的值。

标签: c# string clr


【解决方案1】:

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

如果你在方法中这样做:

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

然后编译器似乎使用@Joachim 回答时使用String.Concat 发出代码(顺便说一句,给他+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 次迭代,每次都会产生一个新的字符串实例:

public class Program
{
    static void Main(string[] args)
    {
        string result = "";
        for (int i = 0; i < 10; i++)
            result += "a";
        Console.ReadKey();
    }
}

但是(同样令人惊讶的是)多个连续的连接被编译器组合成一个单一的多字符串连接。例如,这个程序也只产生 12 个字符串实例!这是因为“Even if you use several + operators in one statement, the string content is copied only once.

public class Program
{
    static void Main(string[] args)
    {
        string result = "";
        for (int i = 0; i < 10; i++)
            result += "a" + result;
        Console.ReadKey();
    }
}

【讨论】:

  • String result = "1" + "2" + 三 + 四怎么样;其中二和三被声明为字符串三=“3”;字符串四 = "4";?
  • 即使这会导致一个字符串。我只是通过 LinqPad 运行它来仔细检查自己。
  • @Servy - 评论似乎已更新。当您更改评论时,它不会标记为正在更改。
  • 一个很好考虑完整性的情况是在循环中连接。例如。以下代码分配了多少个字符串对象:string s = ""; for (int i = 0; i &lt; n; i++) s += "a";
  • 我使用 LINQPad (linqpad.net) 或 Reflector (reflector.net)。前者向您显示任意 sn-ps 代码的 IL,后者将程序集反编译为 IL,并可以从该 IL 重新生成等效的 C#。还有一个名为 ILDASM 的内置工具 (msdn.microsoft.com/en-us/library/f7dy01k1(v=vs.80).aspx) 理解 IL 是一件棘手的事情 - 请参阅 codebetter.com/raymondlewallen/2005/02/07/…
【解决方案2】:

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())

第二个有趣的地方是 null 和空字符串被优化掉了。所以如果你这样做:

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

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

然后提出了一个有趣的问题:这个呢?

string s = M() + null;

我们无法将其优化到

string s = M();

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

string s = M() + null;

string s = M() ?? "";

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

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

Why is String.Concat not optimized to 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())
  • 开头段落+1。 :) 像这样的答案总是让我对 Stack Overflow 感到惊讶。
【解决方案3】:

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

How to: Concatenate Multiple Strings (C# Programming Guide)

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

【讨论】:

    【解决方案4】:

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

    String result = "1234";
    

    【讨论】:

    • 我认为每当你使用“”时,它都会创建一个字符串对象。
    • @William 通常是的。但是不断的折叠会去掉不必要的中间步骤
    【解决方案5】:

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

    【讨论】:

    • 至少对于微软的 VS 2008 和 2010 的 C# 编译器的记录行为(参见@David-Stratton 的回答)。也就是说,你是对的——据我快速阅读,C# 规范没有指定这一点,它可能应该被视为一个实现细节。
    【解决方案6】:

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

    如果它们是动态的,它们会被优化为对 String.Concat(string, string, string, string) 的一次调用。

    【讨论】:

      猜你喜欢
      • 2014-11-26
      • 1970-01-01
      • 2018-04-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-02
      • 1970-01-01
      • 2014-08-01
      相关资源
      最近更新 更多