Types ยท immutability

const vs readonly vs static readonly

All three say "this won't change" โ€” but they make that promise at different moments, to different scopes, with different consequences. Getting the wrong one is how configuration values mysteriously become 0.

The one-line version

KeywordDecided whenScopeWho can set it
constcompile timeimplicitly staticthe compiler literally pastes the value in
readonlyobject constructionper objectthe constructor (or field initializer)
static readonlytype initializationone per classthe static constructor (or initializer)

const: a compile-time literal

A const must be a literal that the compiler can evaluate before the program ever runs. It is copied into every use site โ€” which is why it must be a primitive or string:

const int MaxRetries = 3;         // compiler knows this number at build time
Console.WriteLine("Max retries: " + MaxRetries);

// const string Version = "1.0";  // fine โ€” literal
// const double Pi = 3.14159;     // fine โ€” literal

What you cannot do: initialize a const from anything computed at runtime.

// All of these are compile errors:
const int FromMethod = int.Parse("5");       // a method call
const string FromNow = DateTime.Now.ToString(); // runtime value
const List<int> Numbers = new List<int>();  // reference types can't be const

That last one is the important limitation: const only works for value types and strings. For everything else โ€” a list, a class instance, a computed value โ€” you need readonly.

readonly: set once per object

A readonly field is assigned in the constructor and never again. Each object gets its own copy:

class Ticket
{
    public readonly int Number;     // set once, in the constructor

    public Ticket(int number)
    {
        Number = number;            // โœ“ allowed: we're in the constructor
    }

    public void TryChange()
    {
        // Number = 99;             // โœ— CS0191: readonly field
    }
}

var a = new Ticket(1);
var b = new Ticket(2);
Console.WriteLine("Ada: created " + a.Number);
Console.WriteLine("Bob: created " + b.Number);

This is the right tool when the value is fixed for that instance but unknown until construction โ€” an ID, a creation timestamp, an immutable configuration object's field.

static readonly: set once for the whole class

Same "set once" rule, but shared by every instance. It's set in the static constructor (or a static field initializer) and is the standard home for runtime-computed constants:

class AppConfig
{
    public static readonly int StartupYear = 2026;   // one copy, shared by every instance
}

Console.WriteLine("Startup year: " + AppConfig.StartupYear);

var c1 = new AppConfig();
var c2 = new AppConfig();
// static members are reached through the CLASS, never through an instance:
Console.WriteLine("Still: " + AppConfig.StartupYear);

Why this matters: static readonly lets you compute the value at startup โ€” read a config file, call a method, use DateTime.Now โ€” which const cannot do. This is the idiomatic replacement for "I want a constant but it's a computed value".

The subtle trap: const across assemblies

Because const is baked into call sites at compile time, changing it in one assembly does not update the code that already compiled against it. Ship a library with public const int Version = 1;, bump it to 2, and apps compiled against the old library still see 1 until they recompile.

static readonly has no such problem โ€” the value is read at runtime, so a new library version takes effect immediately. Rule: use const for genuinely fixed literals (ฯ€, day names), static readonly for anything that might change between releases.

Decision table

What you haveUse
A literal that will never change (3, "v1", 3.14159)const
A value computed once at startup (config, environment)static readonly
A value fixed per object but unknown until constructionreadonly
A list or class instance that shouldn't be reassignedreadonly / static readonly (note: the contents can still change!)

One trap worth knowing: readonly List<int> Items = new List<int>(); prevents reassigning Items, but Items.Add(5) still works. readonly freezes the reference, not the contents.

Frequently asked

Can a const be assigned from a method call?

No. const must be a compile-time literal (numbers, strings, chars, bools). For anything computed at runtime โ€” including DateTime.Now, file reads, or new List() โ€” use static readonly.

Does readonly make a field deeply immutable?

No. It prevents reassigning the field itself. If the field holds a list or an object, that object's contents can still be mutated. For real immutability you need immutable types (records, readonly structs).