C# 字符串上的排除锁具有奇怪的行为

C# 字符串上的排除锁具有奇怪的行为,c#,string,locking,C#,String,Locking,我在lock语句中遇到了一个让我困惑的问题: 如果像下面这样将两个字符串与相同的表达式(“1”+“2”)连接起来,lock语句将此表达式实现为字符串,并按预期锁定: lock ("1" + "2") { Task.Factory.StartNew(() => { lock ("1" + "2") {//launched afetr 10 second

我在lock语句中遇到了一个让我困惑的问题:

如果像下面这样将两个字符串与相同的表达式(
“1”+“2”
)连接起来,lock语句将此表达式实现为字符串,并按预期锁定:

lock ("1" + "2")
{
    Task.Factory.StartNew(() =>
    {
        lock ("1" + "2")
        {//launched afetr 10 second
                        
        }
    });
    Thread.Sleep(10000);
}
但是如果第一个
锁(“1”+“2”)
var a=“1”一起改变;锁(a+“2”)

虽然两个表达式的结果相同,但lock语句将其作为两个不同的表达式处理,因此立即启动第二个lock语句:

lock ("1" + "2")
{
    Task.Factory.StartNew(() =>
    {
        var a = "1";
        lock (a + "2")
        {//launched immediately
                        
        }
    });
    Thread.Sleep(10000);
}
请解释一下这种行为:


(我知道在lock语句()中使用字符串违反了lock准则。)

如果代码更改为:

lock ("1" + "2")
{
    Console.WriteLine("outer lock");
    Task.Factory.StartNew(() =>
    {
        lock ("12")
        {//launched afetr 10 second
            Console.WriteLine("inner lock");
        }
    });
    Thread.Sleep(10000);
}
然后,“内锁”将在“外锁”后10秒打印

这意味着“1”+“2”字面上等于“12”

但是,如果使用.NET Reflector打开IL代码:

lock ("1" + "2")
{
    Console.WriteLine("outer lock");
    Task.Factory.StartNew(() =>
    {
        var a = "1";
        lock (a + "2")
        {//launched afetr 10 second
            Console.WriteLine("inner lock");
        }
    });
    Thread.Sleep(10000);
}
IL将显示外锁的以下代码

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 3
    .locals init (
        [0] bool flag,
        [1] string str,
        [2] bool flag2)
    L_0000: nop 
    L_0001: ldc.i4.0 
    L_0002: stloc.0 
    L_0003: ldstr "12"
    L_0008: dup 
    L_0009: stloc.1 
    L_000a: ldloca.s flag
    L_000c: call void [mscorlib]System.Threading.Monitor::Enter(object, bool&)
    L_0011: nop 
    L_0012: nop 
    L_0013: ldstr "outer lock"
    L_0018: call void [mscorlib]System.Console::WriteLine(string)
IL代码:

L_0003: ldstr "12"
L_0008: dup 
L_0009: stloc.1 
L_000a: ldloca.s flag
L_0001: ldstr "1"
L_0006: stloc.0 
L_0007: ldc.i4.0 
L_0008: stloc.1 
L_0009: ldloc.0 
L_000a: ldstr "2"
L_000f: call string [mscorlib]System.String::Concat(string, string)
最终将“12”保存到本地变量中

“内锁”的IL代码为

将“1”存储在局部变量中,将“2”存储在另一个局部变量中。然后调用String.Concat

如果您尝试其他代码:(在另一个控制台程序中)

您将找到第一个字符串。IsInterned(d)不返回任何内容,但返回第二个字符串。IsInterned(e)将在控制台中打印“12”

因为c++“2”并不是字面上等于“1”+“2”,而是“12”字面上等于“12”。


这意味着即使是c+“2”也会返回“12”,但在内部它们是不同的表达式。这意味着您原来的第二个“lock(a+“2”)”正试图锁定另一个表达式,这就是您的第二个代码块将立即执行的原因。

奇怪的行为是因为字符串插入。有关锁定内部字符串的更多信息,请参见具有相同值的字符串不一定位于相同的内存位置。编译器将自动将
“1”+“2”
编译为
“12”
,但
a+“2”
是在运行时创建的,因此会产生不同的字符串,即使它具有相同的值。因此你锁定了一个不同的变量。@ScottChamberlain-我不相信这与实习有任何关系。你有参考资料的链接吗?@ScottChamberlain是的,我现在才用String.Intern!请注意注释作为答案。@Enigmativity:
“1”+“2”
的编译可以是与
“12”
相同的字符串实例的唯一原因就是字符串内部。也就是说,编译器在编译时静态地将所有相同的字符串文本实习为同一个对象。这也可以在运行时完成,但无论哪种方式,实习都是行为背后的原因。
L_0001: ldstr "1"
L_0006: stloc.0 
L_0007: ldc.i4.0 
L_0008: stloc.1 
L_0009: ldloc.0 
L_000a: ldstr "2"
L_000f: call string [mscorlib]System.String::Concat(string, string)
var c = "1" + "2";
var d = c + "2";
Console.WriteLine(string.IsInterned(d));

var e = "12";
Console.WriteLine(string.IsInterned(e));