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:
- IEnumerable<T> - The most basic interface, enabling iteration over a collection
- ICollection<T> - Adds size, enumeration, and modification capabilities
- IList<T> - Adds indexed access and more list-specific operations
- 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>
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>
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>
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>
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:
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:
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
-
Create a method that takes an
IEnumerable<int>
and returns the sum of all even numbers. -
Create a custom collection class implementing
IList<T>
that ensures all items added are unique and maintains items in a sorted order. -
Implement a simple caching system using
IDictionary<string, object>
that expires items after a certain time period. -
Create a method that takes an
ICollection<T>
and returns a newICollection<T>
containing only the unique elements from the original collection.
Additional Resources
- Microsoft Documentation: Collection Interfaces
- C# Programming Guide: Collections
- Implementing Collection Interfaces in C#
- Framework Design Guidelines: Collection Design
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! :)