Skip to main content
ANVISoftware Solutions
Lesson 8 of 62Beginner13 min

Constants and Readonly Values

By the end of this lesson

Choose between const and readonly for values that must not change.

Some values should not move once they are set: a VAT rate, the date an invoice was raised, a reference number. C# gives you two ways to say so.

They look interchangeable and are not. The difference is when the value is decided, and it shows up in a way that can leave two running services disagreeing about the same number.

const — the value is settled while the code is compiled
C#
decimal net = 1240.50m;
decimal vat = net * TaxRules.StandardVatRate;

Console.WriteLine($"VAT at {TaxRules.StandardVatRate:P0}: {vat}");

public static class TaxRules
{
    public const decimal StandardVatRate = 0.20m;
    public const int MaxInvoiceLineItems = 250;
    public const string CurrencyCode = "GBP";
}
  • A const can only hold something the compiler can evaluate itself: a number, bool, char, string, enum member or null.
  • A const is automatically static — it belongs to the type, not to an instance — and you do not write static yourself.
  • In a file using top-level statements, the statements come first and type declarations follow. The compiler requires that order.
  • The :P0 in the output is a format string: show it as a percentage with no decimal places.

readonly — fixed, but decided while the program runs

C#
var first = new Invoice("INV-2041");
var second = new Invoice("INV-2042");

Console.WriteLine($"{first.Reference} raised at {first.CreatedUtc:HH:mm:ss.fff}");
Console.WriteLine($"{second.Reference} raised at {second.CreatedUtc:HH:mm:ss.fff}");
Console.WriteLine($"Late fee for both: {Invoice.LateFee}");

public class Invoice
{
    public static readonly decimal LateFee = 25m;

    public readonly string Reference;
    public readonly DateTime CreatedUtc;

    public Invoice(string reference)
    {
        Reference = reference;
        CreatedUtc = DateTime.UtcNow;
    }
}
  • A readonly field can be assigned in exactly two places: where it is declared, or inside a constructor. Anywhere else is a build error.
  • Because the assignment happens while the program runs, the value can be anything — a DateTime, a list, the result of a calculation.
  • static readonly means one shared value for the whole type, worked out once. DateTime.UtcNow could never be a const, because the compiler cannot know it.
  • The two invoices have different CreatedUtc values, which is the point: readonly is per object unless you mark it static.

Side by side:

 constreadonly
When the value is decidedAt compile timeAt run time, as the object or type is created
Types allowedNumbers, bool, char, string, enum, nullAny type at all
Can a constructor set itNoYes
Per object or per typeAlways per type — it is implicitly staticPer object, or per type with static readonly
How other projects see itThe value is copied into their compiled codeRead from your assembly each time the program runs
Usable as a case label or default parameter valueYesNo — those positions require a compile-time constant

Choosing in practice:

  • const for values fixed by definition: months in a year, characters in a sort code
  • static readonly for values fixed today but open to revision: a tax rate, a retry limit, a default page size
  • An instance readonly field, or a get-only property, for a value that belongs to one object and is only knowable when it is created
  • Configuration, not either of these, for anything that differs by environment. Needing a rebuild to change a connection string is a poor trade
readonly protects the field, not the object behind it
C#
var order = new Order();

order.Lines.Add("Desk lamp");     // allowed
order.Lines.Add("Cable tidy");    // also allowed
// order.Lines = new List<string>();   // build error: cannot assign to a readonly field

Console.WriteLine(order.Lines.Count);   // 2

public class Order
{
    public readonly List<string> Lines = new List<string>();
}
  • readonly stops the field pointing at a different list. It says nothing about the contents of that list.
  • If the contents must not change either, expose the field as IReadOnlyList<string> or hand back a copy. The keyword alone does not make anything immutable.

Summary

  • const is decided at compile time and limited to values the compiler can evaluate itself
  • readonly is decided at run time, holds any type, and can be set in a constructor
  • A const used from another assembly is copied into the caller, so changing it needs a rebuild
  • readonly protects the field, not the object it points at
  • Values that vary by environment belong in configuration rather than in either keyword

Practice

Attempt each one before opening the solution. Getting it wrong first is how the idea sticks.

Think about it

Think about it

A shared library exposes public const int DefaultPageSize = 20;. The team changes it to 50 and publishes a new version. One service is updated to reference that new version, rebuilt nothing else, and still returns 20 items per page.

What happened, and what change to the library would prevent this class of problem?

Show solution

The service was compiled against the old library, so 20 was copied into its own compiled code. Swapping the library file cannot change a value that is no longer being read from it. Rebuilding the service fixes this one case.

Changing the declaration to public static readonly int DefaultPageSize = 50; fixes the category, because the value is then fetched from the library every time the program runs.

The cost is real but small: a field read instead of a literal, and the value can no longer be used where a compile-time constant is required, such as a default parameter value or a switch case label.

Try it yourself

Try it yourself

Write a Subscription class with a const string for the product code prefix, a static readonly decimal for the current monthly price, and a readonly DateTime for when the subscription started.

Create two subscriptions a moment apart and print both start times along with the shared price. Before you run it, predict which of the three values differ between the two objects.

Show solution

The prefix is fixed by definition, so const fits. The price is fixed today but a commercial decision could revise it, so static readonly is the safer home. The start date is only knowable when a subscription is created and belongs to that one object, so it has to be an instance readonly field set in the constructor.

Only the start times differ. That single output makes the const, static readonly and instance readonly distinction concrete in a way a definition does not.

C#
var first = new Subscription(1041);
Thread.Sleep(50);
var second = new Subscription(1042);

Console.WriteLine($"{first.Reference} started {first.StartedUtc:HH:mm:ss.fff}");
Console.WriteLine($"{second.Reference} started {second.StartedUtc:HH:mm:ss.fff}");
Console.WriteLine($"Both pay {Subscription.MonthlyPrice} per month");

public class Subscription
{
    public const string ProductCodePrefix = "SUB";
    public static readonly decimal MonthlyPrice = 14.99m;

    public readonly string Reference;
    public readonly DateTime StartedUtc;

    public Subscription(int accountId)
    {
        Reference = $"{ProductCodePrefix}-{accountId}";
        StartedUtc = DateTime.UtcNow;
    }
}

Saved in this browser only.