views:

358

answers:

5

For the following code I get a compile time error, *

'int' is not a reference type as required by the lock statement

int i = 0;
lock(i);

But no errors for this:

int i = 0;
Monitor.Enter(i);

I understand that a value type shouldn't be used for locking due to the complications arising due to boxing. But, then why does it work with Monitor.

+13  A: 

The reason why is that lock is a language construct and compiler chooses to impose extra semantics on the expression. Monitor.Enter is simply a method call and the C# compiler does not special case the call in any way and hence it goes through normal overload resolution and boxing.

JaredPar
A: 

I'd say it's because Monitor.Enter() is a method call, so the compiler performs the boxing automatically, while lock() is a syntactic element, so the compiler can check and throw an error on value types.

Sean Nyman
I don't think there is any checking.Isn't lock just expanded with a Monito.Enter in a try block and Monitor.Exit in the finally block.
Sandbox
no it is expanded to that but first the compiler spots if you're being silly on the way...
ShuggyCoUk
+6  A: 

You should definitely not use Monitor.Enter on an int. The reason it works is because the int is boxed, so unless you store a reference to the boxed value, you will be locking on a temporary object, which means that you cannot call Monitor.Exit without getting an exception.

The recommended way to do locking is to create a private readonly object and lock on that. For a static method you can use a private static object.

Brian Rasmussen
+1  A: 

Just out of curiosity, what are you doing with the variable 'i' that requires it to be locked? It may be more efficient to use the Interlocked class if all your doing is an increment or something:

Interlocked.Increment(i); // i++ in a thread safe manner

The Interlocked class is the lightest weight thread synch tool that .NET provides, and for simple increments, decrements, reads, or exchanges, it is the best option.

If you are trying to synchronize a block of behavior, then I would simply create an object that can be used as a synchronization root:

object syncRoot = new object();

// ...

lock(syncRoot)
{
    // put synced behavior here
}
jrista
purely out of interest...am aware of Interlocked class. Thanks.
Sandbox
+3  A: 

The specification for the compiler defines the behaviour of lock like so:

The compile time type of the expression of a lock statement shall be a reference-type or a > type parameter (§25.1.1) known to be a reference type. It is a compile-time error for the compile time type of the expression to denote a value-type.

It then defines what it is equivalent to so long as it compiles

Since Monitor.Exit is just a method call without any constraints it will not prevent the compiler automatically boxing the int and going on its merry (and very) wrong way.

lock is NOT simply syntactic sugar in the same way foreach is not simply syntactic sugar. The resulting IL transform is not presented to the rest of the code as if that was what had been written.

In foreach it is illegal to modify the iteration variable (despite there being nothing at the IL level in the resulting output code that would prevent this). In lock the compiler prevents compile time known value types, again despite the resulting IL not caring about this.

As an aside:
In theory the compiler could be 'blessed' with intimate knowledge of this (and other) methods so that it spotted obvious cases of this happening but fundamentally it is impossible to always spot this at compile time (consider an object passed in from another method, assembly or via reflection) so bothering to spot any such instances would likely be counter productive.

The more the compiler knows about the internals of the API the more problems you will have if you wish to alter the API in future.

It is possible for example that an overload of Monitor.Enter() could be added which took an int and locked on a process wide mutex associated with the int value.
This would conform to the specifications of monitor (even though it would likely be hideous) but would cause massive problems for the older compiler still merrily preventing an operation which had become legal.

ShuggyCoUk
Nice answer. Thanks.
Sandbox