Differences between .NET Collection Interfaces
Tags: csharp, dotnet
Published: 2022-10-23 01:38:56

---
title: Differences between .NET Collection Interfaces
published: true
description: 
tags: csharp, dotnet
cover_image: https://newdevsguide.com/wp-content/uploads/2022/10/A_DotNetCollections.png
# Use a ratio of 100:42 for best results.
# published_at: 2022-10-23 01:34 +0000
canonical_url: https://newdevsguide.com/2022/10/09/understanding-dotnet-collection-interfaces/
---

If you’re anything like most new .NET developers you’ve probably seen `IEnumerable<T>`, `IList<T>`, and maybe even `ICollection<T>` around. If you’re anything like most people, you’ve probably found these interfaces confusing and been unsure which one to use when.

These interfaces are everywhere. For example, if you were to look at the `List<T>` class’s implementation in modern .NET you’d see it implements the following interfaces (among others):

- `IList<T>`
- `ICollection<T>`
- `IReadOnlyList<T>`
- `IReadOnlyCollection<T>`
- `IEnumerable<T>`

Each one of these interfaces has a point, and each one is slightly different. However, many of these interfaces can be intimidating the first time you encounter them.

In this article we’ll explore these common .NET interfaces, what they provide, and when you might choose to use each one.

*Note: There are also the older non-generic interfaces of IEnumerable and IList. For the purposes of this article we’ll focus instead on [generic collection](https://learn.microsoft.com/en-us/dotnet/standard/collections/when-to-use-generic-collections) interfaces since these are more commonly used in modern .NET.*

## Why not just use List&lt;T&gt;?

When I teach my students about `IEnumerable`, I usually see eyes gloss over from the abstractness of the concept and the foreignness of the word enumerable. A modest chunk of my students start panicking and wonder if they can scurry back to the safety of just working with `List<T>` and pretend that interfaces aren’t there.

The short answer is this: You *can* ignore collection interfaces *some* of the time.

However, sometimes you have to work with these interfaces when others provide them to you, and sometimes it’s better to work with these interfaces instead of concrete types.

To explore this, let’s take a look at this simple example below:

```csharp
private List<User> _users = new();
public List<User> Users
{
   get
   {
      return _users;
   }
}
```

Here it looks like we have a properly encapsulated List of User objects in the `_users `field. However, upon closer examination, there are a few problems:

First, by returning a` List<User>` we invite others to store the result of this call in List&lt;User&gt; variables or properties. This is fine for now, but if we ever change the type of collection we’re returning, our callers will need to update as well.

Secondly, by returning a` List<User>`, we give calling methods full access to the methods on that List object, including `Add`, `Remove`, and `Sort`. Worse, these methods will work and update the same `List `our code is using in its \_users field.

This violates encapsulation and may result in inconsistent state within the class due to changes to the internal state of the class coming from outside of the class in unintended ways.

Alternatively, if this *didn’t* actually modify the class, but the caller still thought they could use the `Add`, `Remove`, or `Sort `methods, they are now confused and frustrated because our design didn’t indicate to them that they *couldn’t* do this.

While these problems aren’t typically catastrophic, it’d be better to avoid them if we can, which is where .NET collection interfaces come into play.

## Common .NET Collection Interfaces

Because a picture can be far more efficient than entire sections, I’ve summarized these five interfaces in this comparison matrix:

![A comparison of common .NET collection interfaces as detailed below.](https://i0.wp.com/newdevsguide.com/wp-content/uploads/2022/10/CollectionComparison.png?resize=1024%2C252&ssl=1)

In the sections below we’ll explore each one of these five interfaces and when to use each one.

### IEnumerable&lt;T&gt;

`IEnumerable<T>` is a foundational interface in .NET that simply means that something can be looped over in a `foreach `statement.

`IEnumerable<T>` is supported by `List<T>`, arrays, and almost any other collection type in .NET.

That’s about all you need to know about `IEnumerable<T>`, but if you’d like a few more details, read on to learn about `IEnumerator<T>` as well.

- - - - - -

`IEnumerable<T>` internally works by providing a `GetEnumerator` method that returns an `IEnumerator<T>` object.

This `IEnumerator<T>` provides ways to:

- Get the current item (if one is present)
- Move to the next item
- Reset back to the first item

These three capabilities power any collection type that can be looped over.

### ICollection&lt;T&gt;

`ICollection<T>` allows you to manage collections of items. All `ICollection<T>`s are also `IEnumerable<T>`.

Using `ICollection<T>` you can:

- Get a `Count `of the number of items in a collection
- See if a collection `Contains `something
- Modify the collection by `Add`, `Remove`, and `Clear `methods
- Copy the collection to an array
- Determine if the collection is read only

Notably, `ICollection<T>` does not include array indexers in the interface, so you cannot use an indexer to get an item out of a collection by its index.

`ICollection<T>` is a great choice if you need to add and remove things from a collection as well as loop over it, but you don’t need to use an indexer.

### IList&lt;T&gt;

`IList<T>` is an extremely powerful and common ordered collection interface in .NET.

`IList<T>`s all implement both `ICollection<T>` and `IEnumerable<T>` but add the ability to work with indexes.

Specifically, `IList<T`&gt; adds the following capabilities:

- A collection *indexer* that lets you use syntax like int number = myList\[0\];
- Allows you to find the `IndexOf `an item in the list
- `Insert `items at a specific index
- `RemoveAt `a specific index

If your collection is ordered and has specific indexes and you want others to take full advantage of them, `IList<T>` is a great choice.

### IReadOnlyCollection&lt;T&gt;

`IReadOnlyCollection<T>` is a rarer interface to see, but it is essentially a version of `IEnumerable<T>` that also has a Count of items.

You can also think about `IReadOnlyCollection<T>` as a version of Collection&lt;T&gt; that removes ways of modifying the items.

All `IReadOnlyCollection<T>`s are also `IEnumerable<T>`.

`IReadOnlyCollection<T>` is a good choice if you want to return a collection but don’t want to expose ways of manipulating it through the interface.

### IReadOnlyList&lt;T&gt;

`IReadOnlyList<T>` is an extension of `IReadOnlyCollection<T>` but adds an indexer so that you can get values out of the collection by an index. Otherwise, `IReadOnlyList<T>` is functionally identical to `IReadOnlyCollection<T>`.

## Securing against Casting

You may now be thinking that using an IReadOnlyList&lt;T&gt; or IReadOnlyCollection&lt;T&gt; protects you from others modifying your code.

However, that actually may not be fully true.

Let’s revisit our example from earlier, but take advantage of IReadOnlyList&lt;T&gt;:

```csharp
private List<User> _users = new();
public IReadOnlyList<User> Users
{
   get
   {
      return _users;
   }
}
```

Here the class can use a full List&lt;User&gt; internally but only expose IReadOnlyList&lt;User&gt; to external callers.

This stops callers from doing the following:

```csharp
// Wouldn't work since Users is an IReadOnlyList
myObject.Users.Add(new User("Matt Eland"));
```

However, since the actual object being returned above is still a `List<User>`, savvy callers may notice this and cast the result to a `List<User>` and then interact with it in a way you didn’t intend:

```csharp
// Cast the result of the Users property to a List<User>
List<User> users = (List<User>)myObject.Users;

// This works
users. Add(new User("Matt Eland"));
```

Because of this, it’s a good idea to secure the items you intend to be read only by calling either `ToList()` or `AsReadOnly()` on them using LINQ to create a separate collection when returning values as shown below:

```csharp
private List<User> _users = new();
public IReadOnlyList<User> Users
{
   get
   {
      return _users.AsReadOnly();
   }
}
```

## Conclusion

While it’s certainly possible to write code that just returns Lists or other concrete types, using the appropriate .NET collection interface can help your code shine.

Using the right interface lets others see your intent, helps you properly encapsulate your classes, and gives you flexibility to change collection types in a class without impacting callers.