【问题标题】:What happens when we initialize a string in C#?当我们在 C# 中初始化一个字符串时会发生什么?
【发布时间】:2015-12-07 18:26:38
【问题描述】:

请看下面的程序:

static void Main()
{
    string s1 = "Hello";
    string s2 = "Hello";
    Console.WriteLine ( ( object ) s1 == ( object ) s2 );
    Console.ReadLine();
}

这个 sn-p 的输出是“TRUE”。现在我的问题是:

  1. string s1 = "HELLO" ; 会创建一个新的字符串对象吗?如果是,如何在不调用构造函数和不使用new操作符的情况下创建新对象?

  2. 如果string s1 = "HELLO"string s2 = "HELLO"创建了两个对象,那为什么答案是TRUE??

【问题讨论】:

  • 我很惊讶一个非常简单的问题会变成关于谁在尽可能低的层次上解释问题的争论。字符串实习,中间语言。我期待一个解释汇编语言相等性的答案......
  • 虽然 OP 会为自己找到最有用的答案,但我认为假设无法使用高级语义解释问题是错误的。
  • 当你使用像 C# 和 .NET 这样的高级语言或框架时,你并不关心内存是如何使用的,C# 编译器如何将 C# 代码翻译成 IL,除非问题本身要求提供这些细节.除非 Object.Equals 被覆盖,否则不足以告诉基于 Object.ReferenceEquals 检查任何对象是否相等? :D
  • 我在这里不喊这个,因为我已经以高级方式回答了这个问答,但是因为如果 OP 要求这个,那是因为他还是 C# 领域的新手,并且太多的细节会让他和其他寻求简单答案的未来读者感到困惑............

标签: c# string


【解决方案1】:

如果你打算比较对象引用,这样做更清楚:

Console.WriteLine ( object.ReferenceEquals(s1, s2 ));

而不是这样:

Console.WriteLine ( ( object ) s1 == ( object ) s3 ); // false

也就是说,让我们稍微重写一下您的代码:

using System;

public class Program
{
    public static void Main()
    {
        string s1 = "Hello";
        string s2 = string2();
        Console.WriteLine ( object.ReferenceEquals(s1, s2 )); // true

        string s3 = "Hel";
        s3 = s3 + "lo";

        Console.WriteLine ( object.ReferenceEquals(s1, s3 )); // false

        // This is the equivalent of the line above:
        Console.WriteLine ( ( object ) s1 == ( object ) s3 ); // also false

        Console.WriteLine (s1 == s3); // true (comparing string contents)

        s3 = string.Intern(s3);
        Console.WriteLine ( object.ReferenceEquals(s1, s3 )); // now true

        Console.ReadLine();
    }

    private static string string2()
    {
        return "Hello";
    }
}

好的,那么问题是,“为什么前两个字符串具有相同的引用”?

这个问题的答案是因为编译器保留了一个包含它迄今为止存储的所有字符串的表,如果它遇到的新字符串已经在该表中,它不会存储新字符串;相反,它使新字符串引用已在其表中的相应字符串。这称为string interning

接下来要注意的是,如果您在运行时通过连接两个字符串来创建一个新字符串,那么该新字符串与现有字符串的引用不同。创建一个全新的字符串。

但是,如果您使用== 将该字符串与另一个具有不同引用但内容相同的字符串进行比较,则将返回true。那是因为字符串== 比较了字符串的内容

以上代码中的以下行演示了这一点:

Console.WriteLine (s1, s3); // true

最后,请注意,运行时可以“实习”字符串,即使用对现有字符串的引用而不是新字符串。但是,它不会自动执行此操作。

您可以调用string.Intern() 来显式地实习一个字符串,如上面的代码所示。

【讨论】:

  • 我不确定字符串实习是否是这个问题的答案
  • @MatthewWatson:您说表达式( object ) s1 == ( object ) s2 正在使用string.Equals,因此进行的是内容比较而不是参考比较。我不确定这是否正确 - 请参阅string.Intern msdn.microsoft.com/en-us/library/… 描述中 Remarks 部分中的示例
  • @downvoter:想解释一下你认为这个答案有什么问题吗?这会很有启发性!
  • 我!几分钟前我给了你一个链接:dotnetfiddle.net/Qst0NX 我不明白向上转换对object 的引用是如何修改相等性的
  • 也许值得指出的是,虽然string 重载了== 运算符,但运算符解析发生在编译时,因此由于转换为object,重载的string.== 运算符不会' t 被使用,它是一个标准的参考比较。
【解决方案2】:

字符串 s1 = "HELLO" ;创建一个新的字符串对象?如果是,如何 它是否在不调用构造函数的情况下创建一个新对象并且 不使用 new 运算符??

是的,它不仅会创建一个新字符串,还会将其烘焙到“用户字符串”部分下的库元数据中(否则称为"string interning"),因此它可以在运行时直接从那里提取它并节省分配时间。您可以使用 ILDASM 查看它:

User Strings
-------------------------------------------------------
70000001 : ( 5) L"Hello"

还可以看到编译器在解析语法树时将其识别为StringLiteralToken

编译器知道为字符串提供的特殊语法,并允许您使用特殊的语法糖。

如果字符串 s1 = "HELLO" 和字符串 s2 = "HELLO" 创建两个对象, 那为什么答案是真的??

正如我之前在第一部分中所说,字符串文字实际上只在运行时加载。这意味着字符串将被加载一次、缓存并与自身进行比较,从而导致此引用相等性检查产生 true。

您可以在发出的 IL 中看到这一点(在 Release 模式下编译):

IL_0000:  ldstr       "Hello"
IL_0005:  ldstr       "Hello"
IL_000A:  stloc.0     // s2
IL_000B:  ldloc.0     // s2
IL_000C:  ceq    

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-12
    相关资源
    最近更新 更多