CodeGym /Courses /C# SELF /Interfaces in the .NET Standard Library

Interfaces in the .NET Standard Library

C# SELF
Level 24, Lesson 3
Available

1. Classification of Key Interfaces

If you think interfaces are just a toy for architects and "clean code" fans, and you can get by without them in real projects, let me surprise you: any serious C# project dives headfirst into interfaces. Why? Because pretty much every part of the .NET standard library is built around interfaces! Without them, you can't work with collections, read files, or even filter collections with LINQ.

Interfaces are the foundation of polymorphism and extensibility in .NET, and they're what make all the "building blocks" of the framework fit together.

The .NET standard library is literally packed with interfaces. So you don't drown in this ocean, here's a classification (of course, it's not complete — you couldn't cover it all in a lifetime!):

Category Interfaces What are they for?
Collections
IEnumerable
,
IEnumerator
,
IList
,
ICollection
,
IDictionary
Iteration, modification, index access, working with key-value pairs
Working with resources
IDisposable
Releasing resources (files, connections, streams)
Comparison
IComparable
,
IComparer
,
IEquatable
Ordering, comparison, object uniqueness
Serialization
ISerializable
Turning objects into a byte stream (and back)
LINQ and queries
IQueryable
,
IQueryProvider
Supporting complex queries (like to a DB)
Async
IAsyncEnumerable
,
IAsyncDisposable
Async iteration and resource cleanup
Events and notifications
INotifyPropertyChanged
,
INotifyCollectionChanged
Reacting to property/collection changes
Date and time
IFormattable
Custom string formatting
Data structures
IStructuralComparable
,
IStructuralEquatable
Deep comparison of collections and tuples
Streams/input-output
IStream
,
IAsyncDisposable
,
IObserver
,
IObservable
Working with streams, push/pull notifications

Important! A lot of the interfaces above have type parameters: List<string>. Usually, they're used in collections and for collection interfaces Int[] → List<int>. We'll dig into them more in a couple levels, when we get to collections.

2. Collection Interfaces

IEnumerable and IEnumerator — your ticket to foreach

Pretty much every collection in .NET implements IEnumerable or even its generic version IEnumerable<T>. This is the interface that lets you use the magic foreach syntax:

List<int> numbers = new List<int> { 1, 2, 3 };
foreach (int n in numbers) // works because List<int> implements IEnumerable<int>
{
    Console.WriteLine(n);
}

Here's what this interface looks like in its minimal version:

public interface IEnumerable
{
    IEnumerator GetEnumerator();
}

IEnumerator is the interface for the actual "iterator" that walks through your collection:

public interface IEnumerator
{
    bool MoveNext();
    object Current { get; }
    void Reset();
}

In real tasks, you'll very often pass parameters and return values of type IEnumerable<T>. For example, a method that returns all even numbers from an array:

public IEnumerable<int> GetEvenNumbers(int[] array)
{
    foreach (var x in array)
        if (x % 2 == 0) yield return x;
}

Yep, the yield keyword does the magic — it implements the interface for you, but more on that later.

ICollection and IList — collections with index access and modification

If you need not just iteration, but also the ability to add/remove elements or even get them by index, you need more specialized interfaces:

  • ICollection<T> — adds Add, Remove methods, and the Count property.
  • IList<T> — expands the list of possible operations, including index access (this[int index]).
public void PrintFirstItem(IList<string> list)
{
    if (list.Count > 0)
        Console.WriteLine(list[0]);
}

List<T> implements IEnumerable<T>, ICollection<T>, and IList<T>. Super handy — you can use it as a simple enumerable list or use all the rich methods.

IDictionary<TKey, TValue> — key-value pairs

If you love working with dictionaries (and you will, a lot!), use the IDictionary<TKey, TValue> interface. It guarantees you can get a value by key and vice versa.

public void PrintAllPairs(IDictionary<string, int> ages)
{
    foreach (var pair in ages)
        Console.WriteLine($"{pair.Key}: {pair.Value}");
}

Here, ages can be anything, like Dictionary<string, int> or even SortedDictionary<string, int> — the main thing is they implement this interface!

3. The IDisposable Interface: Proper Resource Management

All input-output, file work, network connections, databases in .NET are tied to the IDisposable interface. This interface defines a super important contract: if an object has unmanaged resources, you gotta "clean it up" after use. Yep, this is the guy that made the using syntax possible:

using (StreamReader reader = new StreamReader("file.txt"))
{
    // Working with the file, and after using the file is guaranteed to close!
    string line = reader.ReadLine();
}

How's the interface itself set up? It's hilariously simple:

public interface IDisposable
{
    void Dispose();
}

But every "serious" library implements it! For more on good practices with this interface, check out the official docs.

4. Interfaces for Comparison and Uniqueness

IComparable<T> and IComparer<T>

If you want your collection of objects to be sortable, the objects need to be able to compare themselves to each other. That's what IComparable<T> is for:

public class Student : IComparable<Student>
{
    public string Name { get; set; }
    public int Score { get; set; }

    public int CompareTo(Student? other)
    {
        // Sort by descending score
        if (other == null) return 1;
        return other.Score.CompareTo(this.Score);
    }
}

// Now you can sort students:
var students = new List<Student> { ... };
students.Sort();

More in the Microsoft docs.

IComparer<T> lets you define comparison outside the class — for example, sometimes you want to sort students by name, sometimes by score:

public class NameComparer : IComparer<Student>
{
    public int Compare(Student? x, Student? y)
    {
        return string.Compare(x?.Name, y?.Name);
    }
}

IEquatable<T> — equality comparison

Want your object to work in a HashSet<T> (i.e. be "unique" for collections)? Implement IEquatable<T>:

public class Person : IEquatable<Person>
{
    public string Name { get; set; }
    public bool Equals(Person? other)
    {
        if (other == null) return false;
        return this.Name == other.Name;
    }
}

5. Interfaces for Events and Notifications

Ever wanted your app to "know" when an object's properties change? Like, you want the UI to update itself when the user changes their last name? That's exactly what the INotifyPropertyChanged interface is for. It's super popular in GUI apps (like WPF or Xamarin).

The interface signature is simple:

public interface INotifyPropertyChanged
{
    event PropertyChangedEventHandler? PropertyChanged;
}

Example implementation:

public class User : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    private string name;
    public string Name
    {
        get => name;
        set
        {
            if (name != value)
            {
                name = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
            }
        }
    }
}

Now any UI bindings to this object will auto-update when Name changes. If you're curious — check out the official docs.

6. Other Useful Interfaces

The IFormattable Interface: Flexible Formatting

When you want your object to print itself nicely and in different ways (like with different numbers of decimal places), implement IFormattable:

public class Temperature : IFormattable
{
    public double Celsius { get; }
    public Temperature(double celsius) => Celsius = celsius;

    public string ToString(string? format, IFormatProvider? formatProvider)
    {
        // Let's keep it simple, just show 'C' or 'F' depending on format
        if (format == "F")
            return $"{Celsius * 9 / 5 + 32} F";
        return $"{Celsius} C";
    }
}

Now you can call temp.ToString("F", null) or temp.ToString("C", null). That's why DateTime, double, decimal and other built-in types support flexible formatting.

Interfaces for Async

With async coming to C#, we needed new interfaces for async world. For example, if your object releases resources asynchronously — implement IAsyncDisposable:

public interface IAsyncDisposable
{
    ValueTask DisposeAsync();
}

And now instead of using you write await using!

Async enumerations (await foreach) use the IAsyncEnumerable<T> interface, which lets you iterate elements without blocking the main thread. It's used, for example, when reading big files in chunks or working with data streams from the internet. More on this at level 58 :P

Serialization Interfaces: ISerializable

If your task is to turn objects into a byte stream for saving/transmitting (like over the network), .NET offers the ISerializable interface. It's not used as much today, since there are easier ways (like with attributes and ready-made serializers), but it's worth mentioning. Here's an example signature:

public interface ISerializable
{
    void GetObjectData(SerializationInfo info, StreamingContext context);
}

7. Using Interfaces in Practice: A Real App

Let's say you're building a console app for tracking books in a library (hey, someone has to!). You want to be able to print book lists, sort them, filter, load and save data. Thanks to knowing .NET interfaces, you can:

  • Return different collection types via IEnumerable<Book>, so the user doesn't have to care if it's an array or a list.
  • Add sorting by different criteria: implement IComparable<Book> for sorting by title and a separate IComparer<Book> for author.
  • Ensure efficient resource cleanup when working with files via IDisposable.
  • Organize filtering books by genre or author using LINQ, which works with anything that implements IEnumerable<T>.
  • Scale the app — for example, swap file storage for a database connection if your classes work through interfaces (IBookStorage), not concrete implementations.

8. Typical Mistakes and Gotchas

One of the most common newbie mistakes is using concrete implementations ("List") instead of interfaces ("IEnumerable", "IList") when declaring variables and method arguments. REMEMBER: if you "program to interfaces", your code will be flexible and easy to extend.

Sometimes you gotta be careful choosing which interface level to use — for example, if you're writing a method that only needs to iterate, use IEnumerable<T>, not IList<T>, so you don't force unnecessary restrictions.

Another gotcha: if your class has a bunch of interfaces, and they have overlapping methods or properties, you'll need to use explicit interface implementation, or the compiler won't get you.

2
Task
C# SELF, level 24, lesson 3
Locked
IEnumerable Interface Check
IEnumerable Interface Check
2
Task
C# SELF, level 24, lesson 3
Locked
Sorting with the IComparable Interface
Sorting with the IComparable Interface
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION