Const, readonly and static in C#

Const, readonly, and static are keywords in C#, used to declare class members with different purposes. In this article, I will explain the difference between them.

Const

Firstly, a variable marked as constant is known at compile time, and cannot be changed at runtime. It is declared once and that’s it, it can’t be reassigned after that. Constants are typically used for values that will not change, such as mathematical constants, names, or even web API endpoints. For instance:

C#
public class WebServiceClient
{
    private const string BaseServiceUrl = "example-service-base-path.com";

    public object Get(string itemId)
    {
        using (HttpClient client = new HttpClient())
        {
            return client.GetAsync($"{BaseServiceUrl}/{itemId}").Result;
        }
    }
}

In the example above, we are not expecting the change of the base URL of an API. Therefore the BaseServiceUrl constant can be used. Moreover, if you try to reassign it, you will get a compile-time error:

Const readonly and static in C# - reassign constant error
Const readonly and static in C# – reassign constant error

It is also interesting that if you decompile the code, at the place of usage you won’t see that this variable is declared. It will be substituted with its value, for instance:

Const readonly and static in C# - decompiled constant
Const readonly and static in C# – decompiled constant

Hence, during compilation everywhere you are using a constant, it is substituted with its respective value.

Readonly

A readonly variable is a value that can be assigned only once at runtime (in the constructor) or in a declaration statement. Unlike const, readonly variables can be assigned values determined at runtime but can not be changed afterward. Or in other words – it defines immutable data structures. This may be useful when you want to ensure that a value can not be changed after it has been set, for instance – some application configuration data.

Similarly, let’s take a look at the previous example with BaseServiceUrl constant. Instead of hard-coding the constant value, imagine it can come from a system configuration. In other words, you can use readonly, and assign its value in the constructor:

C#
public class WebServiceClient
{
    private readonly string BaseServiceUrl;

    public WebServiceClient(WebServiceConfiguration config)
    {
        BaseServiceUrl = config.BaseServiceUrl;
    }

    public object Get(string itemId)
    {
        using (HttpClient client = new HttpClient())
        {
            return client.GetAsync($"{BaseServiceUrl}/{itemId}").Result;
        }
    }
}

public class WebServiceConfiguration
{
    public string BaseServiceUrl { get; }
}

Finally, it is important to mention that, if you try to reassign a readonly, after it was already assigned, you will get a compile time error:

Const readonly and static in C# - reassign readonly error
Const readonly and static in C# – reassign readonly error

Static

A static member belongs to the type itself, rather than to a specific object. Therefore, It is shared by all instances of a class and is stored in one location in memory. A static variable is typically used for values that are common to all instances of a class. In addition, except variables, classes, constructors, and methods can be static as well.

Static constructor

This constructor is called when you instantiate the class or access something static in it for first time. For instance:

C#
var firstPerson = new Person("John", 32);
var secondPerson = new Person("Maria", 28);

public class Person
{
    static Person()
    {
        Console.WriteLine("Static constructor called");
    }

    public Person(string name, int age)
    {
        this.Name = name;
        this.Age = age;
    }

    public string Name { get; set; }

    public int Age { get; set; }
}

The result from the above code will be:

Static constructor initialization
Static constructor initialization

Although there are two instances of the class Person, the static constructor is executed only once – the first time you initialize the class. Static constructors are used for initializing static fields of the class, or for any other operation, that has to be executed only once.

Static field

The static field is shared along all class instances, for example:

C#
Console.WriteLine(EmailClient.SenderEmail);

public class EmailClient
{
    static EmailClient()
    {
        Console.WriteLine("Static constructor called");
        SenderEmail = "senderemail@test.test";
    }

    public static string SenderEmail;

    public void SendEmail(string toEmail)
    {
        Console.WriteLine($"Email sending from {SenderEmail}, to {toEmail}...");
    }
}

In this example the SenderEmail is the same for the whole application, therefore you can define it in the static constructor. The code will print the following result:

Static field
Static field

As you can see the static constructor is initialized just before the first time when a static property is accessed.

Static method

Static methods include common class behavior, for instance:

C#
public class Dog
{
    public Dog(string breed)
    {
        this.Breed = breed;
    }

    public string Breed { get; private set; }

    public static void Bark()
    {
        Console.WriteLine("Dog barking ...");
    }
}

Static class

Static classes can’t be initialized, so everything inside them should also be static and shared. The Console in C#, for example, is static – there is only one console. The Math class, providing mathematical operations is also static. Since all math constants and operations are the same, there is no need from different instances.