CodeGym /Courses /C# SELF /The Role of Interfaces in C# Collections

The Role of Interfaces in C# Collections

C# SELF
Level 28 , Lesson 0
Available

1. Why do collections need interfaces?

Let's be real: while we were writing code with a specific type (List<int>, Dictionary<string, string>), everything was simple and clear. But imagine you suddenly decide to swap List<T> for HashSet<T> because you only need unique values. Or you want to make a method that could work with both types of collections. What do you do?

You need a single entry point — a "universal plug." In C#, that plug is called an interface. They define a set of rules (methods, properties) that a collection must have. Anything that implements the interface supports those operations.

It's like a phone charger: if you have a standard USB-C port, you can charge your phone, a modern vacuum cleaner, and even an electric toothbrush (just don't mix them up in the dark).

Real-life example

Say you want to write a method that sums up all the numbers in any collection of numbers. Here's how it would look without interfaces:


// This method only works with arrays:
static int SumArray(int[] array)
{
    int sum = 0;
    for (int i = 0; i < array.Length; i++)
    {
        sum += array[i];
    }
    return sum;
} 

// And this one - only with List<int>:
static int SumList(List<int> list)
{
    int sum = 0;
    for (int i = 0; i < list.Count; i++)
        sum += list[i];
    return sum;
}

Not cool! The code is almost the same. With the IEnumerable<int> interface, you can make it universal:


static int SumAll(IEnumerable<int> collection)
{
    int sum = 0;
    foreach (var number in collection)
        sum += number;
    return sum;
}

Now this method takes any type of collection that implements IEnumerable<int>: array, list, set, whatever.

2. Main collection interfaces

.NET has a few key interfaces for collections. Let's check them out quickly — you'll get the details on each in later lectures.

Interface What it's for Examples of collections implementing the interface
IEnumerable<T>
Enumerating elements (foreach) All:
List<T>
,
HashSet<T>
, arrays
ICollection<T>
Mutable set (Add, Remove, Count)
List<T>
,
HashSet<T>
,
Dictionary<K,V>
IList<T>
Index access, modification
List<T>
, arrays
IDictionary<K,V>
Working with "key-value" pairs
Dictionary<K,V>
,
SortedDictionary<K,V>
ISet<T>
Set (unique elements)
HashSet<T>
,
SortedSet<T>

P.S. For simplicity, we're skipping the "multiple levels" of inheritance between these interfaces for now. Who inherits from whom — we'll talk about that later.

Visualization: collection interface tree


           IEnumerable<T>
                 ^
                 |
          ICollection<T>
              ^       ^
              |       |
         IList<T>  ISet<T>
              |       |
         List<T>   HashSet<T>
Interface hierarchy for list and set collections

For dictionaries:


           IEnumerable<KeyValuePair<K,V>>
                      ^
                      |
               ICollection<KeyValuePair<K,V>>
                      ^
                      |
               IDictionary<K,V>
                      |
                Dictionary<K,V>
Interface hierarchy for dictionaries

3. Why use interfaces in real life?

Flexibility

When your method or class works with an interface (IEnumerable<int>) instead of a specific type (List<int>), you can pass in any collection that implements that interface. Your program becomes flexible and extendable. (Instead of "Only iPhone" — now any phone with Bluetooth will do.)


static void PrintAll(IEnumerable<string> collection)
{
    foreach (var item in collection)
        Console.WriteLine(item);
}

Now you can print elements of any list, array, set, even a LINQ query result!

Unification

If you make your own collection type (like some super-specific collection for a certain task), just implement the right interfaces — and your collection will work with other people's code that doesn't even know about your collection. That's why in .NET you often see: "program to interfaces, not concrete implementations."

Compatibility with standard tools

Thanks to supporting standard interfaces, your collections can work with LINQ, sorting, serialization, and other .NET features.

4. List<T> — it's a list, a collection, and an enumeration

A few words about enumeration

IEnumerable<T> is the main and most basic collection interface in .NET. It defines just one method — GetEnumerator(), which returns an enumerator object for iterating over the collection. That's why you can write:


foreach (var name in names)
{
    Console.WriteLine(name);
}

Where names can be a list, a set, or even an array. foreach is a sneaky bit of syntactic sugar that under the hood uses the GetEnumerator() method.

The IEnumerable interface (without <T>) is older and non-generic. These days, always use the generic version.

List class declaration syntax

Let's peek at the List<T> class declaration, just to see a bit of its magic:


public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, ...

This means a List<T> object can be used as:

  • A list with random index access (IList<T>)
  • A collection you can add/remove items from (ICollection<T>)
  • Just a set of elements you can iterate over (IEnumerable<T>)

Example:


List<string> names = new List<string> { "Anna", "Boris", "Vasya" };

// As IEnumerable - you can loop through it
IEnumerable<string> asEnumerable = names;
foreach (var n in asEnumerable) Console.WriteLine(n);

// As ICollection - you can get Count:
ICollection<string> asCollection = names;
int count = asCollection.Count;

// As IList - you can access by index:
IList<string> asList = names;
string firstItem = asList[0];

5. App example

Let's say we have a simple app for tracking users and their roles (we started building it in previous lectures):


// User class
public class User
{
    public string Name { get; set; }
    public int Age { get; set; }
}

We can store users in any collection that implements IEnumerable<User>:


List<User> listUsers = new List<User> { new User { Name = "Anna", Age = 20 } };
HashSet<User> setUsers = new HashSet<User> { new User { Name = "Boris", Age = 25 } };

// Method that prints all user names
static void PrintUserNames(IEnumerable<User> users)
{
    foreach (var user in users)
        Console.WriteLine(user.Name);
}

// Use with different collection types:
PrintUserNames(listUsers);
PrintUserNames(setUsers);

Now the app is way more flexible — no magic or custom overloads needed!

6. Typical mistakes and gotchas

One of the most common mistakes is using types that are too specific when an interface would do:


// Bad (hardwired to implementation)
void DoSomethingWithList(List<int> numbers) { ... }

// Better (method works with any collection)
void DoSomethingWithNumbers(IEnumerable<int> numbers) { ... }

Another trap — forgetting that an interface only gives you the operations it explicitly describes. For example, IEnumerable<T> doesn't let you get the number of elements (Count); for that, you need ICollection<T> — or count them manually.

2
Task
C# SELF, level 28, lesson 0
Locked
Implementing the IEnumerable<T> Interface for a Custom Collection
Implementing the IEnumerable<T> Interface for a Custom Collection
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION