C# const vs readonly: Key Differences and Usage
c# const vs readonly: Understand the differences between C# const and readonly, including compile-time vs runtime behavior, usage scenarios, and performance implications.
In C#, const and readonly both define values that cannot be reassigned after initialization, but they behave very differently. The choice between c# const vs readonly affects when the value is evaluated, where it can be used, and how it behaves across assembly boundaries. Getting that choice wrong can lead to subtle bugs or unnecessary runtime overhead.
What const Does at Compile Time
A const field is a compile-time constant. The compiler replaces every usage of the field with its literal value during compilation. This means the value must be known at compile time and cannot be computed at runtime.
public class MathConstants { public const double Pi = 3.14159; public const int MaxRetries = 3; }
Only primitive types, string, and enum types can be declared as const. You cannot use const with DateTime, Guid, or any user-defined struct or class. Because the value is inlined, a const field is implicitly static and belongs to the type, not to an instance.
When you reference MathConstants.Pi in another assembly, the compiler copies 3.14159 directly into the calling code. If you later change the value of Pi and rebuild only the defining assembly, the consuming assembly still uses the old value until it is recompiled. This is an important versioning consideration.
How readonly Behaves at Runtime
A readonly field is a runtime constant. Its value is assigned once, either at declaration or in the constructor of the containing class. After that, it cannot be modified. Unlike const, a readonly field can hold any type, including reference types and structs.
public class ServiceConfig { public readonly DateTime StartedAt; public readonly ILogger Logger; public ServiceConfig(ILogger logger) { StartedAt = DateTime.UtcNow; Logger = logger ?? throw new ArgumentNullException(nameof(logger)); } }
A readonly field is not implicitly static. You can have instance readonly fields, or you can declare them as static readonly if you need a shared value. The assignment can happen in the constructor, which means the value can be computed at runtime, based on constructor parameters, environment state, or any other logic.
When you reference a readonly field, the actual field is accessed at runtime, not a compile-time copy. This means changes to the field's value in the defining assembly are picked up by consuming assemblies without recompilation, as long as the field is not inlined.
Key Differences Between const and readonly
The following table summarizes the most important differences:
| Criterion | const | readonly |
|---|---|---|
| Evaluation time | Compile time | Runtime |
| Allowed types | Primitives, string, enum | Any type |
| Implicitly static | Yes | No (unless declared static) |
| Assignment location | Declaration only | Declaration or constructor |
| Assembly versioning | Value inlined at compile time | Field accessed at runtime |
| Use in expressions | Can be used in compile-time expressions | Cannot be used in compile-time expressions |
Because const values are inlined, they can be used in switch case labels, attribute arguments, and enum definitions. readonly fields cannot be used in these compile-time contexts.
When to Use const vs readonly
The decision depends on whether the value is truly fixed at compile time or depends on runtime conditions.
Use const when the value is a fundamental constant that will never change across versions, such as mathematical constants, configuration keys, or fixed limits that are part of the contract. For example, const int MaxBufferSize = 1024; is appropriate if the buffer size is a hard limit that all callers must agree on.
Use readonly when the value is known only when the object is created, or when the value might change in future versions without breaking consumers. For example, a service endpoint URL or a timeout value that is read from configuration should be readonly, because it is initialized at runtime and can vary per instance.
If you are designing a public API, prefer readonly over const for values that could change. Changing a const value in a public API is a breaking change for consumers because they have the old value compiled into their code. A static readonly field avoids that problem because consumers read the current value at runtime.
Performance and Maintainability Considerations
const can offer a tiny performance benefit because the value is inlined and no field access is needed at runtime. However, this is rarely significant in practice. The larger concern is maintainability and versioning.
When you use const across assembly boundaries, you are effectively hard-coding the value into every consumer. If you need to change that value, you must rebuild all dependent assemblies. This can lead to inconsistent behavior if only the defining assembly is updated. readonly avoids this by deferring the lookup to runtime, but it adds a field access per use.
Another consideration is memory. A const field does not occupy memory at runtime because it is replaced by its literal value. A static readonly field occupies memory for the field itself. For most applications, this difference is negligible, but it matters in extremely memory-constrained environments.
Common Pitfalls and Edge Cases
One common mistake is using const with a reference type that is not string. For example, const int[] Numbers = { 1, 2, 3 }; will not compile because arrays are not allowed. You might think you can use readonly for arrays, but readonly only prevents reassigning the field, not modifying the array elements. The array can still be mutated.
public class DataStore { public static readonly int[] Numbers = { 1, 2, 3 }; } DataStore.Numbers[0] = 99; // Allowed, despite readonly
If you need an immutable collection, use ReadOnlyCollection or ImmutableArray instead.
Another edge case is const with string concatenation. The compiler evaluates const string FullName = "John" + " Doe"; at compile time, so it is valid. However, you cannot use readonly fields in such expressions because they are not known at compile time.
Advanced Usage: Static readonly and Interplay with Constructors
A static readonly field is often used for values that are expensive to create and shared across all instances. For example, a static readonly HttpClient is a common pattern because HttpClient is designed to be reused.
public class ApiClient { private static readonly HttpClient _httpClient = new HttpClient(); }
The initialization happens when the type is first used, and it runs only once. You can also assign a static readonly field in a static constructor, which gives you more control over the initialization logic.
For instance readonly fields, the assignment must happen in every constructor path. If a class has multiple constructors, each must assign the field, or you can use constructor chaining to ensure a single assignment point.
public class Order { public readonly DateTime CreatedAt; public Order() : this(DateTime.UtcNow) { } public Order(DateTime createdAt) { CreatedAt = createdAt; } }
This pattern ensures that CreatedAt is always set, regardless of which constructor is called.
Understanding these behaviors helps you choose the right keyword for each scenario. Use const for true compile-time constants that are part of the type's contract, and use readonly for values that are determined at runtime and should remain stable after initialization.