【问题标题】:string concatenation and reference equality字符串连接和引用相等
【发布时间】:2015-06-15 06:02:31
【问题描述】:

在 C# 中,字符串是不可变的和托管的。从理论上讲,这意味着任何字符串AB 的连接都会导致分配一个新的缓冲区,但这一切都非常模糊。当您与标识(空字符串)连接时,引用保持不变。这是编译时优化还是重载赋值运算符决定在运行时不重新分配?此外,当我修改s1 的值时,运行时/编译器如何处理s2 的值/分配?我的程序将表明s1 的原始地址处的内存保持不变(s2 继续指向那里),而新值发生 relloc,然后s1 指向那里,这是对什么的准确描述发生在幕后?

示例程序;

    static void Main(string[] args)
    {
        string s1 = "Some random text I chose";
        string s2 = s1;
        string s3 = s2;

        Console.WriteLine(Object.ReferenceEquals(s1, s2)); // true

        s1 = s1 + "";

        Console.WriteLine(Object.ReferenceEquals(s1, s2)); // true
        Console.WriteLine(s2);

        s1 = s1 + " something else";

        Console.WriteLine(Object.ReferenceEquals(s1, s2)); // false cause s1 got realloc'd
        Console.WriteLine(Object.ReferenceEquals(s2, s3));
        Console.WriteLine(s2);

        Console.ReadKey();
    }

【问题讨论】:

  • 别忘了还有字符串实习可以加入其中:-)
  • 您的问题到底是什么?这些只是对一种简单优化的一些观察。对于 s1/s2 混淆,请查找有关引用类型的教程。一张有图片。 s1 不是string
  • 添加这个:Object.ReferenceEquals("Hi Evan", "Hi Evan"); 看看结果如何。
  • @HenkHolterman 主要问题是导致该行为的优化是什么?它发生在运行时还是编译时?
  • @Cameron 好吧,是的,我认为 s2s3 的比较表明所有字符串都已被保留,s1 曾经指向那里,但现在不再指向那里。

标签: c# .net memory


【解决方案1】:

当您与标识(空字符串)连接时,引用保持不变。这是编译时优化还是重载赋值运算符决定在运行时不重新分配?

它既是编译时优化,也是在实现重载连接运算符时执行的优化。如果您连接两个编译时文字,或连接一个已知为 null 或在编译时为空的字符串,则连接在编译时完成,然后可能被实习,因此将引用等于任何其他编译时文字字符串相同的值。

此外,String.Concat 的实现方式是,如果您将一个字符串与null 或一个空字符串连接起来,它只会返回另一个字符串(除非另一个字符串是null,在这种情况下它会返回一个空字符串)细绳)。您已经拥有的测试证明了这一点,因为您将非编译时文字字符串与空字符串连接起来,并且它保持引用相等。

当然,如果您不相信自己的测试,您可以look at the source 看看,如果其中一个参数为空,那么它只会返回另一个。

if (IsNullOrEmpty(str0)) {
    if (IsNullOrEmpty(str1)) {
        return String.Empty;
    }
    return str1;
}

if (IsNullOrEmpty(str1)) {
    return str0;
}

【讨论】:

  • 补充一下,ILSpy下s1 = s1 + "";这一行看起来像s1 = (s1 ?? "");
  • 这不正确。没有重载的赋值运算符做出这样的决定,因为赋值运算符不能被重载。
  • @Guffa 是正确的,因为它与赋值运算符无关,而是+ 运算符。这也是编译器对避免调用+ 运算符而不是通过运算符本身的实现进行的优化。
  • @Servy:你在自相矛盾。在答案中,您说 OP 关于重载赋值运算符的说法是正确的,现在您说它与赋值运算符无关。
  • @Guffa 抱歉,我误读了 OP 的声明,并假设他指的是 + 运算符,而不是赋值运算符。我已经对其进行了调整。
【解决方案2】:

当您与标识(空字符串)连接时,引用保持不变。这是编译时优化还是重载的赋值运算符决定在运行时不重新分配?

这是一个运行时优化。以下是它在 Mono 中的实现方式:

public static String Concat(String str0, String str1) {
    Contract.Ensures(Contract.Result() != null);
    Contract.Ensures(Contract.Result().Length ==
        (str0 == null ? 0 : str0.Length) + 
        (str1 == null ? 0 : str1.Length));
    Contract.EndContractBlock(); 

    // ========= OPTIMIZATION BEGINS ===============
    if (IsNullOrEmpty(str0)) {
        if (IsNullOrEmpty(str1)) { 
            return String.Empty;
        }
        return str1;
    } 

    if (IsNullOrEmpty(str1)) { 
        return str0; 
    }
    // ========== OPTIMIZATION ENDS =============

    int str0Length = str0.Length;

    String result = FastAllocateString(str0Length + str1.Length);

    FillStringChecked(result, 0,        str0);
    FillStringChecked(result, str0Length, str1); 

    return result;
}

编译器可能会产生自己的其他优化 - 例如,连接两个字符串文字会在编译时产生一个新的文字值,而无需调用 string.Concat。不过,这与 C# 对包含其他数据类型的编译时常量的其他表达式的处理没有什么不同。

另外,当我修改s1的值时,运行时/编译器如何处理s2的值/分配?

s1s2 是对同一 string 对象的独立引用,该对象是不可变的。将另一个对象重新分配给其中一个对象不会更改另一个引用。

【讨论】:

    【解决方案3】:

    当您与身份(空字符串)连接时, 参考保持不变。这是编译时优化还是 重载的赋值运算符决定不重新分配 在运行时?

    两者都没有。做出该决定的是Concat 方法。代码实际编译成:

    s1 = String.Concat(s1, "");
    

    Concat 方法包含此代码,如果第二个参数为空,则返回第一个参数:

    if (IsNullOrEmpty(str1)) {
      return str0;
    }
    

    参考:Microsoft reference source: String.Concat(string, string)

    我的程序会指出原始地址的内存 s1 保持不变(并且 s2 继续指向那里),而 relloc 为新值发生,然后将 s1 指向那里

    没错。

    【讨论】:

    • 为什么投反对票?如果你不解释你认为错的地方是什么,它就无法改进答案。
    【解决方案4】:

    String.Concat 函数决定不连接字符串。它检查 s1 是否为空,如果是,则将 "" 分配给 s1。


    s1 = s1 + "";
    

    被编译器优化。

    s1 = s1 ?? "";
    

    如果您想了解更多信息,请查看this link

    【讨论】:

      【解决方案5】:

      字符串连接被指定返回一个字符串,其字符序列是由被连接事物的字符串表示封装的序列的连接。如果现有字符串不包含正确的字符序列,则需要创建一个新的连接代码;此外,即使在现有字符串可能包含正确的字符序列的情况下,计算机创建新字符串通常也比尝试查找现有字符串要快。但是,我相信,在任何可以快速找到包含正确字符的字符串的情况下,允许连接返回现有字符串,并且在将零长度字符串连接到非零长度字符串的情况下,发现包含正确字符的字符串很容易。

      由于上述行为细节,在大多数情况下,ReferenceEquals 与字符串的唯一合法应用是在 true 结果被解释为“字符串肯定包含相同的字符”和“错误”的情况下” 结果是说“字符串可能不包含相同的字符”。不应将其解释为说明字符串的来源、创建方式或类似内容。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-04-06
        • 2018-08-23
        • 1970-01-01
        • 1970-01-01
        • 2018-08-23
        • 1970-01-01
        • 2014-12-21
        相关资源
        最近更新 更多