Skip to content
Interfaces
step 1/7

Reading — step 1 of 7

Learn

~3 min readInheritance, Interfaces, and Exceptions

Interfaces define contracts — what a type can do, without saying how. C# 8+ added DEFAULT INTERFACE METHODS, but the core idea is unchanged: any class can implement multiple interfaces; this is C#'s answer to multiple inheritance.

Basic interface

public interface IGreeter
{
    string Name { get; }
    string Greet();
}

public class Person : IGreeter
{
    public string Name { get; }
    public Person(string name) { Name = name; }
    public string Greet() => $"Hi, I'm {Name}";
}

Classes implement an interface by listing it after :. The compiler checks that every member is provided.

Convention: interface names start with I (IEnumerable, IDisposable, IComparable).

Multiple interfaces

public class Logger : IGreeter, IDisposable
{
    public string Name { get; } = "Logger";
    public string Greet() => "Hello from logger";
    public void Dispose() { /* cleanup */ }
}

A class can implement any number of interfaces. IDisposable and IEnumerable are common — many BCL types implement them.

Properties on interfaces

public interface ICountable
{
    int Count { get; }              // read-only on the interface
}

public class Container : ICountable
{
    public int Count { get; private set; }    // implementer can have a setter
}

The interface declares a get-only property. The implementer can add a setter; the interface doesn't restrict it.

Default interface methods (C# 8+)

public interface ILogger
{
    void Log(string message);
    
    void LogInfo(string msg) => Log($"INFO: {msg}");
    void LogError(string msg) => Log($"ERROR: {msg}");
}

The LogInfo and LogError methods have default bodies. Implementers only need to provide Log.

Default methods came late to C# (C# 8 / 2019). Different from Java 8 (2014). Used cautiously — they're great for evolving interfaces without breaking existing implementers, but power-tooling-only.

Explicit interface implementation

public class Session : IDisposable
{
    void IDisposable.Dispose() { /* ... */ }    // ONLY accessible via IDisposable reference
    
}

var session = new Session();
// session.Dispose();                          // ERROR — not on Session directly
((IDisposable)session).Dispose();             // OK

Use when:

  • Two interfaces have conflicting member names
  • You want the interface method hidden from the class's public surface
  • Disambiguating implementations

Common BCL interfaces

  • IEnumerable<T> — for foreach
  • IDisposable — for using
  • IComparable<T> — for Sort
  • IEquatable<T> — for ==
  • INotifyPropertyChanged — for data binding
  • IList<T>, ICollection<T>, IDictionary<TKey, TValue> — collection protocols

Most C# code consumes (rather than defines) interfaces. When defining your own, follow the same I prefix convention.

When to use interface vs abstract class

  • Interface — contract; multiple inheritance; no state (except in C# 8+ default methods, no fields ever)
  • Abstract class — partial implementation with state, single inheritance, lifecycle hooks

Common pattern: an interface defines the contract, an abstract base provides shared default behavior:

public interface IShape { double Area(); }
public abstract class ShapeBase : IShape
{
    public abstract double Area();
    public string Describe() => $"Shape with area {Area()}";
}

Common mistakes

  • Forgetting interface members — the class won't compile until everything is provided.
  • Confusing implicit and explicit interface implementation — explicit hides the member from the class type.
  • Using interfaces with state expectations — interfaces traditionally have no fields. Default methods can call other interface members but can't store data.
  • Putting too much in an interface — Interface Segregation Principle: prefer many small interfaces over one bloated one.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…