Sandbox
Sandbox

Reputation: 8178

Why lock(<integer var>) is not allowed, but Monitor.Enter(<integer var>) allowed?

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.

Upvotes: 7

Views: 5111

Answers (5)

Brian Rasmussen
Brian Rasmussen

Reputation: 116401

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.

Upvotes: 13

ShuggyCoUk
ShuggyCoUk

Reputation: 36438

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.

Upvotes: 6

jrista
jrista

Reputation: 32960

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
}

Upvotes: 4

Sean
Sean

Reputation: 4470

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.

Upvotes: 0

JaredPar
JaredPar

Reputation: 754715

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.

Upvotes: 16

Related Questions