How to Resolve Error "CS0678: 'variable': a field can not be both volatile and readonly" in C#
The Compiler Error CS0678 is a logical contradiction error. The message reads: "'variable': a field can not be both volatile and readonly".
This error occurs because the keywords volatile and readonly represent mutually exclusive behaviors regarding memory usage:
readonlytells the compiler: "This value is assigned once (during initialization) and never changes afterwards."volatiletells the compiler: "This value can change at any time (by other threads or hardware), so never cache it and always read it directly from memory."
You cannot tell the compiler that a variable will never change (readonly) while simultaneously warning it that the variable changes unpredictably (volatile).
This guide explains how to choose the correct modifier based on your threading requirements.
Understanding the Conflict
To understand why this is forbidden, look at how the compiler optimizes code:
- For
readonly: The compiler assumes the value is stable. It might cache the value in a CPU register or optimize away redundant reads because it "knows" the value hasn't changed. - For
volatile: The compiler is strictly forbidden from optimizing reads/writes. It must generate a "memory barrier" or force a fresh read from main RAM every single time the variable is accessed, because another thread might have changed it.
Applying both instructions to the same memory slot creates a paradox.
Scenario: The Invalid Declaration
This often happens when a developer wants to create a thread-safe "flag" (like a boolean indicating a stop signal) but also wants to ensure it is only initialized once, confusing the intent of readonly.
Example of error:
public class Worker
{
// ⛔️ Error CS0678: a field can not be both volatile and readonly.
// 'readonly' implies it won't change.
// 'volatile' implies it changes often (by other threads).
private volatile readonly bool _shouldStop;
public Worker()
{
_shouldStop = false;
}
public void DoWork()
{
while (!_shouldStop)
{
// Work...
}
}
}
Solution 1: Remove readonly (Thread Safety)
If the variable is intended to be used as a signaling flag between threads (e.g., one thread reads it, another thread writes to it later), then it must be mutable.
By definition, if you need volatile, you expect the value to change. Therefore, it cannot be readonly.
Solution: remove the readonly keyword.
public class Worker
{
// ✅ Correct: The field is mutable and thread-safe (atomic read/write).
private volatile bool _shouldStop;
public void RequestStop()
{
// We can now update the value from another thread.
_shouldStop = true;
}
public void DoWork()
{
// This loop checks the main memory value every time due to 'volatile'.
while (!_shouldStop)
{
// Work...
}
}
}
Solution 2: Remove volatile (Immutability)
If the variable is truly meant to be set once in the constructor and never touched again (e.g., a configuration setting or a dependency reference), then thread caching issues are irrelevant because the value is constant across all threads after creation.
Solution: remove the volatile keyword.
public class Worker
{
// ✅ Correct: The value is immutable.
// All threads will see the same value once the constructor completes.
private readonly int _maxRetries;
public Worker(int retries)
{
_maxRetries = retries;
}
}
Memory Models: The .NET memory model guarantees that writes to readonly fields in a constructor are visible to other threads immediately after the constructor finishes (publication safety). You do not need volatile to safely read a readonly field.
Conclusion
CS0678 forces you to decide the nature of your data.
- Is it a Constant? If the value never changes after initialization, use
readonly. It is inherently thread-safe because it is immutable. - Is it a Signal? If the value changes during runtime and is shared between threads, use
volatile(and ensure it is notreadonly).