.NET Academy

Interfaces

Define contracts that unrelated classes can fulfil, and program against capabilities instead of concrete types.

12 min read

You will learn

  • Declare and implement an interface
  • Implement several interfaces in one class
  • Use interfaces as parameter and variable types
  • Choose between an interface and an abstract class

Contracts, not families

Inheritance models families: a Manager is an Employee. But many things share a capability without being related at all. A Document, a Photo and an Invoice can all be printed; a Bird, a Plane and a Drone can all fly.

An interface describes a capability as a contract: "any type that implements me provides these members".

interface IPrintable
{
    string Title { get; }
    void Print();
}

By convention, interface names start with I. Members have no access modifier — they're public by definition — and usually no body.

Implementing an interface

class Invoice : IPrintable
{
    public string Title => $"Invoice #{Number}";
    public int Number { get; init; }
 
    public void Print() => Console.WriteLine($"Printing {Title}...");
}
 
class Photo : IPrintable
{
    public string Title { get; init; } = "Untitled";
    public void Print() => Console.WriteLine($"Printing photo '{Title}' in color");
}

If a class forgets a member, the compiler reports an error — the contract is enforced.

Programming against the interface

The real power: code that uses the capability doesn't care what the concrete type is.

void PrintAll(IEnumerable<IPrintable> items)
{
    foreach (var item in items)
        item.Print();
}
 
PrintAll(new IPrintable[] { new Invoice { Number = 42 }, new Photo { Title = "Beach" } });

PrintAll will work with types that don't even exist yet. This idea — depend on abstractions, not concrete classes — is everywhere in professional .NET code: dependency injection, unit testing with fake implementations, plugins.

interface IEmailSender
{
    void Send(string to, string subject, string body);
}
 
class OrderService
{
    private readonly IEmailSender email;
    public OrderService(IEmailSender email) => this.email = email;   // real sender or a test fake
}

Several interfaces at once

A class has only one base class, but can implement any number of interfaces:

class Duck : Animal, IFlyable, ISwimmable
{
    public void Fly() { ... }
    public void Swim() { ... }
}

Interfaces you'll use constantly

InterfaceCapability
IEnumerable<T>Can be looped over with foreach — arrays, lists, LINQ results
IComparable<T>Can be compared and sorted
IEquatable<T>Can be compared for equality
IDisposableHolds resources that must be released (Track 5)

For example, implementing IComparable<T> lets Array.Sort and List.Sort order your objects:

class Player : IComparable<Player>
{
    public string Name { get; init; } = "";
    public int Score { get; init; }
 
    // negative: this comes first; positive: other comes first; 0: equal
    public int CompareTo(Player? other) => other is null ? 1 : other.Score.CompareTo(Score);
}

Interface or abstract class?

InterfaceAbstract class
ExpressesA capability ("can do")A family ("is a")
A type can haveManyOne
Fields / stateNoYes
ConstructorsNoYes
Shared codeOnly default methods (rarely used)Yes

Tip

Start with an interface. Reach for an abstract class when several related types genuinely share state and implementation.

Try it

Program.cs
Loading editor…

C# runtime not loaded

Key takeaways

  • An interface is a contract of members that implementing types must provide.
  • A class can implement many interfaces but inherit from only one class.
  • Use interface types for parameters and fields to keep code flexible and testable.
  • Interfaces describe capabilities; abstract classes describe families with shared state.

Exercise

Implement IDiscount twice. PercentageDiscount(10) takes 10% off and describes itself as "10% off". FixedDiscount(15) subtracts 15 (but never goes below 0) and describes itself as "15 off". Then ApplyAll applies a list of discounts one after another.

Program.cs
Loading editor…

C# runtime not loaded

Hint 1

Store the constructor argument in a property or field so Apply can use it.

Hint 2

Percentage: price - price * percent / 100. Fixed: Math.Max(0, price - amount).

Hint 3

ApplyAll: loop over discounts and update price = d.Apply(price) each time. It works for any IDiscount — it never needs to know which kind.

Show solution
IDiscount[] discounts = { new PercentageDiscount(10), new FixedDiscount(5) };
Console.WriteLine(ApplyAll(100, discounts));

decimal ApplyAll(decimal price, IDiscount[] discounts)
{
    foreach (var discount in discounts)
    {
        price = discount.Apply(price);
    }
    return price;
}

interface IDiscount
{
    string Description { get; }
    decimal Apply(decimal price);
}

class PercentageDiscount : IDiscount
{
    private readonly decimal percent;
    public PercentageDiscount(decimal percent) => this.percent = percent;

    public string Description => $"{percent}% off";
    public decimal Apply(decimal price) => price - price * percent / 100;
}

class FixedDiscount : IDiscount
{
    private readonly decimal amount;
    public FixedDiscount(decimal amount) => this.amount = amount;

    public string Description => $"{amount} off";
    public decimal Apply(decimal price) => Math.Max(0, price - amount);
}

Quiz

4 questions · 3 correct to pass
  1. 1.What does an interface contain?

  2. 2.How many interfaces can a class implement?

  3. 3.What's the benefit of a method parameter typed as `IDiscount` rather than `PercentageDiscount`?

  4. 4.When is an abstract class a better choice than an interface?

To complete this lesson

  • ○ Solve the exercise
  • ○ Pass the quiz (3/4 correct)