从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会失败吗?例如,多线程?
例如,如果我使用传递给方法的字符串参数作为执行锁定的对象,则此问题很重要。在这种情况下,参考文献最好相同!!!
答案 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
会返回一个实习字符串(如果存在)。除非您已调用string
或Intern
且使用返回值,否则无法保证对于特定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();
实施非常简单。有人必须使努力识别特定字符串与字符串的另一个实例匹配。这需要时间,不可避免。时间在运行时供不应求,字符串处理需要很快。但编译器有很多时间查找匹配项,它可以使用字符串文字构建哈希表。所以基本规则是只有编译时常量字符串表达式才会被实现。