I do not see any difference between lock statement and Monitor.Enter call.
Look more carefully. The first case copies the reference to a second local variable to ensure that it stays alive.
Notice what the C# 3.0 spec says on the subject:
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.
It’s that last bit — except that x is only evaluated once — that is the key to the behaviour. In order to ensure that x is evaluated only once we evaluate it once, store the result in a local variable, and re-use that local variable later.
In C# 4 we’ve changed the codegen so that it is now
bool entered = false;
try {
System.Threading.Monitor.Enter(x, ref entered);
...
}
finally { if (entered) System.Threading.Monitor.Exit(x); }
but again, x is only evaluated once. In your program you are evaluating the lock expression twice. Your code really should be
bool lockTaken = false;
var temp = test2;
try {
System.Threading.Monitor.Enter(temp, ref lockTaken);
test2 = null;
Console.WriteLine("Manual collect 3.");
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("Manual collect 4.");
GC.Collect();
}
finally {
System.Threading.Monitor.Exit(temp);
}
Now is it clear why this works the way it does?
(Also note that in C# 4 the Enter is inside the try, not outside as it was in C# 3.)