From Eric Lippert’s Blog:
Recall that
lock(obj){body}
was [in C# 3.0 and earlier] a syntactic sugar forvar temp = obj; Monitor.Enter(temp); try { body } finally { Monitor.Exit(temp); }
The problem here is that if the compiler generates a no-op instruction between the monitor enter and the try-protected region then it is possible for the runtime to throw a thread abort exception after the monitor enter but before the try. In that scenario, the finally never runs so the lock leaks, probably eventually deadlocking the program. It would be nice if this were impossible in unoptimized and optimized builds.
In C# 4.0 we've changed lock so that it now generates code as if it were
bool lockWasTaken = false; var temp = obj; try { Monitor.Enter(temp, ref lockWasTaken); { body } } finally { if (lockWasTaken) Monitor.Exit(temp); }
The problem now becomes someone else's problem; the implementation of Monitor.Enter takes on responsibility for atomically setting the flag in a manner that is immune to thread abort exceptions messing it up.
So everything is good now, right?
Sadly, no. [...]
On the other hand, the C# 4.0 Language Specification says:
A lock statement of the form
lock (x) ...
where x is an expression of a reference-type, is precisely equivalent to
System.Threading.Monitor.Enter(x); try { ... } finally { System.Threading.Monitor.Exit(x); }
except that x is only evaluated once.