C# > Advanced C# > Collections and Generics > ReadOnlyCollection<T>

Using ReadOnlyCollection to Protect Internal Data

This code snippet demonstrates how to use `ReadOnlyCollection ` to expose a collection without allowing modification of the underlying list from outside the class. This is a common technique for encapsulating data and maintaining the integrity of your object's state.

Scenario

Imagine a class that manages a list of employees. You want to provide access to this list to other parts of your application, but you don't want them to be able to directly add, remove, or modify the employees. Using `ReadOnlyCollection` is a perfect solution.

Code Example

This code defines an `EmployeeManager` class that holds a list of employee names. The `Employees` property is a `ReadOnlyCollection`, which is initialized with the contents of the internal `employeeList`. The `AddEmployee` and `RemoveEmployee` methods modify the underlying list, then *recreate* the `ReadOnlyCollection` to reflect the changes. Attempting to modify the `Employees` property directly from outside the class will result in a `NotSupportedException`.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

public class EmployeeManager
{
    private List<string> employeeList = new List<string>();
    public ReadOnlyCollection<string> Employees { get; private set; }

    public EmployeeManager()
    {
        employeeList.Add("Alice Smith");
        employeeList.Add("Bob Johnson");
        Employees = new ReadOnlyCollection<string>(employeeList);
    }

    public void AddEmployee(string name)
    {
        employeeList.Add(name);
        //Recreate the ReadOnlyCollection if underlying list is modified
        Employees = new ReadOnlyCollection<string>(employeeList);
    }

    public void RemoveEmployee(string name)
    {
        employeeList.Remove(name);
        //Recreate the ReadOnlyCollection if underlying list is modified
        Employees = new ReadOnlyCollection<string>(employeeList);
    }

    public void DisplayEmployees()
    {
        foreach (var employee in Employees)
        {
            Console.WriteLine(employee);
        }
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        EmployeeManager manager = new EmployeeManager();
        manager.DisplayEmployees();

        // Attempting to modify the ReadOnlyCollection will throw an exception
        // manager.Employees.Add("Charlie Brown"); // This will cause a runtime error

        manager.AddEmployee("Charlie Brown");
        Console.WriteLine("After adding Charlie:");
        manager.DisplayEmployees();

    }
}

Concepts Behind the Snippet

`ReadOnlyCollection` is a wrapper around an `IList`. It provides a read-only view of the collection. It prevents external code from adding, removing, or modifying the elements in the underlying list. This is important for maintaining the internal state of your objects and preventing unintended side effects.

Real-Life Use Case

Configuration settings are often stored in a collection. You might want to expose these settings to other parts of your application for reading, but prevent them from being modified after initialization. `ReadOnlyCollection` is a good choice for this scenario. Another example includes returning data from a database query. You may want to ensure that the calling code cannot modify the data directly but only work with a copy of the data.

Best Practices

  • Always create a new `ReadOnlyCollection` if the underlying list is modified. Failing to do so will result in the `ReadOnlyCollection` not reflecting the changes.
  • Ensure the underlying list is properly initialized before creating the `ReadOnlyCollection`.
  • Do not expose the underlying `List` directly. This defeats the purpose of using `ReadOnlyCollection`.

Interview Tip

Be prepared to explain the difference between `ReadOnlyCollection`, `IEnumerable`, and immutability. `ReadOnlyCollection` is a wrapper that prevents modification of the underlying list. `IEnumerable` is an interface that allows iteration over a collection. Immutability means that the object's state cannot be changed after it's created. `ReadOnlyCollection` provides a *mutable* collection with a *read-only* wrapper, whereas an immutable collection creates a completely new instance on any modifications. `ReadOnlyCollection` prevents modification from *outside*, not *inside* the class that owns the underlying list.

When to Use Them

Use `ReadOnlyCollection` when you want to provide read-only access to a collection while retaining the ability to modify the underlying list internally. This is a good choice when you need to protect the integrity of your object's state and prevent unintended side effects.

Memory Footprint

The memory footprint of `ReadOnlyCollection` is relatively small. It essentially holds a reference to the underlying `IList`. There's a slight overhead for the wrapper object itself.

Alternatives

  • `IEnumerable`: Provides read-only access but does not guarantee that the underlying collection cannot be modified. It only guarantees that you can iterate over the elements.
  • Immutable Collections: Provides true immutability. Any modification creates a new collection. This offers the highest level of protection but can be less efficient for frequent modifications.
  • Defensive Copying: Create a copy of the list and return the copy. This also creates immutability for the client, but comes with a larger memory footprint and overhead of copying.

Pros

  • Provides read-only access to a collection.
  • Prevents external modification of the underlying list.
  • Lightweight wrapper with minimal overhead.
  • Allows internal modification of the underlying list.

Cons

  • Not truly immutable. The underlying list can be modified internally.
  • Requires creating a new `ReadOnlyCollection` if the underlying list is modified.

FAQ

  • What happens if I try to modify a `ReadOnlyCollection`?

    A `NotSupportedException` will be thrown.
  • Is `ReadOnlyCollection` truly immutable?

    No, it's not. The underlying list can still be modified from within the class that owns it. It only prevents modification from external code.
  • How do I update the `ReadOnlyCollection` if the underlying list changes?

    You need to create a new `ReadOnlyCollection` instance with the updated list.