CodeGym /Courses /C# SELF /Collection Contract: IColl...

Collection Contract: ICollection<T>

C# SELF
Level 28 , Lesson 2
Available

1. Introduction

With IEnumerable<T> you could only wander through the existing contents of a collection, but the ICollection<T> interface is your ticket to the world of collection management: add, remove, check the number of elements, copy them to an array, and even keep an eye on changes (well, almost like real life when you try to keep track of what's in your shopping cart).

ICollection<T> is implemented by all mutable .NET collections, like List<T>, HashSet<T>, Dictionary<TKey, TValue>.ValueCollection, and even the less popular ones like Queue<T>, Stack<T> (yep, even queues and stacks!). It's the base contract for all collection classes that let you modify their contents.

The Collection Interface Family

ICollection<T> extends IEnumerable<T> and is the base interface for most collections.

2. What's in the ICollection<T> interface?

Unlike IEnumerable<T>, which is only about enumeration (foreach), ICollection<T> is like the Swiss Army knife of interfaces: there's a method for every job!

Here's the main stuff it has:


public interface ICollection<T> : IEnumerable<T>
{
    int Count { get; }
    bool IsReadOnly { get; }
    void Add(T item);
    void Clear();
    bool Contains(T item);
    void CopyTo(T[] array, int arrayIndex);
    bool Remove(T item);
}

List of methods and properties:

  • Count — number of elements in the collection. Like your favorite count function.
  • IsReadOnly — can you change the collection? If true, the collection is read-only (like some wrappers).
  • Add(T item) — adds an element to the collection. One of your main tools!
  • Clear() — removes all elements. Total wipeout.
  • Contains(T item) — quick check: does the collection have this element?
  • CopyTo(T[] array, int arrayIndex) — copies elements to a regular array, starting from a certain index. Handy for passing data to external APIs or "legacy" methods.
  • Remove(T item) — removes an element (if it's there).

3. Real-life Programmer Tasks

Why does it matter to know how this interface works? In practice, you often write generic code that works with any collection. For example, a function might take any list, queue, or set — and it doesn't matter what specific collection type was passed in. As long as the collection implements ICollection<T>, you can add, remove elements, check the size, and even copy to an array. This is the foundation for flexible libraries and generic algorithms.

Real-life example: the ICollection<T> interface often pops up in method parameters and API properties that return a collection you can modify. For example, in popular ORMs (Entity Framework, Dapper), properties of type ICollection<T> are used to describe collections of related objects.

Usage Examples

Let's try writing a generic method that takes any collection and adds an element to it. The only requirement — it has to implement ICollection<T>.


// Generic method to add an element
void AppendItem<T>(ICollection<T> collection, T item)
{
    collection.Add(item);
}

Now you can use this method with List<T>, HashSet<T>, or even your own interface implementation! Here's what it looks like in action:


var numbers = new List<int> { 1, 2, 3 };
AppendItem(numbers, 42);

var uniqueNames = new HashSet<string> { "Alice", "Bob" };
AppendItem(uniqueNames, "Charlie");

It's kinda funny — we don't care if it's a list or a set, the main thing is it's an ICollection<T>. Don't be surprised, that's how a lot of generic methods inside .NET work!

4. Connection with Other Collections

ICollection<T> isn't just a theoretically nice contract, it's the real foundation for pretty much every collection you'll run into in .NET.

To see how universal it is in practice, try this code:


void ShowCollectionInfo<T>(ICollection<T> collection)
{
    Console.WriteLine($"Number of elements: {collection.Count}");
    Console.WriteLine($"Can modify: {!collection.IsReadOnly}");

    Console.WriteLine("Elements:");
    foreach (var item in collection)
    {
        Console.WriteLine(item);
    }
}

// For List<T>
var list = new List<string> { "apple", "orange" };
ShowCollectionInfo(list);

// For HashSet<T>
var set = new HashSet<string> { "apple", "orange" };
ShowCollectionInfo(set);

The result will be correct for any collection type that implements this interface. Try it with a queue, stack, or even a wrapped array!

5. Read-only Collections

The ICollection<T> interface has the IsReadOnly property, which tells you if you can modify the collection. Don't confuse it with the usual property for arrays! If you run into a collection where IsReadOnly == true, trying to add or remove an element will throw an exception.

Example:


// Let's create an array and wrap it in ReadOnlyCollection
var array = new int[] { 1, 2, 3 };
ICollection<int> readOnly = Array.AsReadOnly(array);

// Try to add an element
try
{
    readOnly.Add(4); // NotSupportedException will be thrown
}
catch (NotSupportedException)
{
    Console.WriteLine("Can't add elements: collection is read-only!");
}

You'll see this, for example, when you get a collection from an external source and you can't change it — but you can read elements and get their count.

6. Comparison Table of Popular Collections' Methods through the ICollection<T> Lens

Collection Add() Remove() Contains() Clear() CopyTo() IsReadOnly
List<T>
Yes Yes Yes Yes Yes No
HashSet<T>
Yes* Yes Yes Yes Yes No
Queue<T> / Stack<T>
No/No No/No No/No Yes/Yes Yes/Yes No
ReadOnlyCollection<T>
No No Yes No Yes Yes
Dictionary<TKey, TValue>.ValueCollection
No No Yes No Yes Yes/No*

For HashSet<T> the Add method returns true if the element was added, otherwise false.

7. CopyTo — Why You Might Need It

The CopyTo method lets you quickly move the contents of a collection into a regular array. It's handy if, for example, you need to return data to an old-school API that only takes arrays, or do some batch processing with array tools.


var contactsArray = new Contact[book.Count];
((ICollection<Contact>)book).CopyTo(contactsArray, 0);
// Now you can use contactsArray the old-fashioned way

8. Example

In our student console app, let's say we're starting to build an address book. Suppose we have a Contact class, and the address book can be based on any collection, as long as it supports adding, removing, and enumeration.


public class Contact
{
    public string Name { get; set; }
    public string Phone { get; set; }
}

public class AddressBook
{
    private readonly ICollection<Contact> _contacts;

    public AddressBook(ICollection<Contact> contacts)
    {
        _contacts = contacts;
    }

    public void AddContact(Contact contact)
    {
        _contacts.Add(contact);
    }

    public bool RemoveContact(Contact contact)
    {
        return _contacts.Remove(contact);
    }

    public int Count => _contacts.Count;

    public void PrintAll()
    {
        foreach (var contact in _contacts)
        {
            Console.WriteLine($"{contact.Name} — {contact.Phone}");
        }
    }
}

Now our address book can work with List<Contact>, HashSet<Contact>, or even — in the future — with our own collection based on a database or files! Example usage:


var book = new AddressBook(new List<Contact>());
book.AddContact(new Contact { Name = "Ivan", Phone = "+7-123-456" });
book.AddContact(new Contact { Name = "Maria", Phone = "+7-987-654" });

Console.WriteLine($"Total contacts: {book.Count}");
book.PrintAll();

If you want to get rid of duplicates, just pass a set to AddressBook instead of a list:


var book = new AddressBook(new HashSet<Contact>());

(just make sure Contact implements comparison properly, but that's a topic for another lecture!)

9. Method Quirks and Common Mistakes

Heads up: not all collections that implement ICollection<T> behave the same! For example, if you try to change a collection when IsReadOnly == true, you'll get an exception. And if you're working with HashSet<T>, the Add method returns true only if the element was actually added (it wasn't there before). Also, different collections might implement methods with different speeds — but the interface doesn't care about that.

Another common thing: if a collection is being accessed by multiple threads, changing the collection in one thread and enumerating in another can throw exceptions. For multithreaded scenarios, you need special collections (ConcurrentBag<T>, ConcurrentQueue<T>, etc. — more on that later).

Another gotcha: if you copy a collection using CopyTo, any changes to the array and the collection after that are independent. The array is a separate chunk of memory.

2
Task
C# SELF, level 28, lesson 2
Locked
Creating and Modifying a Collection Using ICollection<T>
Creating and Modifying a Collection Using ICollection<T>
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION