DEV Community

Cover image for Comparing Init-Only, Get-Only, and Readonly in C# Code
Matt Eland
Matt Eland Subscriber

Posted on • Originally published at newdevsguide.com

Comparing Init-Only, Get-Only, and Readonly in C# Code

Newer .NET developers are sometimes confused when they encounter readonly members in classes and the newer init-only setters. In this article we’ll explore both keywords along with the related get-only auto property. Finally, we’ll help you understand when to use each of these powerful language features.

The C# Readonly Keyword

The readonly keyword states that the field that it is applied to cannot be changed after the class is created.

Let’s take a look at a sample readonly field:

private readonly int birthYear = 1998;
Enter fullscreen mode Exit fullscreen mode

Here the birthYear field is given a value of 1998 and cannot be changed to a different value elsewhere in the class without generating a compiler error.

The advantages of the readonly keyword are the following:

  • It makes it clearer that a value should never change
  • It allows the compiler to make some performance optimizations knowing that the value will never change
  • It can protect you from future mistakes if you need to make sure a value never changes.

I find myself using readonly frequently when a class manages a List, Dictionary, or other class to accomplish its goals.

Important Note: Many people incorrectly assume that readonly means that the object it is attached to becomes unchangeable or immutable. For example, they assume a List<string> defined as readonly cannot add new items to it. This is not the case. All readonly says is that the field holding the list cannot be updated to point to a different List<string> on the heap.

Get-only Auto-Properties

Let’s talk briefly about get-only auto properties in C# because they’re really just a fancy way of using readonly under the hood.

Take a look at the following class that uses a get-only property called Color:

public class Ball
{
  public Ball(string color)
  {
     this.Color = color;
  }

  public string Color {get;} // get-only auto-property
}
Enter fullscreen mode Exit fullscreen mode

In this code, once the Ball is created, its Color property cannot be changed. This is because the code above is roughly equivalent to the following code:

public class Ball
{
   private readonly string color;

   public Ball(string color)
   {
      this.color = color;
   }

   public string Color
   {
      get
      {
         return color;
      }
   }
}
Enter fullscreen mode Exit fullscreen mode

Here we see that get-only auto properties automatically create a readonly field to manage their data.

Init Only Setters

Init-only setters are a newer language feature that give you the ability to set read-only properties of a class at construction without needing to add constructor parameters.

Take the following class that doesn’t use init-only setters:

public class Pet
{
   public Pet(string breed, int birthYear, string name)
   {
   }

   // Get-only auto properties
   public string Breed {get;}
   public int BirthYear {get;}
   public string Name {get;}
}
Enter fullscreen mode Exit fullscreen mode

Here we can create a new Pet with code like the following:

Pet myPet = new Pet("Cairn Terrier", 2016, "Jester");
Enter fullscreen mode Exit fullscreen mode

This is fine, but as the number of properties we want to set grows, the number of parameters to the constructor has to grow too.

To help with this problem, C# gives us the option to use init-only property setters that can only be invoked when creating a class.

Here’s a version of Pet that takes advantage of init-only setters:

public class Pet
{
   public string Breed {get; init;}
   public int BirthYear {get; init;}
   public string Name {get; set;}
}
Enter fullscreen mode Exit fullscreen mode

Note that this class doesn’t declare a constructor beyond the default empty constructor. Admittedly this is a very simple class that perhaps should be a stuct or a record, but simple is fine for articles.

We could create an instance of Pet with the following code that provides an initializer after the constructor call:

Pet myPet = new Pet()
{
   Breed = "Cairn Terrier",
   BirthYear = 2016,
   Name = "Jester"
};
Enter fullscreen mode Exit fullscreen mode

This has the same effect as the code we saw before, but the initializer allows us to make our code more readable as the number of properties we need to initialize grows.

After the class is created, you will not be able to modify Breed, BirthYear, or Name because they are init-only.

Init-only setters are something that you might not use frequently, but they offer an alternative to passing large numbers of parameters to constructors, which can simplify your classes and make their creation more readable as well.

Should I use Readonly, Get-only auto properties, or Init Only Setters?

Everything in this article is what I would consider to be an optional language feature of C#. You can use readonly, get-only auto properties, and init-only setters, but you never have to. Of course, all three of these language features will make your code more readable in its intent, slightly more performant, and can protect against undesired behavior for values you don’t want to change after creation.

If you do want to enforce that a value won’t change after an object is created, either readonly, a get-only auto property, or an init-only setter would work for you. So which one is right?

For me, I use the following decision-making process:

  1. If I want to store a value but don’t need to expose it as a property, I use a readonly field.
  2. If I have a small number of required attributes for a class and don’t expect that list to grow, I use a get only auto-property.
  3. If I have a large number of properties I want to set or expect my property list to grow significantly over time, I use an init-only setter.

Bear in mind, as always, that the more features of a language you use the greater your code’s learning curve will be. This is especially true for newer and less-frequently used aspects of a language.

Still, these language features help your intent be more readable, your code more performant, and reduce the amount of boiler-plate you use in your codebases.

What do you and your team use, and why?

Top comments (4)

Collapse
 
matiturock profile image
Matías Almirón

Muy buen post <3

Collapse
 
integerman profile image
Matt Eland

Merci!

Collapse
 
frederik_vl profile image
Frederik Van Lierde

Very good explanation!

Collapse
 
fanhestyle profile image
FanHe

After the class is created, you will not be able to modify Breed, BirthYear, or Name because they are init-only.

Name field isn't marked as init, it can be changed