86. Collections#

So far, we’ve been storing multiple data items using arrays. However, arrays are usually not the best option. Their size is fixed at creation, and they offer limited built-in functionality. Fortunately, C# provides richer data structures for storing and managing collections of items, which take advantage of the type-safe flexibility afforded by generics. In this chapter, we’ll explore the concept of generic collections and focus on one of the foundational interfaces for these collections, ICollection<T>.

Key point

Generic collections allow you to work with groups of objects in a type-safe manner, offering a wide range of operations not found in arrays. The ICollection<T> interface serves as the cornerstone for mutable collections like lists and dictionaries in C#.

A generic collection is a data structure that allows you to store and manage multiple items of a specified type. Unlike arrays, these collections come with a set of built-in methods for adding, removing, and querying elements, among other things. Since these collections are generic, you get the benefit of type safety, which ensures that you can’t accidentally insert an element of the wrong type.

../_images/cover-collections.jpg

Fig. 86.1 In a library, all books get a place on the shelf. In programming, generic collections allow you to organize your objects while maintaining their types.#

The ICollection<T> Interface#

The ICollection<T> interface is a part of the System.Collections.Generic namespace and serves as a foundational interface for all generic collections that are mutable (i.e., can be modified after creation). This interface provides a standard set of methods and properties that any implementing collection must define.

The generic type parameter T defines the type of items that will be stored in a given collection. For example, ICollection<Cat> defines a collection where each item in the collection must be a Cat.

Important

The ICollection interface provides the most basic methods for working with a collection of objects that can be added to, removed from, or searched.

Here are some of the main methods and properties that ICollection<T> provides:

  • Add(T item): Adds an item to the collection.

  • Clear(): Removes all items from the collection.

  • Contains(T item): Determines whether the collection contains a specific item.

  • Remove(T item): Removes the first occurrence of a specific item from the collection.

  • Count: Gets the number of elements contained in the collection.

See also

To learn more about what you can do with ICollection<T>, you should consult the official Microsoft documentation: ICollection<T> Interface. Learning to read documentation is a key skill in programming!

Note

You can of course define your own specializations of ICollection<T> as long as you make sure to implement all the methods of the interface.

Examples#

In the example below we’re using the generic class List<T> to add some numbers and then perform a few operations on the list.

var numbers = new List<int>();

// Add elements.
numbers.Add(10);
numbers.Add(20);
numbers.Add(30);

// Print the number of items in the list.
Console.WriteLine(numbers.Count);

// Print whether or not the number 2 is in the list.
Console.WriteLine(numbers.Contains(20));

// Remove the number 20 from the list.
numbers.Remove(20);

// Print the number of items in the list again.
Console.WriteLine(numbers.Count);
3
True
2

Next, let’s look at an example where we’re using the generic class Dictionary<TKey, TValue> to associate user ids with nicknames.

The type parameter TKey is the data type that will be used for identifiers and the parameter TValue is the data type of the values that the identifiers identify.

var nicknames = new Dictionary<int, string>();

// Add some key-value pairs.
nicknames.Add(142, "Rafael");
nicknames.Add(104, "Michelangeo");

// Check if the dictionary has a nickname associated with the key 142.
Console.WriteLine(nicknames.ContainsKey(142));

// Remove the key-value pair with the key 142.
nicknames.Remove(142);

// Check if the dictionary has a nickname associated with the key 142 again.
Console.WriteLine(nicknames.ContainsKey(142));
True
False

Let’s look at another use-case for dictionaries: structured data with arbitrary keys. Imagine for example that we’re building a password manager where the user can add arbitrary information about a particular account that they have.

var entry = new Dictionary<string, string>();

entry.Add("Username", "user123");
entry.Add("Password", "XmTHRzVCMgqaEUyq");
entry.Add("Recovery code", "wUcFJjQnRAUNWRmBtEBJ");
entry.Add("API public key", "tXNhSZc5GZEETn7M7QbqfP8w82YNUYaFJ6c7pBe8");
entry.Add("API private key", "zZTMDW46njDeWT5PqXNBnjNNpMy759QMVfn64UDC");

As demonstrated, generic collections offer significant advantages over arrays in flexibility and functionality.

See also

As we’ll learn when we get to the dependency inversion principle we also improve maintainability by coupling to generalizations like ICollection<T> instead than arrays. But more on that later.

Tip

Both List<T> and Dictionary<TKey, TValue> are classes that implement ICollection<T>, so they provide all the methods and properties we discussed earlier.

Summary#

Generic collections offer a more flexible and feature-rich way to work with groups of items compared to arrays. The ICollection<T> interface forms the foundation of many useful collection types like List<T> and Dictionary<TKey, TValue>, providing basic methods for adding, removing, and querying elements.

Tip

Opt to use generic collections over arrays unless you have a very compelling reason to do otherwise.

Understanding how to work with collections, particularly lists and dictionaries, is crucial due to their wide usage in .NET programming.

In a later chapter we’ll explore the even more abstract concept of enumerables but we’ve got some ground to cover before we can get to that point. In the next chapter we’ll look at how to make collection initialization even simpler.