C# tutorials > Core C# Fundamentals > Data Structures and Collections > What are the main interfaces in the .NET Collections Framework (`IEnumerable`, `ICollection`, `IList`, `ISet`, `IDictionary`)?

What are the main interfaces in the .NET Collections Framework (`IEnumerable`, `ICollection`, `IList`, `ISet`, `IDictionary`)?

The .NET Collections Framework provides a set of interfaces and classes that define and implement various data structures. Understanding the core interfaces is crucial for effectively working with collections in C#. This tutorial will explore the main interfaces: IEnumerable, ICollection, IList, ISet, and IDictionary.

Introduction to .NET Collections Framework Interfaces

The .NET Collections Framework offers a variety of interfaces that provide a standardized way to work with groups of objects. Each interface defines a contract that classes can implement to provide specific collection behaviors. These interfaces build upon each other, offering increasing levels of functionality.

IEnumerable: The Foundation for Iteration

IEnumerable is the most basic interface for collections in .NET. It defines a single method, GetEnumerator(), which returns an IEnumerator object. The IEnumerator allows you to iterate over the elements in the collection using a foreach loop.

Any class that implements IEnumerable can be iterated over. This interface establishes the fundamental concept of a sequence of elements.

public interface IEnumerable
{
    IEnumerator GetEnumerator();
}

ICollection: Adding Size and Modification

ICollection extends IEnumerable and adds properties and methods for getting the size of the collection (Count) and copying the collection to an array (CopyTo). It also includes properties related to thread safety (IsSynchronized and SyncRoot), although these are less commonly used in modern .NET development.

ICollection represents a collection that can be modified (in some implementations). It provides basic information about the collection, such as its size.

public interface ICollection : IEnumerable
{
    int Count { get; }
    bool IsSynchronized { get; }
    object SyncRoot { get; }
    void CopyTo(Array array, int index);
}

IList: Indexed Access and Ordering

IList extends ICollection and adds the ability to access elements by their index. It introduces the indexer (this[int index]), allowing you to get or set elements at specific positions. It also includes methods for adding, inserting, removing, and clearing elements.

IList represents an ordered collection where elements can be accessed by their position. Examples include arrays and lists.

public interface IList : ICollection
{
    object this[int index] { get; set; }
    int Add(object value);
    void Clear();
    bool Contains(object value);
    int IndexOf(object value);
    void Insert(int index, object value);
    bool IsFixedSize { get; }
    bool IsReadOnly { get; }
    void Remove(object value);
    void RemoveAt(int index);
}

ISet: Unique Values Only

ISet represents a collection of unique elements. It implements ICollection, but adds methods to perform set operations such as union, intersection, and difference. The key feature of an ISet is that it does not allow duplicate elements.

Typical implementations include HashSet<T> and SortedSet<T>.

public interface ISet<T> : ICollection<T>, IEnumerable<T>, IEnumerable
{
    bool Add(T item);
    void ExceptWith(IEnumerable<T> other);
    void IntersectWith(IEnumerable<T> other);
    bool IsProperSubsetOf(IEnumerable<T> other);
    bool IsProperSupersetOf(IEnumerable<T> other);
    bool IsSubsetOf(IEnumerable<T> other);
    bool IsSupersetOf(IEnumerable<T> other);
    bool Overlaps(IEnumerable<T> other);
    bool SetEquals(IEnumerable<T> other);
    void SymmetricExceptWith(IEnumerable<T> other);
    void UnionWith(IEnumerable<T> other);
}

IDictionary: Key-Value Pairs

IDictionary represents a collection of key-value pairs. It extends ICollection and provides methods for adding, retrieving, and removing elements based on their key. The indexer (this[object key]) allows you to access values by their associated key.

Examples include Hashtable and Dictionary<TKey, TValue>.

public interface IDictionary : ICollection
{
    object this[object key] { get; set; }
    ICollection Keys { get; }
    ICollection Values { get; }
    void Add(object key, object value);
    void Clear();
    bool Contains(object key);
    IDictionaryEnumerator GetEnumerator();
    void Remove(object key);
}

Concepts Behind the Snippet

The core concept behind these interfaces is to provide a common way to work with different types of collections. By adhering to these interfaces, classes can be written that operate generically on any collection type, as long as it implements the required interface. This promotes code reusability and flexibility.

The inheritance relationship between these interfaces (IEnumerable -> ICollection -> IList, IEnumerable -> ICollection -> IDictionary and IEnumerable -> ICollection -> ISet) reflects the increasing levels of functionality they provide.

Real-Life Use Case Section

Consider a scenario where you need to store a list of students in a class. You could use an IList<Student> to store the students in a specific order and access them by their index. If you only need to iterate over the students without requiring indexed access, you could use an IEnumerable<Student>.

If you want to store student IDs as keys and student objects as values, you would use an IDictionary<int, Student>. If you only want to store unique student names, an ISet<string> would be appropriate.

Best Practices

  • Choose the most appropriate interface: Select the interface that provides the necessary functionality without adding unnecessary overhead. For example, if you only need to iterate, use IEnumerable instead of IList.
  • Use generic collections: Use generic versions of collections (e.g., List<T>, Dictionary<TKey, TValue>) to avoid boxing and unboxing, improving performance and type safety.
  • Consider immutability: When appropriate, use immutable collections to improve thread safety and reduce the risk of unexpected modifications.

Interview Tip

Be prepared to explain the differences between these interfaces and provide examples of when you would use each one. Understanding the underlying principles of the Collections Framework is a common interview question for C# developers.

When to Use Them

  • IEnumerable: When you only need to iterate over a sequence of elements.
  • ICollection: When you need to know the size of a collection and copy it to an array, or potentially modify the collection.
  • IList: When you need indexed access to elements and the ability to add, insert, remove, and clear elements.
  • ISet: When you need a collection of unique elements and set operations.
  • IDictionary: When you need to store and retrieve data based on key-value pairs.

Memory Footprint

The memory footprint of each collection type depends on the underlying implementation and the data it stores. Generally, IEnumerable itself has a minimal footprint, as it only defines the interface for iteration. IList and IDictionary can have a larger footprint due to the additional data structures required for indexed access and key-value storage, respectively. ISet implementations also vary but are generally optimized for efficient membership testing and set operations.

Alternatives

Besides the standard implementations of these interfaces like List<T>, Dictionary<T,V>, and HashSet<T>, there are other more specialized collection types. For example, ConcurrentDictionary<T,V> for thread-safe key-value storage, ImmutableList<T> for immutable lists, and various collection types in third-party libraries such as those offered by Reactive Extensions (Rx) or more specialized data structure libraries. The choice depends on the specific needs of your application.

Pros

  • Standardization: Provides a consistent way to work with different collection types.
  • Code Reusability: Allows you to write generic code that can operate on any collection that implements the required interface.
  • Flexibility: Offers a wide range of collection types to choose from, each optimized for different scenarios.

Cons

  • Potential Overhead: Using interfaces can introduce some overhead compared to working directly with concrete classes.
  • Complexity: Choosing the right interface and implementation can be complex, especially for beginners.
  • Mutability: Many standard collection implementations are mutable, which can lead to unexpected behavior if not handled carefully.

FAQ

  • What is the difference between `ICollection` and `IEnumerable`?

    IEnumerable is the base interface for all collections and provides the ability to iterate over elements. ICollection extends IEnumerable and adds properties and methods for getting the size of the collection and copying it to an array.

  • When should I use `IList` instead of `ICollection`?

    Use IList when you need indexed access to elements, the ability to add, insert, remove, and clear elements, and the order of elements is important. Use ICollection when you only need to know the size of the collection and copy it to an array or enumerate the collection.

  • What is the primary purpose of `ISet`?

    The primary purpose of ISet is to represent a collection of unique elements. It is useful when you need to ensure that no duplicate elements are present in the collection.

  • How does `IDictionary` differ from `IList`?

    IDictionary stores elements as key-value pairs, while IList stores elements in a linear sequence with indexed access. You use keys to access values in an IDictionary, while you use indices to access elements in an IList.