Every now and then I come across developers that does not quite get their heads around the meaning of the readonly keyword in C#, and perhaps especially when combined with arrays. Since I like to exemplify with code, let’s start in that end:

class Numbers
{
    public readonly List<int> OneThroughFour = new List<int>{ 1, 2, 3, 4 };
}

class Program
{
    static void Main(string[] args)
    {
        Numbers numbers = new Numbers();
        numbers.OneThroughFour[3] = 42;
        Console.WriteLine(string.Join(", ", numbers.OneThroughFour));
    }
}

This will print "1, 2, 3, 42", which seems confusing for some. If readonly does not make the list read-only, what does it do? The documentation for the readonly keyword says the following:

The readonly keyword is a modifier that you can use on fields. Whena field declaration includes a readonly modifier, assignments to the fields introduced by the declaration can only occur as part of the declaration or in a constructor in the same class.

It clearly says that assignments to the field can only occur during the construction of the class. In our code sample above we assign a value to the list (numbers[3] = 42;). Is that not an assignment to the field? No, it is an assignment to a property in the object that the field holds a reference to, it is not an assignment to the field itself. If we would try to replace the list with another list, the compiler would give us an error:

// the following line gives the compilation error "A readonly
// field cannot be assigned to (except in a static constructor
// or a variable initializer)"
numbers.OneThroughFour = new List<int> { 1, 2, 3, 42 };

OK, but what if the field is an array?

class Numbers
{
    public readonly int[] OneThroughFour = new[] { 1, 2, 3, 4 };
}

class Program
{
    static void Main(string[] args)
    {
        Numbers numbers = new Numbers();
        numbers.OneThroughFour[3] = 42;
        Console.WriteLine(string.Join(",", numbers.OneThroughFour));
    }
}

The result of this code is exactly the same as in the previous example ("1, 2, 3, 42"). Again, we are not assigning a value to the field, but to an element of the array that the field holds a reference to. Now I can hear somebody say “but hey, wait a minute… isn’t int a value type? So the field should hold a copy of the data rather than a reference to it, right?” Yes, int is a value type, but in .NET all arrays are reference types. This means that any code that obtains a reference to the array can also alter its elements. Again, the readonly keyword only prevents the array object itself from being replaced by another array object.

But what if I really want the list read-only?

The first step you should take is to make sure not to expose the field itself outside the type in which it is declared. That way you gain control over what code that can access (and thus modify) the contents of the array directly. The second step is to show intent. Expose the array in a manner that communicates that it should be regarded as being readonly, by returning it as an IEnumerable<int> rather than int[]:

class Numbers
{
    private readonly int[] oneThroughFour = new[] { 1, 2, 3, 4 };

    public IEnumerable<int> GetOneThroughFour()
    {
        return oneThroughFour;
    }
}

This will make any attempt to assign a value to an element (such as numbers.GetOneThroughFour()[3] = 42;) result in a compiler error, since IEnumerable<T> does not support accessing elements by index. This clearly tells developers writing calling code to regard the returned list as a read-only list. However, there is nothing that prevents calling code from manipulating the internal array, using a simple type cast:

((int[])numbers.GetOneThroughFour())[3] = 42;

For most common uses, I would say this is sufficient (unless the type is exposed in some commercial class library or similar); at this point we quite effectively prevent developers from accidentally writing code that modifies the list.

If it is critical that you prevent modification of the list on a technical level, there are some different approaches:

Use a ReadOnlyCollection<T> for storing the list:

class Numbers
{
    private readonly ReadOnlyCollection<int>
    oneThroughFour = new ReadOnlyCollection<int>(new[] { 1, 2, 3, 4 });

    public IEnumerable<int> GetOneThroughFour()
    {
        return oneThroughFour;
    }
}

Store the list as an array, but return a copy of the array:

class Numbers
{
    private readonly int[] oneThroughFour = new[] { 1, 2, 3, 4 };

    public IEnumerable<int> GetOneThroughFour()
    {
        return (IEnumerable<int>)oneThroughFour.Clone();
    }
}

Yet another option is to store the list as an array, but use yield return for returning the items. This might be preferable if the list is potentially large:

class Numbers
{
    private readonly int[] oneThroughFour = new[] { 1, 2, 3, 4 };

    public IEnumerable<int> GetOneThroughFour()
    {
        int index = 0;
        while (index < oneThroughFour.Length)
        {
            yield return oneThroughFour[index++];
        }
    }
}

This will throw an InvalidCastException if calling code attempts to cast the return value to int[].

/Fredrik Mörk