In this article, we will discuss the differences between const and readonly keywords in C#.

const and readonly allow us to declare immutable values in C#.

To download the source code for this article, you can visit our GitHub repository.

Let’s dive in.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

Understanding Const Keyword in C#

const declarations only exist in the source code and not in the compiled output. It’s essentially a directive for the compiler to embed the value into the code that references it. In essence, we should see const references as placeholders that get replaced with actual values, something that we can easily observe by decompiling the code.

After that important clarification, let’s start by taking a look at the properties of const fields and go through some examples.

Access Modifiers and Types Supported By Const Keyword

We can apply the const keyword to fields and locals that are declared as string or primitive value types like bool, int, float and other similar language-native primitives. An enum is also a valid declaration for a const, as it shares the same primitive type restrictions. These limitations exist because constant values are not directly present in the compiled code. For this kind of substitution to be possible the compilers need to use language-native simple types, which is related mainly to the way that the Common Language Runtime designed.

Let’s take a look at how we can declare const values:

public class CircleCalculator
{
    public const double E = 2.71;
    private const double Pi = 3.14;

    public double GetCircumference(double radius) => 2 * Pi * radius;

    public double GetAccurateCircumference(double radius)
    {
        const double accuratePi = 3.14159265359;

        return 2 * accuratePi * radius;
    }
}

In the code above, the class CircleCalculator declares two const fields,  the public E and the private Pi. Additionally, within the second method, there is a local const, accuratePi. The two methods make use of these constants to calculate the circumference of a circle with different precision of accuracy.

Assignment and Mutability

The value of a const field or a local const in C# must be known and is evaluated at compile time. As a result, either of those must be assigned a value when the code is written and cannot be changed or computed at runtime. We can easily understand that const fields and locals are immutable because of their nature as they simply don’t exist during runtime. Hence we cannot refer to const declarations as variables as they simply are a compile-time find-and-replace type of reference that just gets swapped out with the value provided.

Interestingly, the compiler can perform calculations between values that are known at compile-time. For example, in the GetCircumference() method since the values of the literal 2 and the const field Pi are known at compile time their product will be compiled as one number and the only known parameter will be radius which is parsed at the method’s call. Once again we can see this behavior in action by decompiling the DLL.

Similarities to Static

Since a const is a compile-time construct, by nature, it acts as a static reference. It allows for shared usage across all instances of a class and ensures that the value remains consistent throughout the program’s execution. Stated differently, unlike variables which can have different values for each instance of a class, a const is associated with the class itself meaning that all instances of the class share the same value. Another property of a const is that you can directly access the constant using the class name, without needing to create an object or instance:

Console.WriteLine(CircleCalculator.E);

Referencing Const Between Assemblies

Let’s say that AssemblyA references AssemblyB and utilizes its values. If we change a value of a constant field in AssemblyB and recompile only that project, AssemblyA will not automatically receive the updated value. To see the updated value in AssemblyA, we need to recompile it as well.

In our code, we have created two projects ConstVsReadonlyInCSharp and ConstVsReadonlyInCSharp.Library. The Library project declares the ConstValue:

public class Values
{
    //V1
    public const string ConstValue = "Const field of version 1.";
    //V2
    //public const string ConstValue = "Const field of version 2.";
}

We can see its usage in the main project:

public class Program 
{ 
    static void Main(string[] args) 
    { 
        Console.WriteLine(Values.ConstValue); 
    } 
}

If we change the value of the field to the second version, rebuild the Library project, and place the DLL in the same folder as the ConstVsReadonlyInCSharp exe file, we will not see any changes in its value as the outputted result will be:

Const field of version 1.

If we rebuild both projects we will be able to receive the updated value:

Const field of version 2.

Understanding Readonly Modifier in C#

Readonly fields unlike const declarations, act as variables and reserve heap memory space at runtime. Let’s examine how readonly keyword alters field properties.

Access Modifiers and Types Supported by Readonly Declarations

We can apply the readonly modifier to any public or private field we declared with any type, primitive or otherwise. Moreover, it’s important to note that this keyword cannot be applied to locals.

Let’s provide a simple example of readonly fields :

public class TaxCalculator
{
    public readonly decimal _countryVAT;
    public readonly decimal _euroToDollarConversionRate = 1.08M;
    private readonly MathCalculator _calculator = new MathCalculator();

    public TaxCalculator(decimal countryVAT)
    {
        _countryVAT = countryVAT;
    }

    public decimal CalculateCountryVatInEuro(decimal productValue) 
        => _calculator.Multiply(productValue, _countryVAT);

    public decimal CalculateCountryVatInDollars(decimal productValue) 
        => CalculateCountryVatInEuro(productValue) * _euroToDollarConversionRate;
}

In this code, we create the TaxCalculator class which contains the declaration of multiple readonly fields. Firstly, we have the public field _countryVAT that is assigned a value provided as a parameter to the constructor. Additionally, we have the private field _euroToDollarConversionRate which is an inline assignment, and the _calculator field to which we assign an instance of the MathCalculator class. By using these fields within the class methods, we calculate the VAT of a product in both Euros and Dollars.

Assignment and Mutability

The assignment of readonly fields always takes place at runtime, which means that all readonly fields start as mutable until we assign a value to them at which point they become immutable.

Given that the main difference between readonly and regular fields is – mutability, the rest of the characteristics with respect to initialization and object construction remain the same. In other words – both static and non-static readonly fields can be initialized in-line or within the respective static or instance constructors. For non-static declarations, each instance of a class maintains its own separate copy of the value, requiring class instantiation for access.

Referencing Readonly Between Assemblies

In case we have two assemblies, AssemblyA and AssemblyB of which the first one references the second, if we change the value of a readonly field in AssemblyB and recompile only that project, AssemblyA will automatically receive the updated value.

Let’s see that through our code in which we have the two projects we previously discussed. The library project declares the ReadonlyValue:

public class Values
{
    //V1
    public static readonly string ReadonlyValue = "Readonly field of version 1.";
    //V2
    //public static readonly string ReadonlyValue = "Readonly field of version 2.";
}

Let’s use it in the main project:

public class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(Values.ReadonlyValue);
    }
}

If we change the value of ReadonlyValue to the second version, rebuild the library project, and place the produced DLL in the same folder as the main program’s exe file, we will see the changes in its value:

Readonly field of version 2.

Const and Readonly Differences

Now that we have a brief overview of const and readonly, let’s present their key differences in the following table:

constreadonly
Exists only in the source codeExists in the source code and in the compiled output
Use it with enums, strings and primitive data type for public or private class fields as well as method localsUse it with any data type for public or private class fields
Gets compile-time evaluationGets runtime evaluation
Always immutable by natureIs mutable until it is assigned a value
Has the same value accross all class instancesCan have the same value accross all instances, or be instance specific
When using a const declared in another assembly, we need to recompile both to update the valueWhen using a readonly field in another assembly, we only have to recompile the referenced assembly to update the value

Choosing Between Const and Readonly

After analyzing the differences between const and readonly, we will discuss the situations where each one is most suitable.

As a general rule, let’s see in which cases const can be most suitable:

  • The field is a string, enum, or primitive type
  • We need a value that is the same for all class instances
  • We need to access it without creating an instance of the class 

In contrast, we prefer the readonly when:

  • The field can be of any type
  • We need to initialize the value dynamically, at runtime
  • We need different values for each instance of a class
  • We need the value to be updated in another assembly without having to recompile it

Conclusion

In summary, in this article, we’ve provided a comprehensive comparison of const and readonly keywords in C#. We started with an introduction to the characteristics of each one while providing simple examples in order to illustrate their usage. We moved on by presenting a detailed comparison, contrasting the two modifiers side by side and enabling a deeper understanding of their differences. Lastly, based on that comparison we mentioned the appropriate use cases for const and readonly.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!