48

I would like to create a list in C# that after its creation I won't be able to add or remove items from it. For example, I will create the list;

List<int> lst = a;

(a is an existing list), but after I won't be able to write the code (it will mark it as an error):

lst.Add(2);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ofer Magen
  • 942
  • 1
  • 9
  • 19
  • 2
    `readonly List lst = new List { 1, 2, 3 };` – Tim Schmelter Nov 24 '15 at 09:16
  • 16
    @TimSchmelter: That doesn't fulfil the requirements - it only makes the variable itself readonly. – Jon Skeet Nov 24 '15 at 09:17
  • 2
    Possible duplicate of [readonly list or unmodifiable list in .NET 4.0](http://stackoverflow.com/questions/984042/readonly-list-or-unmodifiable-list-in-net-4-0) – Chawin Nov 24 '15 at 09:28
  • 2
    @CHawin: that question is from 2009. Unsurprisingly, the solutions explored there are outdated and not very duplicative of those explored here, although there is some overlap. – Eamon Nerbonne Nov 24 '15 at 10:20
  • 1
    @EamonNerbonne apologies, when I flagged the question the `ReadonlyCollection` answer was the most voted result, I didn't realise that there were more up to date solutions. – Chawin Nov 24 '15 at 10:35

10 Answers10

57

.NET supports truly immutable collections, read-only views of mutable collections, and read-only interfaces implemented by mutable collections.


One such immutable collection is ImmutableArray<> which you can create as a.ToImmutableArray() in your example. Make sure to take a look at the other options MSDN lists because you may be better served by a different immutable collection. If you want to make copies of the original sequence with slight modifications, ImmutableList<> might be faster, for instance (the array is cheaper to create and access, though). Note that a.Add(...); is valid, but returns a new collection rather than changing a. If you have resharper, that will warn you if you ignore the return value of a pure method like Add (and there may be a roslyn extension to do something similar I'm unaware of). If you're going this route - consider skipping List<> entirely and going straight to immutable collections.

Read-only views of mutable collections are a little less safe but supported on older versions of .NET. The wrapping type is called ReadOnlyCollection<>, which in your example you might construct as a.AsReadOnly(). This collection does not guarantee immutability; it only guarrantees you can't change it. Some other bit of code that shares a reference to the underlying List<> can still change it. Also, ReadOnlyCollection also imposes some additional overhead; so you may not be winning much by avoiding immutable collections for performance reasons (TODO: benchmark this claim). You can use a read-only wrapper such as this even in a public API safely - there's no (non-reflection) way of getting the underlying list. However, since it's often no faster than immutable collections, and it's also not entirely safe, I recommend to avoid ReadOnlyCollection<> - I never use this anymore, personally.

Read-only interfaces implemented by mutable collections are even further down the scale of safety, but fast. You can simply cast List<> as IReadOnlyList<>, which you might do in your example as IReadOnlyList<int> lst = a. This is my preferences for internal code - you still get static type safety, you're simply not protected from malicious code or code that uses type-checks and casts unwisely (but those are avoidable via code-reviews in my experience). I've never been bitten by this choice, but it is less safe than the above two options. On the upside, it incurs no allocations and is faster. If you commonly do this, you may want to define an extension method to do the upcast for you (casts can be unsafe in C# because they not only do safe upcasts, but possibly failing downcasts, and user-defined conversions - so it's a good idea to avoid explicit casts wherever you can).

Note that in all cases, only the sequence itself is read-only. Underlying objects aren't affected (e.g. an int or string are immutable, but more complicated objects may or may not be).


TL;DR:

  • For safety: Use a.ToImmutableArray() to create an immutable copy in an ImmutableArray<int>.
  • For performance: Use IReadOnlyList<int> to help prevent accidental mutation in internal code with minimal performance overhead. Be aware that somebody can cast it back to List<> (don't do that), making this less "safe" for a public api.
  • Avoid a.AsReadOnly() which creates a ReadOnlyCollection<int> unless you're working on a legacy code base that doesn't support the newer alternatives, or if you really know what you're doing and have special needs (e.g. really do want to mutate the list elsewhere and have a read-only view).
Eamon Nerbonne
  • 47,023
  • 20
  • 101
  • 166
  • 7
    That is the first time that I see [`ReadOnlyCollection`](https://msdn.microsoft.com/en-us/library/ms132474.aspx) being described as "quite slow". What is the issue? Is it the overhead of an additional heap object + the forwarding method call to `IList` methods? What kind of speed difference are we talking about? Do you have any benchmarks? – Jean Hominal Nov 24 '15 at 13:01
  • The overhead to access an item is two indirect accesses and one interface-indirect access and two heap allocations over the underlying *array* - the array is a primitive object; the list wraps that; the usage of an interface to access the list makes accesses slower, and the readonlycollection wraps that. I did once benchmark these overheads, but the results are *really* out of date, so *please* don't take these results all that seriously anymore: http://eamon.nerbonne.org/2009/02/net-numeric-performance-disappointing.html – Eamon Nerbonne Nov 25 '15 at 09:40
  • 2
    @JeanHominal I'm going to edit the answer to de-emphasize performance, since I think it's a little misleading, especially without a much deeper discussion of what does and does not make code slow in these cases. AFAIK, it's not specifically ReadOnlyCollection that is slow, even though thats what the text suggested... – Eamon Nerbonne Nov 25 '15 at 09:41
  • Thank you for the benchmark, it does show some interesting things - for example, there is only a relatively small difference between "wrapping in `ReadOnlyCollection`" and "indirecting via `IList`", which is what I would expect. That could mean that the performance advantage of "using `IReadOnlyList`" over "using `ReadOnlyCollection`" would be relatively small. It also shows that access to an array using `IList` is expensive. – Jean Hominal Nov 25 '15 at 10:40
  • It could be interesting to compare, on a recent version of .NET (and the new JIT), between `T[]`, `List`, `ImmutableArray`, `ImmutableList`, `ReadOnlyCollection` (wrapping `T[]`) and `ReadOnlyCollection` (wrapping `List`), when they are used directly vs when they are casted to `IReadOnlyList`, and when using an indexed `for` loop vs when using a `foreach` loop. – Jean Hominal Nov 25 '15 at 10:49
  • @JeanHominal: yeah. Also: for small+large value types, and for reference types (since those are often treated differently). – Eamon Nerbonne Nov 25 '15 at 12:56
  • @JeanHominal, `ImmutableArray` has a struct enumerator and boasts several LINQ method implementations that avoid enumerator allocations altogether. `IReadOnlyList` obviously lacks in those regards. Even as you cast a `List` to `IReadOnlyList`, you may notice a slowdown due to losing `List`'s struct enumerator and falling back onto the explicit `GetEnumerator` implementation. Obviously, being a value type `ImmutableArray` results in boxing where you need to cast it to `IEnumerable`, so whether you opt to use it instead of `IReadOnlyList` is really scenario-specific. – Kirill Shlenskiy Dec 25 '15 at 03:01
  • Also `ToImmutableArray` tends to be quite slow compared to the "standard" `ToList` and `ToArray` calls - due to `ToImmutableArray`'s internal `TryGetCount` call, which sacrifices some CPU time to avoid array reallocations while the collection is being built. *However*, once you've got that `ImmutableArray` instance materialised, it tends to be blazing fast in scenarios which do not require casting to a reference type - `IReadOnlyList` doesn't come close. – Kirill Shlenskiy Dec 25 '15 at 03:07
11

You can use ImmutableList<T> / ImmutableArray<T> from System.Collections.Immutable NuGet:

var immutable = ImmutableList<int>.Create(1, 2, 3);

Or using the ToImmutableList extension method:

var immutable = mutableList.ToImmutableList();

In-case Add is invoked, *a new copy * is returned and doesn't modify the original list. This won't cause a compile time error though.

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
9

You need a ReadonlyCollection. You can create one from a list by calling List.AsReadOnly()

Reference: https://msdn.microsoft.com/en-us/library/ms132474.aspx

Patrick Allwood
  • 1,822
  • 17
  • 21
  • The new immutable data collections are a much better choice than `ReadonlyCollection`. – David Arno Nov 24 '15 at 09:23
  • 1
    The reason for the downvote is that eg `var list = new List { 1, 2, 3 }; var readonlyList = list.AsReadOnly(); list[1] = 1;` results in `readonlyList` being modified. `ReadonlyCollection` isn't an immutable data collection. – David Arno Nov 24 '15 at 09:27
  • 2
    Yes this is true, except in the asker's case he wants add/remove/etc operations to not be valid. An immutable list will allow it and return a new immutable list each time. Whether or not you can modify the items in the list itself is not a stated requirement. – Patrick Allwood Nov 24 '15 at 09:31
  • 1
    That doesn't really matter, the immutable `Add` etc. are *not* `IList.Add` etc. Just because the method has the same doesn't mean that it's the same operation :) – Luaan Nov 24 '15 at 09:34
  • @Luaan It's not the same. It allows the list to be misused without errors or warnings, leading to potential bugs. And this is specifically something that the asker wanted to prevent. – Aluan Haddad Nov 24 '15 at 09:37
  • @AluanHaddad, I'd say that quite the opposite is the case. `ImmutableList` can be used incorrectly by those that haven't read up on how to use it properly. This isn't *misuse* though. The fact that a `ReadonlyCollection` can be modified via the original list does leave it open to misuse. – David Arno Nov 24 '15 at 09:47
  • 2
    @DavidArno I certainly don't recommend ```ReadonlyCollection```, I avoid it as much as possible. If you see my answer, the question is, IMO best resolved by using an ```ImmutableList``` through the ```IReadonlyList``` interface. I meant accidental misuse by the way. – Aluan Haddad Nov 24 '15 at 09:54
  • 1
    @AluanHaddad: If you have resharper, it'll warn you if you fail to use the return value of a pure method. I've got that particular warning configured to be an error ;-). – Eamon Nerbonne Nov 24 '15 at 10:23
  • @EamonNerbonne that's a nice feature for sure, but we can do better by encoding it in the type. – Aluan Haddad Nov 25 '15 at 02:47
7

Why not just use an IEnumerable?

IEnumerable<string> myList = new List<string> { "value1", "value2" };
6

I recommend using a System.Collections.Immutable.ImmutableList<T> instance but referenced by a variable or property of type System.Collections.Generic.IReadOnlyList<T>. If you just use a naked immutable list, you won't get errors for adding to it, as you desire.

System.Collections.Generic.IReadOnlyList<int> list = a.ToImmutableList();
Aluan Haddad
  • 29,886
  • 8
  • 72
  • 84
5

As an alternative to the already posted answers, you can wrap a readonly regular List<T> into an object that exposes it as IReadOnlyList.

class ROList<T>
{
    public ROList(IEnumerable<T> argEnumerable)
    {
        m_list = new List<T>(argEnumerable);
    }

    private readonly List<T> m_list;
    public IReadOnlyList<T> List { get { return m_list; } }
}

void Main()
{
    var list = new  List<int> {1, 2, 3};
    var rolist = new ROList<int>(list);

    foreach(var i in rolist.List)
        Console.WriteLine(i);

    //rolist.List.Add(4); // Uncomment this and it won't compile: Add() is not allowed
}
elnigno
  • 1,751
  • 14
  • 37
  • @A.T. That's a question you should be asking the OP. In any case, there's plenty of good use cases for immutable lists (where you'd usually use the new immutable collections, of course), or even just readonly wrappers for mutable lists. – Luaan Nov 24 '15 at 09:32
5

Your best bet here is to use an IReadOnlyList<int>.

The advantage of using IReadOnlyList<int> compared to List.AsReadOnly() is that a ReadOnlyCollection<T> can be assigned to an IList<T>, which can then be accessed via a writable indexer.

Example to clarify:

var original = new List<int> { 1, 2, 3 };
IReadOnlyList<int> readOnlyList = original;

Console.WriteLine(readOnlyList[0]); // Compiles.

readOnlyList[0] = 0; // Does not compile.

var readOnlyCollection = original.AsReadOnly();

readOnlyCollection[0] = 1; // Does not compile.

IList<int> collection = readOnlyCollection; // Compiles.

collection[0] = 1; // Compiles, but throws runtime exception. 

Using an IReadOnlyList<int> avoids the possibility of accidentally passing the read-only list to a method which accepts an IList<> and which then tries to change an element - which would result in a runtime exception.

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
5

It could be IReadOnlyList<int>, e.g.

  IReadOnlyList<int> lst = a;

So the initial list (a) is mutable while lst is not. Often we use IReadOnlyList<T> for public properties and IList<T> for private ones, e.g.

  public class MyClass {
    // class itself can modify m_MyList 
    private IList<int> m_MyList = new List{1, 2, 3};

    ...
    // ... while code outside can't  
    public IReadOnlyList<int> MyList {
      get {
        return m_MyList; 
      }
    }  
  }
Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
2

Why not just:

readonly IEnumerable<int> lst = new List<int>() { a }
John Jang
  • 2,567
  • 24
  • 28
0
public readonly List<string> constList = new(){"value1", "value",...};

By adding readonly create a const equivalent list.