Important interfaces for types

IComparable<T>

Defines a generalized comparison method that a value type or class implements to create a type-specific comparison method for ordering or sorting its instances.

interface IComparable<T>
{
	int CompareTo (T? other);
}

The CompareTo returns a value that indicates the relative order of the objects being compared. The return value has these meanings:

Value Meaning
Less than zero This instance precedes other in the sort order.
Zero This instance occurs in the same position in the sort order as other.
Greater than zero This instance follows other in the sort order.

IComparer<T>

Defines a method that a type implements to compare two objects.

This interface is used with the List<T>.Sort and List<T>.BinarySearch methods. It provides a way to customize the sort order of a collection. Classes that implement this interface include the SortedDictionary<TKey,TValue> and SortedList<TKey,TValue> generic classes.

The default implementation of this interface is the Comparer<T> class. The StringComparer class implements this interface for type String.

interface IComparer<T>
{
	int Compare (T? x, T? y);
}

Return a signed integer that indicates the relative values of x and y, as shown in the following table:

Value Meaning
Less than zero x is less than y.
Zero x equals y.
Greater than zero x is greater than y.

IEquatable<T>

Defines a generalized method that a value type or class implements to create a type-specific method for determining equality of instances.

interface IEquatable<T>
{
	bool Equals (T? other);
}

Note: If a type implements the IEquatable<T>, then the type must override the Equals(object? other) and GetHashCode() methods provided also.

EqualityComparer<T>

Defines methods to support the comparison of objects for equality. This interface allows the implementation of customized equality comparison for collections. That is, you can create your own definition of equality for type T, and specify that this definition be used with a collection type that accepts the IEqualityComparer<T> generic interface.

interface IEqualityComparer<T>
{
	bool Equals (T? x, T? y);
	int GetHashCode (T obj);
}

IDisposable & IAsyncDisposable

The IDisposable interface is used to release unmanaged resources like file handles, database connections, network connections, or any resource that is not managed by the .NET runtime.

The IDisposable interface is typically implemented when a class holds onto resources that need to be explicitly released or closed to avoid potential resource leaks and do a proper clean-up. By implementing IDisposable, you can provide a mechanism for users of your class to explicitly release those resources when they're done with them, rather than relying on the garbage collector to eventually clean them up.

The IAsyncDisposable interface is similar to IDisposable, but it is specifically designed for asynchronous resource clean-up scenarios.

If you implement the IAsyncDisposable interface but not the IDisposable interface, your app can potentially leak resources. If a class implements IAsyncDisposable, but not IDisposable, and a consumer only calls Dispose(), your implementation would never call DisposeAsync(). This would result in a resource leak.

Implementation example:

public class AsyncDisposable : IAsyncDisposable, IDisposable
{
    //a flag to indicate whether the object has been disposed to prevent multiple dispose calls
    private bool _disposed;

    //A disposable field
    private readonly MemoryStream _field;

    public AsyncDisposable()
    {
        _field = new MemoryStream();
    }

    //IDosposable implementation. Do not change this code. Put cleanup code in the Dispose(bool disposing) method
    public void Dispose()
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }

    //IAsyncDisposable implementation. Do not change this code. Put cleanup code in the DisposeAsyncCore method
    public async ValueTask DisposeAsync()
    {
        //Call the overridable DisposeAsyncCore that does the actual work
        await DisposeAsyncCore().ConfigureAwait(false);

        //no need to call finalizer, because the resources have been freed by the DisposeAsyncCore method
        GC.SuppressFinalize(this);
    }

    // Async version of Dispose method
    protected virtual async ValueTask DisposeAsyncCore()
    {
        if (!_disposed)
        {
            await _field.DisposeAsync().ConfigureAwait(false);
        }
    }

    // Sync version of Dispose method
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                //Dispose managed state (managed objects)
                _field.Dispose();
            }

            // TODO: free unmanaged resources (unmanaged objects) and override finalizer
            // TODO: set large fields to null
            _disposed = true;
        }
    }

    //TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
    // ~AsyncDisposable()
    // {
    //     // Do not change this code. Put clean-up code in Dispose(bool disposing) method
    //     Dispose(disposing: false);
    // }
}