Skip to main content

C# Collection Interfaces

Collection interfaces form the backbone of C#'s collections framework, providing consistent ways to work with different types of collections. Understanding these interfaces is crucial for effective programming in C#, as they enable you to write more flexible and reusable code.

Introduction to Collection Interfaces​

In C#, collection interfaces define standard behaviors that collection classes implement. By programming against these interfaces rather than concrete implementations, you create more maintainable and flexible code. The .NET Framework provides a rich set of collection interfaces, each serving specific purposes.

Let's explore the primary collection interfaces that you'll encounter regularly in C# programming.

Core Collection Interfaces Hierarchy​

The collection interfaces in C# form a hierarchy, with each interface building upon the capabilities of the ones above it:

  1. IEnumerable<T> - The most basic interface, enabling iteration over a collection
  2. ICollection<T> - Adds size, enumeration, and modification capabilities
  3. IList<T> - Adds indexed access and more list-specific operations
  4. IDictionary<TKey, TValue> - Provides key-value pair functionality

Let's examine each of these interfaces in detail.

IEnumerable<T> Interface​

IEnumerable<T> is the simplest and most fundamental collection interface. It has just one method: GetEnumerator(), which returns an enumerator that lets you iterate through the collection.

Key Features of IEnumerable<T>​

  • Enables foreach loop iteration
  • Read-only access to a collection
  • Language-integrated query (LINQ) works with any IEnumerable<T>

Example of Using IEnumerable<T>​

csharp
using System;
using System.Collections.Generic;

public class EnumerableExample
{
public static void Main()
{
// Any collection that implements IEnumerable<T> can be iterated with foreach
IEnumerable<string> names = new List<string> { "Alice", "Bob", "Charlie" };

// Using foreach with IEnumerable<T>
Console.WriteLine("Names in the collection:");
foreach (string name in names)
{
Console.WriteLine($"- {name}");
}

// Using LINQ with IEnumerable<T>
IEnumerable<string> filteredNames = names.Where(name => name.StartsWith("A"));
Console.WriteLine("\nNames starting with 'A':");
foreach (string name in filteredNames)
{
Console.WriteLine($"- {name}");
}
}
}

Output:

Names in the collection:
- Alice
- Bob
- Charlie

Names starting with 'A':
- Alice

ICollection<T> Interface​

ICollection<T> extends IEnumerable<T> by adding properties and methods to check the size of the collection and modify its contents.

Key Features of ICollection<T>​

  • Provides a Count property to determine the number of items
  • Includes methods for adding and removing items
  • Offers a way to check if an item exists in the collection
  • Allows copying to an array
  • Has a IsReadOnly property

Example of Using ICollection<T>​

csharp
using System;
using System.Collections.Generic;

public class CollectionExample
{
public static void Main()
{
// Creating an ICollection<T>
ICollection<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

// Getting the count
Console.WriteLine($"Collection contains {numbers.Count} items");

// Adding an item
numbers.Add(6);
Console.WriteLine($"After adding: Collection contains {numbers.Count} items");

// Checking if an item exists
bool containsThree = numbers.Contains(3);
Console.WriteLine($"Collection contains 3: {containsThree}");

// Removing an item
bool removed = numbers.Remove(2);
Console.WriteLine($"Removed 2: {removed}");
Console.WriteLine($"After removal: Collection contains {numbers.Count} items");

// Copying to an array
int[] array = new int[numbers.Count];
numbers.CopyTo(array, 0);

Console.WriteLine("\nArray contents:");
foreach (int num in array)
{
Console.WriteLine($"- {num}");
}

// Checking if collection is read-only
Console.WriteLine($"\nCollection is read-only: {numbers.IsReadOnly}");

// Clearing the collection
numbers.Clear();
Console.WriteLine($"After clearing: Collection contains {numbers.Count} items");
}
}

Output:

Collection contains 5 items
After adding: Collection contains 6 items
Collection contains 3: True
Removed 2: True
After removal: Collection contains 5 items

Array contents:
- 1
- 3
- 4
- 5
- 6

Collection is read-only: False
After clearing: Collection contains 0 items

IList<T> Interface​

IList<T> extends ICollection<T> by adding indexed access and more list-specific operations.

Key Features of IList<T>​

  • Provides indexed access to elements (list[index])
  • Enables inserting and removing items at specific positions
  • Allows finding the index of an item

Example of Using IList<T>​

csharp
using System;
using System.Collections.Generic;

public class ListExample
{
public static void Main()
{
// Creating an IList<T>
IList<string> fruits = new List<string> { "Apple", "Banana", "Cherry" };

// Accessing by index
Console.WriteLine($"Item at index 1: {fruits[1]}");

// Modifying an item by index
fruits[1] = "Blueberry";
Console.WriteLine($"After modification, item at index 1: {fruits[1]}");

// Inserting at a specific index
fruits.Insert(1, "Blackberry");

// Finding index of an item
int cherryIndex = fruits.IndexOf("Cherry");
Console.WriteLine($"Index of Cherry: {cherryIndex}");

// Removing at a specific index
fruits.RemoveAt(0); // Removes "Apple"

Console.WriteLine("\nFruits after modifications:");
foreach (string fruit in fruits)
{
Console.WriteLine($"- {fruit}");
}
}
}

Output:

Item at index 1: Banana
After modification, item at index 1: Blueberry
Index of Cherry: 3

Fruits after modifications:
- Blackberry
- Blueberry
- Cherry

IDictionary<TKey, TValue> Interface​

IDictionary<TKey, TValue> is a specialized interface for collections of key-value pairs, where each key must be unique.

Key Features of IDictionary<TKey, TValue>​

  • Stores key-value pairs
  • Provides fast lookup by key
  • Ensures unique keys
  • Offers collections of keys and values

Example of Using IDictionary<TKey, TValue>​

csharp
using System;
using System.Collections.Generic;

public class DictionaryExample
{
public static void Main()
{
// Creating an IDictionary<TKey, TValue>
IDictionary<string, int> ages = new Dictionary<string, int>
{
{ "Alice", 25 },
{ "Bob", 30 },
{ "Charlie", 35 }
};

// Accessing by key
Console.WriteLine($"Bob's age: {ages["Bob"]}");

// Adding a new key-value pair
ages["David"] = 40;

// Checking if a key exists
bool hasEvelyn = ages.ContainsKey("Evelyn");
Console.WriteLine($"Dictionary contains Evelyn: {hasEvelyn}");

// Safe way to get a value
if (ages.TryGetValue("Alice", out int aliceAge))
{
Console.WriteLine($"Alice's age: {aliceAge}");
}

// Removing a key-value pair
ages.Remove("Charlie");

// Getting collections of keys and values
ICollection<string> keys = ages.Keys;
ICollection<int> values = ages.Values;

Console.WriteLine("\nAll keys:");
foreach (string key in keys)
{
Console.WriteLine($"- {key}");
}

Console.WriteLine("\nAll values:");
foreach (int value in values)
{
Console.WriteLine($"- {value}");
}

Console.WriteLine("\nAll key-value pairs:");
foreach (KeyValuePair<string, int> pair in ages)
{
Console.WriteLine($"- {pair.Key}: {pair.Value}");
}
}
}

Output:

Bob's age: 30
Dictionary contains Evelyn: False
Alice's age: 25

All keys:
- Alice
- Bob
- David

All values:
- 25
- 30
- 40

All key-value pairs:
- Alice: 25
- Bob: 30
- David: 40

Real-World Example: Student Management System​

Let's create a simple student management system that uses different collection interfaces:

csharp
using System;
using System.Collections.Generic;
using System.Linq;

public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int Grade { get; set; }

public override string ToString() => $"{Id}: {Name} (Grade: {Grade})";
}

public class StudentManagementSystem
{
// Using IDictionary to store students by ID
private IDictionary<int, Student> studentsById;

// Using IList to maintain a sorted list
private IList<Student> sortedStudents;

public StudentManagementSystem()
{
studentsById = new Dictionary<int, Student>();
sortedStudents = new List<Student>();
}

public void AddStudent(Student student)
{
// Add to dictionary for fast lookup
if (studentsById.ContainsKey(student.Id))
{
Console.WriteLine($"Error: Student ID {student.Id} already exists!");
return;
}

studentsById[student.Id] = student;

// Add to sorted list
sortedStudents.Add(student);
// Keep the list sorted by name
SortStudentsByName();

Console.WriteLine($"Added student: {student}");
}

public Student GetStudentById(int id)
{
if (studentsById.TryGetValue(id, out Student student))
{
return student;
}

Console.WriteLine($"Student with ID {id} not found!");
return null;
}

public IEnumerable<Student> GetStudentsByGrade(int grade)
{
// Using IEnumerable for filtered results
return studentsById.Values.Where(s => s.Grade == grade);
}

public ICollection<Student> GetAllStudents()
{
return studentsById.Values;
}

public IList<Student> GetStudentsSortedByName()
{
return sortedStudents;
}

private void SortStudentsByName()
{
// Create a new sorted list and replace the existing one
List<Student> sorted = new List<Student>(sortedStudents);
sorted.Sort((a, b) => a.Name.CompareTo(b.Name));

// Clear and refill the sorted list
sortedStudents.Clear();
foreach (Student student in sorted)
{
sortedStudents.Add(student);
}
}

public static void Main()
{
StudentManagementSystem sms = new StudentManagementSystem();

// Adding students
sms.AddStudent(new Student { Id = 101, Name = "Alice", Grade = 85 });
sms.AddStudent(new Student { Id = 102, Name = "Charlie", Grade = 90 });
sms.AddStudent(new Student { Id = 103, Name = "Bob", Grade = 85 });
sms.AddStudent(new Student { Id = 104, Name = "Diana", Grade = 95 });

// Attempting to add duplicate ID
sms.AddStudent(new Student { Id = 101, Name = "Eve", Grade = 80 });

Console.WriteLine("\n--- Finding a student by ID ---");
Student student = sms.GetStudentById(102);
if (student != null)
{
Console.WriteLine($"Found: {student}");
}

Console.WriteLine("\n--- Students with grade 85 ---");
foreach (Student s in sms.GetStudentsByGrade(85))
{
Console.WriteLine(s);
}

Console.WriteLine("\n--- All students ---");
foreach (Student s in sms.GetAllStudents())
{
Console.WriteLine(s);
}

Console.WriteLine("\n--- Students sorted by name ---");
foreach (Student s in sms.GetStudentsSortedByName())
{
Console.WriteLine(s);
}
}
}

Output:

Added student: 101: Alice (Grade: 85)
Added student: 102: Charlie (Grade: 90)
Added student: 103: Bob (Grade: 85)
Added student: 104: Diana (Grade: 95)
Error: Student ID 101 already exists!

--- Finding a student by ID ---
Found: 102: Charlie (Grade: 90)

--- Students with grade 85 ---
101: Alice (Grade: 85)
103: Bob (Grade: 85)

--- All students ---
101: Alice (Grade: 85)
102: Charlie (Grade: 90)
103: Bob (Grade: 85)
104: Diana (Grade: 95)

--- Students sorted by name ---
101: Alice (Grade: 85)
103: Bob (Grade: 85)
102: Charlie (Grade: 90)
104: Diana (Grade: 95)

When to Use Each Interface​

Choosing the right collection interface depends on your specific needs:

  • IEnumerable<T>: When you only need to iterate through a collection and perform operations like LINQ queries.

  • ICollection<T>: When you need to know the size of the collection and perform basic operations like adding and removing items.

  • IList<T>: When you need indexed access and position-based operations like insertion and removal at specific positions.

  • IDictionary<TKey, TValue>: When you need to store and retrieve values based on keys, and keys must be unique.

Custom Collections and Interface Implementation​

Sometimes, you might want to create your own collection class that implements these interfaces. Here's a simple example:

csharp
using System;
using System.Collections;
using System.Collections.Generic;

// A custom collection that implements ICollection<T>
public class UniqueList<T> : ICollection<T>
{
private List<T> internalList = new List<T>();

// ICollection<T> properties
public int Count => internalList.Count;
public bool IsReadOnly => false;

// ICollection<T> methods
public void Add(T item)
{
if (!internalList.Contains(item))
{
internalList.Add(item);
}
}

public void Clear() => internalList.Clear();

public bool Contains(T item) => internalList.Contains(item);

public void CopyTo(T[] array, int arrayIndex) =>
internalList.CopyTo(array, arrayIndex);

public bool Remove(T item) => internalList.Remove(item);

// IEnumerable<T> implementation
public IEnumerator<T> GetEnumerator() => internalList.GetEnumerator();

// IEnumerable implementation
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

// Demo
public static void Main()
{
UniqueList<string> uniqueNames = new UniqueList<string>();

// Add items
uniqueNames.Add("Alice");
uniqueNames.Add("Bob");
uniqueNames.Add("Alice"); // This won't be added as it's a duplicate
uniqueNames.Add("Charlie");

// Show the items
Console.WriteLine($"Count: {uniqueNames.Count}");
Console.WriteLine("Items:");
foreach (string name in uniqueNames)
{
Console.WriteLine($"- {name}");
}
}
}

Output:

Count: 3
Items:
- Alice
- Bob
- Charlie

Summary​

Collection interfaces in C# provide a standardized way to work with groups of related objects. Understanding these interfaces helps you write more flexible and reusable code:

  • IEnumerable<T>: The most basic interface, enabling iteration over a collection.
  • ICollection<T>: Adds the ability to modify the collection and check its size.
  • IList<T>: Adds indexed access and position-based operations.
  • IDictionary<TKey, TValue>: Provides key-value pair functionality.

By programming against interfaces rather than concrete implementations, you can write more maintainable code that can easily adapt to different collection types.

Exercises​

  1. Create a method that takes an IEnumerable<int> and returns the sum of all even numbers.

  2. Create a custom collection class implementing IList<T> that ensures all items added are unique and maintains items in a sorted order.

  3. Implement a simple caching system using IDictionary<string, object> that expires items after a certain time period.

  4. Create a method that takes an ICollection<T> and returns a new ICollection<T> containing only the unique elements from the original collection.

Additional Resources​

Understanding these interfaces thoroughly will give you a solid foundation for working with collections in C# and help you make informed decisions about which collection types to use in your applications.



If you spot any mistakes on this website, please let me know at feedback@compilenrun.com. I’d greatly appreciate your feedback! :)