为什么不允许锁定(<integer var>),但允许使用Monitor.Enter(<integer var>)?

San*_*box 7 c# multithreading

对于以下代码,我得到编译时错误,*

'int'不是lock语句所要求的引用类型

int i = 0;
lock(i);
Run Code Online (Sandbox Code Playgroud)

但没有错误:

int i = 0;
Monitor.Enter(i);
Run Code Online (Sandbox Code Playgroud)

据我所知,由于拳击造成的并发症,不应使用值类型进行锁定.但是,那为什么它适用于Monitor.

Jar*_*Par 15

原因是lock是一种语言结构,编译器选择在表达式上强加额外的语义.Monitor.Enter只是一个方法调用,C#编译器不会以任何方式调用特殊情况,因此它会通过正常的重载解析和装箱.


Bri*_*sen 12

你绝对不应该使用Monitor.Enterint.它工作的原因是因为它int是盒装的,所以除非你存储对盒装值的引用,否则你将锁定一个临时对象,这意味着你不能在没有Monitor.Exit异常的情况下调用.

建议的锁定方法是创建private readonly object并锁定它.对于静态方法,您可以使用private static object.


Shu*_*oUk 6

编译器的规范定义了锁的行为,如下所示:

lock语句的表达式的编译时类型应该是已知为引用类型的reference-type或> type参数(第25.1.1节).表达式的编译时类型表示值类型是编译时错误.

然后,只要它编译,它就定义了它的等价物

由于Monitor.Exit只是一个没有任何约束的方法调用,它不会阻止编译器自动装箱int并继续其快乐(和非常)错误的方式.

lock不仅仅是语法糖以同样的方式foreach不仅仅是语法糖.生成的IL转换不会呈现给代码的其余部分,就好像这是编写的那样.

foreach它是非法的修改迭代变量(尽管是有没有在输出结果代码,将防止这种情况的IL级别).在锁定中,编译器会阻止编译时已知的值类型,尽管导致IL不关心这一点.

顺便说一下:
从理论上讲,编译器可以"熟悉"这个(和其他)方法的深入了解,以便发现这种情况发生的明显情况,但从根本上说,在编译时总是不可能发现这一点(考虑传入的对象)从另一种方法,装配或通过反射)所以打扰发现任何这样的实例可能会适得其反.

编译器对API内部的了解越多,如果您希望将来更改API,您将遇到的问题就越多.

例如,可以添加Monitor.Enter()的重载,该重载接受int并锁定在与int值关联的进程范围的互斥锁上.
这将符合监视器的规范(即使它可能是可怕的),但是会对旧编译器造成大量问题,仍然可以快速地阻止已经合法的操作.