How to work with read-only collections in C#

Take advantage of read-only generic interfaces such as IReadOnlyList, IReadOnlyDictionary, and IReadOnlyCollection to prevent modifications to collections in your .NET Core applications.

How to work with read-only collections in C#
Thinkstock

A collection represents a set of objects used for the storage and retrieval of data. Collections enable you to allocate memory dynamically to store elements and then retrieve them using a key or index as needed.

You can have standard or generic collections. While standard collections don’t provide type-safety, generic collections are type-safe. The standard collections are part of the System.Collections namespace, and the generic collections are part of the System.Collections.Generic namespace.

An immutable object is defined as an object that cannot be changed after it has been created. Not all collections are immutable, but you can use the read-only collection types in .NET Core such as IReadOnlyList, IReadOnlyDictionary, and IReadOnlyCollection to implement immutable types. These all are part of the System.Collections.Generic namespace.

This article discusses these read-only immutable collection types in .NET Core and how you can work with them in C#. To work with the code examples provided in this article, you should have Visual Studio 2019 installed in your system. If you don’t already have a copy, you can download Visual Studio 2019 here.

Create a console application project in Visual Studio

First off, let’s create a .NET Core console application project in Visual Studio. Assuming Visual Studio 2019 is installed in your system, follow the steps outlined below to create a new .NET Core console application project in Visual Studio.

  1. Launch the Visual Studio IDE.
  2. Click on “Create new project.”
  3. In the “Create new project” window, select “Console App (.NET Core)” from the list of templates displayed.
  4. Click Next.
  5. In the “Configure your new project” window shown next, specify the name and location for the new project.
  6. Click Create.

This will create a new .NET Core console application project in Visual Studio 2019. We’ll use this project in the subsequent sections of this article.

Read-only collections, dictionaries, and lists in .NET Core

The IReadOnlyCollection interface extends the IEnumerable interface and represents a basic read-only collection interface. It also includes a Count property apart from the IEnumerable members as shown in the code snippet given below.

IReadOnlyCollection<Product> data = products;
int numOfRecords = data.Count;

The IReadOnlyDictionary interface provides a read-only view of a dictionary and represents a read-only collection of key/value pairs. The following code snippet illustrates how you can define an IReadOnlyDictionary instance.

public IReadOnlyDictionary<string, string> Dictionary { get; } = new Dictionary<string, string>
        {
            { "1", "ABC" },
            { "2", "XYZ" },
            { "3", "PQR" },
        };

The IReadOnlyList<T> interface pertaining to the System.Collections.Generic namespace represents a read-only list, i.e., a read-only collection of elements that can be accessed by index.

Note that the IReadOnlyList interface works similarly to List and in fact can be used in place of List when you want the elements of the list to be read-only. We will illustrate this with an example in the next section.

Use IReadOnlyList instead of List in C#

Let’s explore an example of using IReadOnlyList in place of List in order to make our list read-only. Consider the following class:

public class Author
{
   public int Id { get; set; }
   public string FirstName { get; set; }
   public string LastName { get; set; }
}

Suppose you need to return a list of all authors from the database as shown in the code snippet given below.

public static List<Author> GetAuthors()
{
   return new List<Author>
   {
       new Author
       {
           Id = 1,
           FirstName = "Joydip",
           LastName = "Kanjilal"
       },
       new Author
       {
           Id = 2,
           FirstName = "Steve",
           LastName = "Smith"
       }
    };
}

For the sake of simplicity, we’re omitting the code necessary to retrieve data (i.e., author records) from the database. The following code snippet shows how you can call the GetAuthors method from the Main method.

static void Main(string[] args)
{
    var authors = GetAuthors();
    Console.Read();           
}

If you take advantage of IntelliSense in Visual Studio to see the members of the authors list object, you’ll see a list of methods supported by List<Author>. Figure 1 below displays the members of List<Author>. Note that you have a method named Add, so you can easily add authors to the list of authors we’ve created in the preceding code snippet.

read only collections csharp 01 IDG

Figure 1.

The collection used here is clearly mutable in nature. So how do you prevent this data from being changed?

Here’s where you can take advantage of IReadOnlyList to ensure that the returned data cannot be changed. By changing the return type of the GetAuthors method from List<Author> to IReadOnlyList<Author> we can make our collection read-only.

The following code snippet illustrates how IReadOnlyList can be used in place of List in the above method.

public static IReadOnlyList<Author> GetAuthors()
{
   return new List<Author>
   {
      new Author
      {
          Id = 1,
          FirstName = "Joydip",
          LastName = "Kanjilal"
      },
      new Author
      {
          Id = 2,
          FirstName = "Steve",
          LastName = "Smith"
      }
    };
}

Now when you use IntelliSense to inspect IReadOnlyList<Author>, you’ll no longer see Add among the supported methods. Figure 2 below displays the members of the IReadOnlyList<Author> instance.

read only collections csharp 02 IDG

Figure 2.

Use the IEnumberable interface in C#

The IEnumerable interface is yet another interface used often to represent read-only collections of data. If you simply want to enumerate the elements of a collection, you can use IEnumerable as shown below.

public void MyMethod(IEnumerable<Author> authors)
{
  foreach (Author author in authors)
  {
      //Write your code here
  }
}

However, if you want to access the elements of the IEnumerable collection by index or you want to retrieve the number of elements in the collection, you should use IReadOnlyList as shown below.

public void MyMethod(IReadOnlyList<Author> authors)
{
  int count = authors.Count;
  for(int index = 0; index < count; index++)
  {
      var author = authors[index];
      //Write your code here
  }
}

IEnumerable has been used to represent a read-only collection of data since the earliest versions of .NET. The read-only collection interfaces available in .NET Core and .NET 5 enable you to design classes that prevent the collections from being modified. However, it should be noted that while these read-only collection types provide a read-only view of the data, they are only wrappers. They do not provide an immutable copy of the collection.

How to do more in C#:

Copyright © 2021 IDG Communications, Inc.