Skip to main content

How to Resolve Error "CS0420: 'identifier': a reference to a volatile field will not be treated as volatile" in C#

The Compiler Warning CS0420 relates to multi-threading and memory semantics. The message reads: "'identifier': a reference to a volatile field will not be treated as volatile".

The volatile keyword in C# ensures that a field is accessed directly from main memory, preventing the CPU or compiler from caching the value in registers. This is crucial for fields shared between threads. However, when you pass a volatile field to a method using ref or out, the called method receives a memory address. Inside that method, the compiler treats that address as a standard, non-volatile variable. Consequently, the specific read/write memory barriers guaranteed by volatile are lost for the duration of that method call.

This guide explains when this warning is safe to ignore (e.g., with Interlocked) and when it indicates a real bug.

Understanding the Warning

When you mark a field as volatile, the compiler emits specific instructions (volatile. prefix in IL) for every read and write to that field.

However, ref parameters work by passing a raw memory pointer.

  1. You declare volatile int count;.
  2. You call Method(ref count);.
  3. Inside Method(ref int x), the variable x is just an int. It is not a volatile int.
  4. Therefore, optimizations inside Method might cache the value of x, bypassing the safety you intended.

Scenario 1: Using Interlocked Methods (Common & Safe)

This is the most frequent trigger for CS0420. You have a volatile field for thread safety, but you also want to perform atomic operations on it using the System.Threading.Interlocked class.

Warning Trigger: Interlocked methods require parameters to be passed by ref.

using System.Threading;

public class Counter
{
// Volatile ensures latest value is read across threads
private volatile int _count = 0;

public void Increment()
{
// ⛔️ Warning CS0420: '_count': a reference to a volatile field
// will not be treated as volatile.
Interlocked.Increment(ref _count);
}
}

Is this dangerous?

  • In the specific case of Interlocked methods: No. The Interlocked class (implemented at the hardware/CPU level) provides much stronger thread-safety guarantees (Atomic operations) than volatile does. Even though the volatile modifier is technically ignored during the call, the Interlocked operation itself ensures memory correctness.

Scenario 2: Custom Methods (Potentially Unsafe)

If you pass a volatile field by reference to a custom helper method, you might introduce real threading bugs.

Example of warning:

public class Processor
{
private volatile int _status = 0;

public void Run()
{
// ⛔️ Warning CS0420
WaitForStatus(ref _status);
}

// Inside this method, 'val' is NOT volatile.
// The CPU might cache 'val' in a register and loop forever,
// never seeing updates from other threads.
private void WaitForStatus(ref int val)
{
while (val == 0)
{
// SpinWait...
}
}
}

In this scenario, the warning is critical. Because val loses volatility inside WaitForStatus, the loop optimization might cause an infinite loop (Live Lock).

Solution 1: Suppress Warning for Interlocked

If you are using Interlocked methods (like .Increment, .CompareExchange, .Exchange), the standard solution is to suppress the warning using a pragma. You are explicitly telling the compiler: "I know volatile is ignored here, but Interlocked handles the safety for me."

Solution:

using System.Threading;

public class Counter
{
private volatile int _count = 0;

public void Increment()
{
// ✅ Correct: Suppress the warning specifically for this line
#pragma warning disable CS0420
Interlocked.Increment(ref _count);
#pragma warning restore CS0420
}
}
tip

Always restore the warning (#pragma warning restore) immediately after the line to ensure you don't miss legitimate warnings elsewhere in the file.

Solution 2: Remove volatile Keyword

If you find that you only ever access the field using Interlocked methods, or inside lock statements, then the volatile keyword is redundant. Interlocked and lock provide full memory fences, making volatile unnecessary.

Solution: remove the keyword if all access is synchronized.

using System.Threading;

public class Counter
{
// ✅ Correct: Removed 'volatile'.
// We rely entirely on Interlocked for thread safety.
private int _count = 0;

public void Increment()
{
Interlocked.Increment(ref _count);
}

public int GetCount()
{
// Use CompareExchange to read atomically (returns original value)
return Interlocked.CompareExchange(ref _count, 0, 0);
}
}

Conclusion

CS0420 warns you that the "freshness" guarantee of volatile is suspended when passed by reference.

  1. Check the Method: Are you calling Interlocked.Xxx()?
    • Yes: It is safe. Use #pragma warning disable CS0420.
  2. Check Custom Methods: Are you calling a standard method?
    • Yes: Be careful. The method might cache the value. Refactor to avoid passing by ref, or ensure the method doesn't rely on polling the variable loop-style.
  3. Review Design: If you use Interlocked everywhere, you might not need volatile at all.