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 [email protected]. I’d greatly appreciate your feedback! :)