Interfaces

Interfaces - define behavior for multiple types | Microsoft Docs
Safely update interfaces using default interface methods in C# | Microsoft Docs
Interfaces require classes or structs to implement certain members. They define a contract. They define “can-do” relationships. Interfaces can be declared in namespace or class scope. Interfaces cannot contain instance state.

KindMay containCannot
instancemethods, properties, events, indexersfields, constructors, finalizers
staticmethods, fields, properties, events, indexers
static abstract
static virtualmethods, properties, events, indexersfields

Creating

interface IScored 
{
    float Score { get; set; }
    float MaximumScore { get; set; }
}

Implementing

Classes and structs implement interfaces.

  • The implementing type must implement all members of the interface that do not have a default implementation.
  • The implemented members must be public and non-static.

Example 1

public class Person : object, IComparable<Person> 
{ 
    // Inherit from another class and an interface. Can also inherit multiple interfaces.
    // … more implementation … 

    public int CompareTo(Person? other) 
    {
        if (Name is null) { return 0; }
        // Call the CompareTo method of the Name field. The Name field is a string, so this uses string's implementation of CompareTo
        return Name.CompareTo(other?.Name); 
    }
}

Example 2

// Class Car implements interface IEquatable<T>
public class Car : IEquatable<Car> 
{ 
    public string Make { get; set; }
    public string Model { get; set; }
    public string Year { get; set; }

    // Implementation of the IEquatable<T> interface by implementing it's only method:
    public bool Equals(Car car) 
    {
        return (this.Make, this.Model, this.Year) == (car.Make, car.Model, car.Year);
    }
}

Interface Members

Properties

In interfaces, declaring the get/set accessors without a body does not declare an auto-implemented property:

public Interface INamed 
{
    public string Name { get; set; } // This property does not have a default implementation and must be implemented by a derived type.
}

Although interface properties may have default implementations, this is rare because interfaces cannot define instance data fields.

Static Virtual/Abstract Members

Interfaces may define static virtual or static abstract members to declare that an implementing type must provide the declared members.

  • Those interfaces can then be used as constraints to create generic types that use operators or other static methods.

static virtual methods are almost exclusively declared in generic interfaces.

Interface fields cannot be static virtual/abstract.

Inheritance

  • A type that inherits an interface must implement all of its members that do not have a default implementation.
  • Types can inherit multiple interfaces (but only one base class).
  • Interfaces may inherit from base interfaces.
  • Interfaces are the only type a struct can inherit.

Default Implementations

For interface methods that define default implementations, any class inheriting that interface also inherits that implementation.

  • The class instance must be cast to the interface type to access the default implementation of the interface method.

Versioning

C# allows an interface to add new members after release so long as they have a default implementation. Default implementations are in effect whenever another implementation is not provided:

public interface IPlayable

{
    void Play();
    void Pause();

    // This member was added after release, so it requires a default implementation.
    void Stop() 
    {     
        
    }
}

Passing Interfaces like Types

Given this utility class:

// Of two assignments, return the one that has the higher score:
public class ScoreUtility 
{ 
    public static IScored BestOfTwo(IScored Assignment1, IScored Assignment2) 
    {
        // Assignment1 and Assignment2 both have the members of the IScored interface:
        var score1 = Assignment1.Score / Assignment1.MaximumScore;
        var score2 = Assignment2.Score / Assignment2.MaximumScore;

        if (score1 >= score2)
            return Assignment1;

        else
            return Assignment2;
    }
}

Don’t Pass IEnumerable<T>

When a method that takes IEnumerable<T> as an input parameter performs iteration, it has to allocate an object on the heap. This is because the IEnumerator<T> GetEnumerator() method returns a reference type.

Instead, pass a concrete type, like List<T>.

Common Interfaces

InterfaceMethod(s)Description
IComparableCompareTo(other)A comparison method that a type implements to sort its instances.
IComparerCompare(first, second)A comparison method that a secondary type implements to sort instances of a primary type.
IDisposableDisposeA method to release unmanaged resources more efficiently than waiting for a finalizer.
IFormattableToString(format, culture)A culture-aware method to format the value of an object into a string.
IFormatterSerialize(stream, object) or Deserialize(stream)Methods to convert an object to and from a stream of bytes.
IFormatProviderGetFormat(type)A method to format inputs based on language and region.
IEnumerableGetEnumerator
ICollection

Using IComparable vs. IComparer

If you’re working with a type for which you do not have the source code, you cannot implement IComparable.
Instead, create a separate type that implements IComparer:

public class PersonComparer : IComparer<Person> 
{
    public int Compare(Person? x, Person? y) 
    {
        if (x is null || y is null) { return 0; }
        int result = x.Name.Length.CompareTo(y.Name.Length); // Compare the Name lengths…

        if (result == 0) 
        { // If they are equal…
            return x.Name.CompareTo(y.Name); // …then compare by the Names…
        }
        else 
        { 
            return result;  // …otherwise, compare by the Lengths.
        }
    }
}