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
| Interface | Capability |
|---|---|
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 |
IDisposable | Holds 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?
| Interface | Abstract class | |
|---|---|---|
| Expresses | A capability ("can do") | A family ("is a") |
| A type can have | Many | One |
| Fields / state | No | Yes |
| Constructors | No | Yes |
| Shared code | Only 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
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.
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 pass1.What does an interface contain?
2.How many interfaces can a class implement?
3.What's the benefit of a method parameter typed as `IDiscount` rather than `PercentageDiscount`?
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)