C# Constants
A C# constant is a named value that cannot be changed after it is declared. Constants make code easier to read because they replace unexplained literal values with meaningful names. They also protect important fixed values, such as tax rates used in a sample, unit conversion factors, labels, and mathematical values, from accidental reassignment.
Overview: How C# Constants Work
In C#, the keyword const declares a compile-time constant. Compile-time means the value must be completely known when the program is built, before the CLR starts running the program. For example, const int MinutesPerHour = 60; is valid because 60 is a literal value. After that declaration, code can read MinutesPerHour, but it cannot assign a different value to it.
Constants are different from ordinary variables. A variable stores a value that may change while the program runs. A constant represents a fixed value baked into the compiled code. When the compiler sees a constant in many expressions, it can substitute the actual value. This is why constants can be used in places that require compile-time information, such as case labels in a switch statement.
Only certain types can be used with const. Common constant types include numeric types such as int, double, and decimal, plus bool, char, string, and enum values. You cannot create a const array, const list, or const object instance because those require runtime object creation. A string is allowed because string literals are handled specially by the compiler and runtime, and strings are immutable.
There is an important companion feature called readonly. A readonly field cannot be changed after construction, but its value does not have to be known at compile time. That makes readonly suitable for values loaded from configuration, calculated in a constructor, or created with new. Use const for true fixed facts known at build time; use readonly when the value is fixed after an object or type is initialized.
Constants also affect API design. A public const value from one assembly can be copied into another assembly at compile time. If the library later changes the constant, existing compiled clients may still use the old value until they are rebuilt. For values exposed across assembly boundaries, public static readonly is often safer unless the value is truly permanent.
Syntax
const type ConstantName = constantValue;
public const type ConstantName = constantValue;
readonly type fieldName = value;
| Part | Meaning |
|---|---|
const |
Creates a compile-time constant that must be initialized in the declaration and cannot be assigned later. |
type |
The constant’s type, such as int, decimal, bool, char, string, or an enum type. |
ConstantName |
The identifier used to read the constant. Public constants commonly use PascalCase. |
constantValue |
A literal or another compile-time constant expression that the compiler can evaluate during compilation. |
readonly |
Creates a field that can be assigned in its declaration or constructor, then remains fixed for that instance or type. |
A local constant inside a method is written like this:
const double InchesPerCentimeter = 0.3937007874;
const string UnitLabel = "in";
Examples
Using Constants for Unit Conversion
using System;
class Program
{
static void Main()
{
const int MinutesPerHour = 60;
const int HoursPerWorkday = 8;
int workdays = 5;
int totalMinutes = workdays * HoursPerWorkday * MinutesPerHour;
Console.WriteLine($"Workdays: {workdays}");
Console.WriteLine($"Minutes per hour: {MinutesPerHour}");
Console.WriteLine($"Total minutes: {totalMinutes}");
}
}
Output:
Workdays: 5
Minutes per hour: 60
Total minutes: 2400
The values 60 and 8 are not mysterious numbers scattered through the calculation. Their names explain the formula. If the program tried to assign MinutesPerHour = 61;, the compiler would reject it.
Constants in a Price Calculation
using System;
using System.Globalization;
class Program
{
static void Main()
{
const string ProductCodePrefix = "BK";
const decimal ListPrice = 24.95m;
const decimal DiscountRate = 0.10m;
decimal discount = ListPrice * DiscountRate;
decimal salePrice = ListPrice - discount;
Console.WriteLine($"Product: {ProductCodePrefix}-1024");
Console.WriteLine($"List price: {ListPrice.ToString("F2", CultureInfo.InvariantCulture)}");
Console.WriteLine($"Discount: {discount.ToString("F2", CultureInfo.InvariantCulture)}");
Console.WriteLine($"Sale price: {salePrice.ToString("F2", CultureInfo.InvariantCulture)}");
}
}
Output:
Product: BK-1024
List price: 24.95
Discount: 2.50
Sale price: 22.46
This example uses const string and const decimal. Notice the m suffix on decimal literals. Without it, values such as 24.95 are treated as double literals, and the assignment to a decimal constant would not compile.
Constants in switch Labels
using System;
class Program
{
static void Main()
{
const int BronzeLevel = 1;
const int SilverLevel = 2;
const int GoldLevel = 3;
int memberLevel = SilverLevel;
string benefit;
switch (memberLevel)
{
case BronzeLevel:
benefit = "Standard newsletter";
break;
case SilverLevel:
benefit = "Newsletter and early access";
break;
case GoldLevel:
benefit = "All benefits plus priority support";
break;
default:
benefit = "No benefits assigned";
break;
}
Console.WriteLine($"Level: {memberLevel}");
Console.WriteLine($"Benefit: {benefit}");
}
}
Output:
Level: 2
Benefit: Newsletter and early access
case labels must be constant expressions. The named constants keep the switch readable while still satisfying the compiler’s requirement that each label is known at compile time.
When readonly Is the Better Choice
using System;
class Program
{
static void Main()
{
var settings = new ReportSettings("monthly", 12);
Console.WriteLine($"Report type: {settings.ReportType}");
Console.WriteLine($"Months retained: {settings.MonthsRetained}");
}
}
class ReportSettings
{
public readonly string ReportType;
public readonly int MonthsRetained;
public ReportSettings(string reportType, int monthsRetained)
{
ReportType = reportType;
MonthsRetained = monthsRetained;
}
}
Output:
Report type: monthly
Months retained: 12
The constructor arguments are not known at compile time, so they cannot initialize const fields. They can initialize readonly fields because readonly allows assignment during construction and then prevents later changes.
How Constants Work Step by Step
- The compiler reads a declaration such as
const int MaxAttempts = 3;. - It checks that the type is allowed for constants and that the initializer can be evaluated at compile time.
- It records the name, type, and value in the program’s metadata and symbol table.
- When the constant is used in an expression, the compiler can treat it as the literal value. For example,
MaxAttempts + 1can be evaluated as3 + 1. - At runtime, there is no ordinary local variable slot whose value can be reassigned. The compiled instructions use the constant value directly where appropriate.
This does not mean constants are always faster in a way you should optimize for manually. The main benefit is correctness and clarity. The compiler and JIT already perform many optimizations; your job is to give stable values clear names and choose the right immutability feature.
Common Mistakes
Trying to Reassign a Constant
const int MaxRetries = 3;
MaxRetries = 4;
This does not compile because a constant cannot appear on the left side of an assignment after it is declared. If the value is meant to change during the program, use a variable.
int maxRetries = 3;
maxRetries = 4;
Console.WriteLine(maxRetries);
Output:
4
Using a Runtime Value as a const Initializer
string folder = Console.ReadLine();
const string DefaultFolder = folder;
This does not compile. Console.ReadLine() runs at runtime, and a local variable value is not a compile-time constant. For a fixed value that comes from runtime input or configuration, use readonly in a class or an ordinary variable in a method.
string folder = "reports";
string defaultFolder = folder;
Console.WriteLine(defaultFolder);
Output:
reports
Forgetting the decimal Suffix
const decimal TaxRate = 0.0825;
This does not compile because 0.0825 is a double literal. Add the m suffix to make the literal a decimal.
const decimal TaxRate = 0.0825m;
Console.WriteLine(TaxRate);
Output:
0.0825
Best Practices
- Use
constfor true compile-time facts: conversion factors, fixed labels, mathematical constants, and values that are not expected to change between builds. - Use descriptive names such as
MinutesPerHourinstead of vague names such asNumberorValue. - Prefer PascalCase for constants that behave like named values, especially fields. Follow your team’s style for local constants.
- Use
decimalconstants with anmsuffix for money-like values. - Use
readonlyorstatic readonlywhen the value is created at runtime, read from configuration, or exposed publicly from a reusable library. - Avoid making every repeated literal a constant. Name a value when the name explains meaning, prevents mistakes, or centralizes a real rule.
- Do not use
public constfor values that might change in a separate library version unless you are comfortable requiring consumers to rebuild.
Practice Exercises
- Create constants for
DaysPerWeekandHoursPerDay. Store a number of weeks in a variable and print the total hours. - Write a program with a
const decimal TaxRate, a product price, and a quantity. Print the subtotal, tax, and total with two decimal places. - Create three integer constants for shipping speeds, then use a
switchstatement to print the delivery description for one selected speed.
Summary
constdeclares a value that is known at compile time and cannot be reassigned.- Constants improve readability by giving meaningful names to fixed values.
- Constant initializers must be literals or other compile-time constant expressions.
- Constants can be used in places that require compile-time values, such as
switchcase labels. readonlyis different: it protects a field after construction but allows runtime initialization.- Choose
constfor permanent facts andreadonlyfor values that are fixed only after initialization.
