Constants
Learn how to declare unchangeable values using const and readonly, and when to reach for each.
What is a Constant?
A constant is a value that cannot change after it is set — useful for things that are fixed by definition (the value of pi), by business rule (a tax rate), or by convention (an application's name). C# provides two distinct ways to declare one: `const` and `readonly`, and despite both meaning "unchangeable," they behave quite differently under the hood.
const
A `const` value must be known at compile time — meaning it has to be a literal or a simple expression the compiler can fully evaluate while building your program, never something computed at runtime. It is implicitly static, meaning it is shared across the whole type rather than belonging to any one object instance, and you never write the static keyword yourself for a const.
const double Pi = 3.14159;const string AppName = "PrograMinds";const int MaxRetries = 3;
// Simple expressions using other consts are allowed:const int SecondsPerMinute = 60;const int SecondsPerHour = SecondsPerMinute * 60; // fine — fully computable at compile timeconst CANNOT be used for anything whose value is only known while the program is running — a value read from a database, a configuration file, user input, or even DateTime.Now, since none of these are knowable at compile time. Attempting this produces a compile error.
readonly
A `readonly` field can be assigned either at declaration or inside a constructor, but never changed afterward. Unlike const, its value can be computed at runtime — this makes it useful for values that vary per object but shouldn't change once that particular object is created.
class Account{ public readonly string AccountId; public readonly DateTime CreatedAt;
public Account(string id) { AccountId = id; // allowed inside constructor CreatedAt = DateTime.Now; // runtime value — impossible with const }}
var account = new Account("ACC-001");Console.WriteLine(account.AccountId); // ACC-001// account.AccountId = "ACC-002"; // ❌ compile error — readonly fields can't be reassignedstatic readonly
Combining static and readonly gives you a value that is shared across every instance of a class (like const) but can still be computed at runtime (unlike const) — the closest thing to "the best of both worlds" for values that are fixed once, at startup, but can't be known purely at compile time.
class AppConfig{ public static readonly string StartupTime = DateTime.Now.ToString(); public static readonly Guid InstanceId = Guid.NewGuid();}const vs readonly
| const | readonly | static readonly | |
|---|---|---|---|
| Value known at | Compile time only | Compile time or runtime | Compile time or runtime |
| Can be set in constructor | No | Yes | No (only at declaration or a static constructor) |
| Shared across instances | Yes (implicitly) | No (per-instance) | Yes (explicitly static) |
| Can hold a computed/runtime value | No | Yes | Yes |
Real-World Example: A Configuration Class
A realistic example combining all three, showing exactly when each one is the right tool.
class AppSettings{ // True mathematical/logical constant — never changes, ever, for any app public const int MaxUsernameLength = 20;
// Fixed at startup, but computed at runtime — can't be a const public static readonly DateTime ApplicationStartedAt = DateTime.Now;
// Varies per instance, set once in the constructor public readonly string Environment;
public AppSettings(string environment) { Environment = environment; }}
var devSettings = new AppSettings("Development");var prodSettings = new AppSettings("Production");
Console.WriteLine(devSettings.Environment); // DevelopmentConsole.WriteLine(prodSettings.Environment); // ProductionConsole.WriteLine(AppSettings.MaxUsernameLength); // 20 — same for both, accessed via the class, not an instanceCommon Beginner Mistakes
const DateTime Now = DateTime.Now; will not compile — DateTime.Now can only be known while the program runs, never at compile time. Use static readonly instead.
Once construction finishes, a readonly field is locked — attempting to change it anywhere else, even in another method of the same class, is a compile error.
A const baked into a compiled library requires recompiling every consumer of that library if the value ever changes; a static readonly value can be updated by simply rebuilding the one library, without breaking binary compatibility for its consumers — a subtle but real distinction in library design.
FAQs
Use `const` for true, fixed values like mathematical constants or a hard limit that will genuinely never change. Use `readonly` for values that depend on runtime input, like an ID passed into a constructor, or `static readonly` for a runtime-computed value shared across every instance.
Only string and null are allowed as reference-type consts — any other reference type (like a custom class or an array) cannot be a const, since the compiler can't guarantee its contents are truly fixed at compile time.
No — readonly only prevents reassigning the field itself to a different object; if the field holds a mutable object (like a List<T>), the contents of that object can still be changed freely, just not swapped for an entirely different object.
const is technically faster since its value is baked directly into the compiled code at every usage site (no memory lookup needed), but this difference is utterly negligible for the vast majority of real applications.
Key Takeaways
- `const` values are fixed at compile time, implicitly static, and can only be primitives, strings, or null.
- `readonly` values can be set in a constructor (allowing runtime computation) but not changed afterward.
- `static readonly` combines runtime computation with being shared across every instance of a class.
- Choose based on whether the value is truly fixed forever (const), varies per instance but set once (readonly), or fixed once but shared and computed at runtime (static readonly).
Summary
const, readonly, and static readonly give you three distinct flavors of "unchangeable," each suited to a different scenario — knowing which to reach for is a small but genuinely useful piece of professional C# judgment. Next, you'll explore C#'s full range of built-in data types.