C# · Object-Oriented Programming

Interfaces

An interface is a contract: “anything that implements me promises to have these members.” It lets unrelated classes be used the same way — and it is the backbone of dependency injection in ASP.NET Core.

Intermediate 5 min Lesson 16 of 26Examples target C# 13 on .NET 9 unless stated otherwise.
Before you start:Abstraction
0/26 in C#
On this page · 5 min · Intermediate

In simple words#

A power socket is an interface: any plug that fits works — phone charger, laptop or fan. Classes that “fit” an interface can be swapped freely.

Interface
A contract: methods a class promises to have.
implements
class EmailSender : INotifier fulfils the contract.
Loose coupling
Code depends on the contract, not a specific class.

Learning objectives#

  • Declare and implement an interface
  • Implement several interfaces in one class
  • Program against an interface instead of a concrete class
  • Use built-in interfaces such as IComparable<T>

Explanation#

An interface lists members without (usually) any implementation: interface INotifier { void Send(string message); }. By convention its name starts with I.

A class implements it with the same : syntax as inheritance: class EmailNotifier : INotifier. It must provide every member.

A class can inherit only one class but can implement many interfaces: class Order : IComparable<Order>, IDisposable.

Code that depends on INotifier works with any notifier — email, SMS, WhatsApp or a fake one in a unit test. This is “programming to an interface”.

.NET is full of interfaces: IEnumerable<T> (anything you can foreach), IComparable<T> (anything you can sort), IDisposable (anything you must clean up).

Syntax#

C# Illustrative
interface IShippingCalculator
{
    decimal Calculate(decimal weightKg);
}


class FlatRateShipping : IShippingCalculator
{
    public decimal Calculate(decimal weightKg) => 50m;
}

Code example: Notifiers and sortable products#

C#Program.cs Illustrative
INotifier[] notifiers = [new EmailNotifier(), new SmsNotifier()];
var orders = new OrderService(notifiers);
orders.PlaceOrder("ORD-1001");


var products = new List<Product> { new("Bag", 650m), new("Pen", 20m), new("Book", 120m) };
products.Sort();   // uses IComparable<Product>
Console.WriteLine(string.Join(", ", products.Select(p => p.Name)));


interface INotifier
{
    void Send(string message);
}


class EmailNotifier : INotifier
{
    public void Send(string message) => Console.WriteLine(
quot;Email:
{message}");
} class SmsNotifier : INotifier { public void Send(string message) => Console.WriteLine(
quot;SMS:
{message}");
} // OrderService only knows about INotifier, not about email or SMS. class OrderService(IEnumerable<INotifier> notifiers) { public void PlaceOrder(string orderId) { foreach (var notifier in notifiers) { notifier.Send(
quot;Order
{orderId} confirmed");
} } } class Product(string name, decimal price) : IComparable<Product> { public string Name => name; public decimal Price => price; public int CompareTo(Product? other) => other is null ? 1 : Price.CompareTo(other.Price); }
Expected output
Email: Order ORD-1001 confirmed
SMS: Order ORD-1001 confirmed
Pen, Book, Bag

This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.

Step-by-step explanation#

  1. INotifier declares one member: Send.
  2. EmailNotifier and SmsNotifier are unrelated classes that both keep the contract.
  3. OrderService receives a collection of INotifier and never mentions a concrete class.
  4. Product implements the built-in IComparable<Product>, so List.Sort knows how to order products by price.

Real-world use case#

In ASP.NET Core you register builder.Services.AddScoped<IEmailSender, SmtpEmailSender>(). Controllers ask for IEmailSender; in tests you pass a fake implementation and no real email is sent.

Common mistakes#

Not implementing every member

Error CS0535: the class does not implement interface member X. Add the missing member with the exact signature.

Fat interfaces

An IRepository with 25 methods forces every implementation to write them all. Split into smaller interfaces (Interface Segregation Principle).

An interface for every class “just because”

Add an interface when you need multiple implementations or test doubles, not automatically.

Best practices#

  • Name interfaces with an I prefix (IOrderService)
  • Keep interfaces small and focused on one role
  • Depend on interfaces in constructors; register implementations in DI
  • Use built-in interfaces (IEnumerable<T>, IDisposable) where they fit

Remember this#

Interface = a promise of members, not the code.

One base class, many interfaces.

Depend on IThing, not on Thing.

Practice exercise#

Your task: Create an IDiscount interface with decimal Apply(decimal price). Implement PercentDiscount(10%) and FlatDiscount(50). Apply both to a price of 500 and print the results.

Need a hint?
PercentDiscount returns price - price * percent / 100; FlatDiscount returns price - amount.
Show solution
C# Illustrative
IDiscount[] discounts = [new PercentDiscount(10), new FlatDiscount(50)];
foreach (var d in discounts) Console.WriteLine(d.Apply(500m));
// 450
// 450


interface IDiscount { decimal Apply(decimal price); }
class PercentDiscount(decimal percent) : IDiscount
{
    public decimal Apply(decimal price) => price - price * percent / 100;
}
class FlatDiscount(decimal amount) : IDiscount
{
    public decimal Apply(decimal price) => price - amount;
}

Mini quiz#

Interfaces quizMultiple choice Question 1 of 3

How many interfaces can a C# class implement?

Score so far: 0/0

Want more? Practise all C# MCQs · C# interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.