3

从 Richter 和这个讨论中,我希望任何两个“相同”的字符串都是相同的引用。但刚才在 LINQPad 中,我在这个主题上得到了不同的结果。这是代码:

void Main()
{
    string alpha = String.Format("Hello{0}", 5);
    string brava = String.Format("Hello{0}", 5);
    ReferenceEquals(alpha, brava).Dump();
    String.IsInterned(alpha).Dump();
    String.IsInterned(brava).Dump();

    alpha = "hello";
    brava = "hello";
    ReferenceEquals(alpha, brava).Dump();
}

以下是 Dump() 调用的结果:

False
Hello5
Hello5
True

我本来希望第一个和最后一个ReferenceEquals都是True. 发生了什么?

除了上面的例子,在其他什么情况下 ReferenceEquals 会失败?例如,多线程?

例如,如果我使用传递给方法的字符串参数作为对其进行锁定的对象,则此问题很重要。在这种情况下,参考最好是相同的!!!

4

5 回答 5

3

不保证会发生字符串实习。永远不应该依赖这一点。

您最后的比较产生True. 这不是因为发生了“临时”实习,而是因为两个字符串都是从相同的字符串字面 "hello"量初始化的。在这种特殊情况下,他们将被拘留。Svick在链接的答案中对此进行了解释。

也没有真正的必要。

用于String.Equals比较字符串。

更新锁定问题

您需要一个单独的锁定变量。通常的模式包括

private /*readonly*/ object lockObject = new object();

在包含它应该保护的对象(在本例中为字符串)的范围内。这是在引用发生变化的情况下它可以稳健工作的唯一方法。

于 2012-10-10T22:56:05.870 回答
3

动态创建的字符串不会发生字符串驻留。这包括由 String.Format 和 StringBuilder 创建的那些(我相信 String.Format 在内部使用 StringBuilder)。MSDN 的String.Intern文档表明了这一点:

在以下示例中,值为“MyTest”的字符串 s1 已被实习,因为它是程序中的文字。System.Text.StringBuilder 类生成一个与 s1 具有相同值的新字符串对象。对该字符串的引用分配给 s2。Intern 方法搜索与 s2 具有相同值的字符串。因为存在这样的字符串,所以该方法返回分配给 s1 的相同引用。然后将该引用分配给 s3。引用 s1 和 s2 比较不相等,因为它们引用了不同的对象;引用 s1 和 s3 比较相等,因为它们引用相同的字符串。

string s1 = "MyTest";
string s2 = new StringBuilder().Append("My").Append("Test").ToString();  
string s3 = String.Intern(s2);  
Console.WriteLine((Object)s2==(Object)s1); //Different references. 
Console.WriteLine((Object)s3==(Object)s1); //The same reference.

需要注意的关键是,对于 CLR,您生成的字符串string.Format("Hello{0}", 5)不会被视为文字字符串,因此在加载程序集时不会发生实习。"hello"另一方面,字符串由 CLR 执行。为了实习这些字符串,您必须使用 String.Intern 显式执行此操作。

编辑

关于您的锁定问题,理论上您可以使用字符串作为锁定对象,但我认为这是不好的做法。您不知道传递给您的应用程序的字符串来自何处,因此无法保证它们是相同的引用。字符串可能来自数据库读取调用、使用 StringBuilder、使用 String.Format 或用户输入。在这些情况下,您的锁定将无法确保一次只有一个线程在您的关键部分中,因为不能保证会发生字符串驻留。

即使您可以保证始终使用实习字符串,您仍然会遇到潜在的危险问题。现在,任何人都可以在您的应用程序(包括其他 AppDomains)的任何地方锁定相同的字符串引用。这是个坏消息。

我建议有一个明确声明的锁对象(对象类型)。如果出现线程问题,您将节省大量时间来调试线程问题。

于 2012-10-10T23:25:53.750 回答
2

要回答您最初的问题,字符串实习仅适用于常量字符串,并且当您明确要求它时,至少按照该答案。

但是,如果您希望保证某个特定字符串被保留,您可以调用string.Intern.

string internedVersion = string.Intern("Some string");

相反,如果存在则string.IsInterned返回一个实习字符串。不能保证对于特定对象,您具有该实习字符串,除非您已调用或正在使用这些方法之一的返回值。stringInternIsInterned

例如,如果我使用传递给方法的字符串参数作为对其进行锁定的对象,则此问题很重要。在这种情况下,参考最好是相同的!!!

你永远不应该锁定一个字符串对象,你不知道(由于实习)这些字符串上的锁定是什么。

如果您需要锁定字符串,我建议您使用以下方法:

Dictionary<string,object> locks;
locks.Add("TEST", new object());
lock (locks["TEST"])
{
}

请注意,由于字符串提供的默认相等性,此逻辑也适用于非内部字符串。

或者,您可以创建自己的类来包装字符串并处理相等性,但这对于锁来说可能是多余的。

于 2012-10-10T23:01:49.763 回答
2

此博客条目解释了原因。

简而言之,如果您的字符串不是通过分配的ldstr(即它不是在您的代码中定义的字符串文字),它不会最终出现在实习字符串的(散列)表中,因此不会发生实习。

解决方案是调用String.Intern(str). Intern 方法使用实习生池来搜索等于 的值的字符串str。如果存在这样的字符串,则返回其在实习池中的引用。如果字符串不存在,则将对 str 的引用添加到实习池,然后返回该引用。

不要锁定字符串,尤其是当您尝试使用两个不同的引用变量来尝试指向相同(可能)的内部字符串时。

另请注意,实习字符串有一些缺点。因为字符串文字在程序的生命周期内不会发生变化,所以在程序退出之前不会对实习字符串进行垃圾收集。

于 2012-10-10T23:18:58.673 回答
1

使第二种情况更有趣:

alpha = "hello";
brava = "hell" + "o";
ReferenceEquals(alpha, brava).Dump();

实现非常简单。有人必须努力识别特定字符串与字符串的另一个实例匹配。这需要时间,不可避免。运行时时间紧缺,字符串处理需要快速。但是编译器在寻找匹配项时有其美好的时光,它可以用字符串文字构建一个哈希表。所以基本规则是只有编译时常量字符串表达式会被实习。

于 2012-10-11T00:17:44.770 回答