Skip to main content

How to Resolve Error "CS0185: 'type' is not a reference type as required by the lock statement" in C#

The Compiler Error CS0185 is a threading and type-system error. The message reads: " 'int' (or other type) is not a reference type as required by the lock statement".

In C#, the lock statement ensures that only one thread can execute a block of code at a time. To achieve this, it relies on the System.Threading.Monitor class, which works by associating a "lock token" with a specific object instance on the heap (using the object's sync block index). Value types (like int, bool, struct, or enum) do not have this header information. Therefore, you cannot use a value type as a lock key.

This guide explains why value types are invalid for locking and the standard patterns used to fix this.

Understanding Reference vs. Value Types in Locking

  • Reference Types (class, object, string): Stored on the Heap. They have a header that can store thread synchronization information. They have a distinct "Identity" (memory address).
  • Value Types (int, bool, struct): Stored on the Stack (usually). They are raw data without headers.

If C# allowed you to lock on an integer (e.g., lock(5)), the runtime would have to Box that integer (wrap it in an object). Every time you box a value, you create a new object. If Thread A locks Box(5) and Thread B locks a new Box(5), they are locking two different objects. The lock would fail to synchronize anything.

To prevent this logic trap, the compiler strictly forbids value types in lock.

Scenario 1: Locking on Primitives (int, bool)

A common mistake is trying to lock the specific variable you are trying to protect (like a counter or a boolean flag).

Example of error

public class Counter
{
private int _count = 0;

public void Increment()
{
// ⛔️ Error CS0185: 'int' is not a reference type as required by the lock statement.
// You cannot lock the data itself if the data is a value type.
lock (_count)
{
_count++;
}
}
}

Example of error (Boolean Flag)

private bool _isRunning;

public void Process()
{
// ⛔️ Error CS0185: 'bool' is not a reference type.
lock (_isRunning)
{
// ...
}
}

Solution 1: Use a Dedicated Object (The Standard Fix)

The standard pattern in .NET (prior to C# 13) is to create a private readonly object specifically to serve as the lock identifier. This object is a reference type, it is stable (never null, never changes), and it is private (so external code cannot interfere with your lock).

public class Counter
{
private int _count = 0;

// ✅ Correct: A dedicated reference type object for locking.
private readonly object _syncRoot = new object();

public void Increment()
{
// We lock the OBJECT '_syncRoot' to protect the DATA '_count'.
lock (_syncRoot)
{
_count++;
}
}
}
note

Why not lock(this)? Locking on this is valid syntax (it is a reference type), but it is considered a bad practice. Because this is publicly visible, external code could also lock on your object, leading to deadlocks. Always use a private locking object.

Solution 2: Use System.Threading.Lock (C# 13+)

Introduced in .NET 9 and C# 13, the System.Threading.Lock class is the new, preferred way to handle synchronization. It provides better performance than locking on a generic object.

Because System.Threading.Lock is a class (Reference Type), it satisfies the requirement of CS0185 perfectly.

using System.Threading;

public class ModernCounter
{
private int _count = 0;

// ✅ Correct: Using the specific Lock type (C# 13+)
private readonly Lock _gate = new Lock();

public void Increment()
{
// The compiler recognizes this pattern and generates optimized code.
lock (_gate)
{
_count++;
}
}
}

Conclusion

CS0185 prevents you from writing threading code that wouldn't work anyway (due to boxing).

  1. Don't lock data: You cannot lock int, bool, or struct variables.
  2. Create a padlock: Define a private readonly object _lock = new object();.
  3. Lock the padlock: Use lock(_lock) to protect the code block that modifies your value types.