Skip to main content

On This Page

The Essential C# Interfaces Every Developer Should Know

4 min read
Share

These articles are AI-generated summaries. Please check the original sources for full details.

The Essential C# Interfaces Every Developer Should Know

This article explores five critical C# interfaces—IEnumerable<T>, IEquatable<T>, IDisposable, IComparable<T>, and IEqualityComparer<T>—explaining their roles, implementation patterns, and real-world use cases. These interfaces are foundational for managing collections, equality, resource cleanup, sorting, and custom comparison logic in .NET applications.


IEnumerable and IEnumerator

Purpose: Enable enumeration of collections (e.g., use in foreach loops).
Key Concepts:

  • IEnumerable<T> defines a contract for enumerable collections.
  • IEnumerator<T> provides the actual iteration logic.
  • Deferred Execution: LINQ operations like Where or Select do not execute immediately but build a pipeline that executes only when enumerated (e.g., via foreach, ToList()).

Example:

public class FileLineEnumerable : IEnumerable<string>
{
    private readonly string _path;
    public FileLineEnumerable(string path) => _path = path;
    public IEnumerator<string> GetEnumerator()
    {
        using var reader = new StreamReader(_path);
        string? line;
        while ((line = reader.ReadLine()) != null)
            yield return line;
    }
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

Usage:

var errorLogs = new FileLineEnumerable("logs.txt")
    .Where(line => line.Contains("ERROR"));
foreach (var log in errorLogs)
    Console.WriteLine(log);

Best Practices:

  • Prefer built-in IEnumerable<T> types (e.g., List<T>, Array) unless custom logic is needed.
  • Avoid manually implementing IEnumerable<T> unless handling unmanaged resources or streaming data.

IEquatable

Purpose: Define type-safe equality checks for objects.
Key Concepts:

  • Overrides System.Object.Equals(object?) for better performance and type safety.
  • Must also override GetHashCode() to ensure consistency with hash-based collections (e.g., HashSet<T>).

Example:

public class User : IEquatable<User>
{
    public string Email { get; set; } = string.Empty;
    public bool Equals(User? other)
    {
        if (other is null) return false;
        return string.Equals(Email, other.Email, StringComparison.OrdinalIgnoreCase);
    }
    public override bool Equals(object? obj) => Equals(obj as User);
    public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Email);
}

Impact:

  • Ensures List<T>.Contains() or HashSet<T> correctly identifies equality based on Email.
  • Prevents reference equality checks (which compare memory addresses) by default.

Pitfalls:

  • Failing to override Equals(object?) and GetHashCode() can lead to inconsistent behavior in collections.

IDisposable

Purpose: Manage unmanaged resources (e.g., file handles, database connections).
Key Concepts:

  • Managed Resources: Automatically handled by the garbage collector (e.g., User objects).
  • Unmanaged Resources: Require explicit cleanup via Dispose().

Example:

public class FileLogger : IDisposable
{
    private readonly StreamWriter _writer;
    private bool _disposed;
    public FileLogger(string filePath)
    {
        _writer = new StreamWriter(filePath, append: true);
    }
    public void Log(string message)
    {
        if (_disposed) throw new ObjectDisposedException(nameof(FileLogger));
        _writer.WriteLine($"[{DateTime.Now}] {message}");
    }
    public void Dispose()
    {
        if (_disposed) return;
        _writer.Dispose();
        _disposed = true;
    }
}

Usage:

// Using 'using' block (preferred)
using (var logger = new FileLogger("logs.txt"))
{
    logger.Log("Application started.");
}

// Manual disposal
var logger = new FileLogger("logs.txt");
logger.Log("Processing request...");
logger.Dispose();

Best Practices:

  • Always use using blocks for deterministic cleanup.
  • Ensure Dispose() is idempotent (safe to call multiple times).

IComparable

Purpose: Define sorting logic for objects.
Key Concepts:

  • CompareTo(T?) returns:
    • Negative: Current instance comes before other.
    • Zero: Equal in sort order.
    • Positive: Current instance comes after other.

Example:

public class Task : IComparable<Task>
{
    public int Priority { get; set; }
    public DateTime DueDate { get; set; }
    public int CompareTo(Task? other)
    {
        if (other is null) return 1;
        int priorityComparison = Priority.CompareTo(other.Priority);
        if (priorityComparison != 0) return priorityComparison;
        return DueDate.CompareTo(other.DueDate);
    }
}

Usage:

var tasks = new List<Task> { /* ... */ };
tasks.Sort(); // Sorts by Priority, then DueDate

Best Practices:

  • Override Equals(object?) and GetHashCode() if CompareTo defines equality.

IEqualityComparer

Purpose: Provide custom equality logic for objects or types you cannot modify.
Key Concepts:

  • Used with collections like HashSet<T> or LINQ operations (GroupBy, Distinct).
  • Allows flexible comparison strategies (e.g., by SKU, name, or category).

Example:

public class ProductSKUComparer : IEqualityComparer<Product>
{
    public bool Equals(Product? x, Product? y)
    {
        if (ReferenceEquals(x, y)) return true;
        if (x is null || y is null) return false;
        return string.Equals(x.SKU, y.SKU, StringComparison.OrdinalIgnoreCase);
    }
    public int GetHashCode(Product obj)
    {
        return StringComparer.OrdinalIgnoreCase.GetHashCode(obj.SKU);
    }
}

Usage with LINQ:

var duplicateSKUs = inventory
    .GroupBy(p => p, new ProductSKUComparer())
    .Where(g => g.Count() > 1)
    .Select(g => g.Key.SKU);

Best Practices:

  • Use IEqualityComparer<T> for comparison logic external to the object itself.
  • Ensure GetHashCode() aligns with Equals() to avoid hash collisions.

Working Example: Custom Equality with IEqualityComparer

var inventory = new List<Product>
{
    new() { SKU = "LAPTOP-001", Name = "Gaming Laptop" },
    new() { SKU = "LAPTOP-001", Name = "Gaming Laptop Pro" }
};
var uniqueProducts = inventory.Distinct(new ProductSKUComparer()).ToList();

Recommendations

  • IEnumerable: Use deferred execution with LINQ for performance.
  • IEquatable: Always override Equals and GetHashCode for consistency.
  • IDisposable: Prefer using blocks for resource cleanup.
  • IComparable: Implement for natural sorting; avoid overriding Equals unless needed.
  • IEqualityComparer: Use for flexible, external comparison logic (e.g., deduplication).

URL: https://dev.to/rasheedmozaffar/the-essential-interfaces-every-c-developer-should-know-2ma4

Continue reading

Next article

Creating AI-Ready APIs: Best Practices for Enhancing AI Performance and Reliability

Related Content