Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# lock single line (if statement)

Is this code locking only on if statement, or for both lines of code?

lock (LockObject)
    if (instance == null)
        instance = Instance();
like image 351
SulNR Avatar asked Aug 26 '15 07:08

SulNR


4 Answers

lock locks an entire block. Since it's not followed by curly braces ({}), it locks an implicit block - the if statement. Here, the same logic applies - if executes a block if the condition is met. Since it is also not followed by curly braces, it implicitly has a block that contains a single statement. In other words, the given code is equivalent to:

lock (LockObject) {
    if (instance == null) {
        instance = Instance();
    }
}
like image 200
Mureinik Avatar answered Oct 01 '22 12:10

Mureinik


Lock is translated by the C# compiler to Monitor.Enter and Monitor.Exit. This C# code

static void Main(string[] args)
{
    lock (LockObject)
        if (instance == null)
            instance = Instance();
    Console.WriteLine(instance == null);
}

gives the IL code below, which clearly shows that Monitor.Exit (L_0036) is called after instance is assigned (L_0026).

Both lines of code are locked.

.method private hidebysig static void Main(string[] args) cil managed
{
  .entrypoint
  .maxstack 2
  .locals init (
    [0] bool flag,
    [1] object obj2,
    [2] bool flag2)
  L_0000: nop
  L_0001: ldc.i4.0
  L_0002: stloc.0
  L_0003: ldsfld object TestLock.Program::LockObject
  L_0008: dup
  L_0009: stloc.1
  L_000a: ldloca.s flag
  L_000c: call void [mscorlib]System.Threading.Monitor::Enter(object, bool&)
  L_0011: nop
  L_0012: ldsfld object TestLock.Program::instance
  L_0017: ldnull
  L_0018: ceq
  L_001a: ldc.i4.0
  L_001b: ceq
  L_001d: stloc.2
  L_001e: ldloc.2
  L_001f: brtrue.s L_002b
  L_0021: call object TestLock.Program::Instance()
  L_0026: stsfld object TestLock.Program::instance
  L_002b: leave.s L_003d
  L_002d: ldloc.0
  L_002e: ldc.i4.0
  L_002f: ceq
  L_0031: stloc.2
  L_0032: ldloc.2
  L_0033: brtrue.s L_003c
  L_0035: ldloc.1
  L_0036: call void [mscorlib]System.Threading.Monitor::Exit(object)
  L_003b: nop
  L_003c: endfinally
  L_003d: nop
  L_003e: ldsfld object TestLock.Program::instance
  L_0043: ldnull
  L_0044: ceq
  L_0046: call void [mscorlib]System.Console::WriteLine(bool)
  L_004b: nop
  L_004c: ret
  .try L_0003 to L_002d finally handler L_002d to L_003d
}
like image 39
Joel V. Earnest-DeYoung Avatar answered Oct 01 '22 10:10

Joel V. Earnest-DeYoung


if statement can't be executed alone, it needs block after it for the true-case of expression, so, as @Mureinik already said, lock locks your entire initialization block. You can even write it like this:

lock (LockObject) if (instance == null) instance = Instance();

However, this is not recommended to write your code without curly braces in such cases as it is very confusing and hard to debug. Also note that lock statement is a syntax sugar for Monitor class usage, and your code is compiled into this:

try
{
    Monitor.Enter(LockObject);
    if (instance == null)
    {
        instance = Instance();
    }
}
finally
{
    Monitor.Exit(LockObject);
}

And I want to note that for initialization logic you can use a Lazy<T> class, which is thread-safe, and uses not so heavy constructures as Monitor, and can perform faster than your code. Code will be like this:

// field in class
Lazy<Instance> lazyInstance = new Lazy<Instance>(() => Instance());

//usage in code
var instanceValue = lazyInstance.Value;
like image 43
VMAtm Avatar answered Oct 01 '22 11:10

VMAtm


It's locking all of the code. Here { and } for if are just omitted.

like image 33
Nevca Avatar answered Oct 01 '22 11:10

Nevca