CLR字符串引用不(始终)匹配

时间:2012-10-10 22:52:06

标签: c# .net .net-4.0

从Richter和this discussion,我希望任何两个“相同”的字符串都是相同的引用。但就在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会失败吗?例如,多线程?

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

5 个答案:

答案 0 :(得分:3)

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

您的上一次比较得出True。这不是因为“随意”实习,而是因为两个字符串都是从相同的字符串 literal "hello"初始化的。在那种特殊情况下,他们将被拘禁。这在Svick的linked answer中有所解释。

也没有必要。

使用String.Equals来比较字符串。

更新关于锁定问题

您需要一个单独的锁变量。通常的模式涉及

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

在包含对象(在本例中为字符串)的范围内,它应该保护。这是在refence发生变化的情况下它可以运行的唯一方法。

答案 1 :(得分:3)

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

  

在以下示例中,字符串s1的值为   “MyTest”已经被实习,因为它是程序中的文字。   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或用户输入。在这些情况下,由于不保证发生字符串实习,因此锁定不能确保一次只有一个线程在您的关键部分。

即使你可以保证你总是使用实习字符串,你仍然会有潜在的危险问题。现在任何人都可以锁定应用程序中任何位置的相同字符串引用(包括其他AppDomain)。这是坏消息。

我建议使用显式声明的锁定对象(类型为object)。如果出现线程问题,您将节省一些时间。

答案 2 :(得分:2)

要回答原始问题,字符串实习至少为only for constant strings and when you explicitly ask for it

但是,如果您希望保证特定字符串被实习,您可以致电string.Intern

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

相反,string.IsInterned会返回一个实习字符串(如果存在)。除非您已调用stringIntern且使用返回值,否则无法保证对于特定IsInterned对象您拥有该实际字符串其中一种方法。

  

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

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

如果你需要锁定字符串,我建议改为:

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

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

或者你可以让你自己的类来包装字符串并处理相等性,但这对于锁来说可能是过度的。

答案 3 :(得分:2)

This blog entry解释了原因。

简而言之,如果你的字符串没有通过ldstr分配(即它不是你代码中定义的字符串文字),它就不会在(哈希)中结束)实习字符串表,因此不会发生实习。

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

不要锁定字符串,特别是如果您尝试使用两个不同的引用变量来尝试指向相同(可能)的实际字符串。

另请注意,实习字符串存在一些缺点。由于字符串文字在程序的生命周期内不会发生变化,因此在您的程序退出之前,实习字符串不会被垃圾收集。

答案 4 :(得分:1)

使第二个案例更有趣:

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

实施非常简单。有人必须使努力识别特定字符串与字符串的另一个实例匹配。这需要时间,不可避免。时间在运行时供不应求,字符串处理需要很快。但编译器有很多时间查找匹配项,它可以使用字符串文字构建哈希表。所以基本规则是只有编译时常量字符串表达式才会被实现。