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#.
Let’s dive in.
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:
const | readonly |
---|---|
Exists only in the source code | Exists 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 locals | Use it with any data type for public or private class fields |
Gets compile-time evaluation | Gets runtime evaluation |
Always immutable by nature | Is mutable until it is assigned a value |
Has the same value accross all class instances | Can 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 value | When 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.