字符串连接和引用相等

时间:2015-04-09 18:07:01

标签: c# .net memory

在C#中,字符串是不可变和受管理的。从理论上讲,这意味着任何字符串AB的串联都会导致新缓冲区的分配,但这一切都非常模糊。当您与标识(空字符串)连接时,引用保持不变。这是编译时优化还是重载赋值运算符决定在运行时不重新分配?此外,当我修改s2的值时,运行时/编译器如何处理s1的值/分配?我的程序会指示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();
    }

5 个答案:

答案 0 :(得分:4)

  

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

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

此外,实现了String.Concat,如果你用一个null或一个空字符串连接一个字符串,它只返回另一个字符串(除非另一个字符串是null,在哪种情况下它返回一个空字符串)。您已经进行的测试演示了这一点,因为您正在使用空字符串连接非编译时字面值字符串并保持引用相等。

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

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

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

答案 1 :(得分:4)

  

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

这是运行时优化。以下是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#对包含其他数据类型的编译时常量的其他表达式的处理没有什么不同。

  

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

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

答案 2 :(得分:1)

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


s1 = s1 + "";

由编制者优化。

s1 = s1 ?? "";

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

答案 3 :(得分:1)

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

由于上述行为细节,在大多数情况下,ReferenceEquals字符串的唯一合法应用是在true结果被解释为说"字符串肯定包含相同的字符"和"假"结果说"字符串可能不包含相同的字符"。它不应该被解释为关于字符串的来源,它们是如何创建的,或类似的东西。

答案 4 :(得分:0)

  

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

都不是。它是执行该决定的Concat方法。代码实际上编译成:

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

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

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

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

  

我的程序会指示原始地址的内存   s1保持完整(并且s2继续指向那里)而relloc   对于新值发生,然后指向s1

这是正确的。