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.
On this page · 5 min · Intermediate
On this page
- In simple words
- Learning objectives
- Explanation
- Syntax
- Code example
- Step by step
- Real-world use
- Common mistakes
- Best practices
- Remember this
- Practice exercise
- Mini quiz
- Related topics
- My notes
- Reading time
- 5 min
- Difficulty
- Intermediate
- Updated
- Oct 28, 2026
Related tutorials
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#
interface IShippingCalculator
{
decimal Calculate(decimal weightKg);
}
class FlatRateShipping : IShippingCalculator
{
public decimal Calculate(decimal weightKg) => 50m;
}
Code example: Notifiers and sortable products#
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);
}
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#
- INotifier declares one member: Send.
- EmailNotifier and SmsNotifier are unrelated classes that both keep the contract.
- OrderService receives a collection of INotifier and never mentions a concrete class.
- 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#
Error CS0535: the class does not implement interface member X. Add the missing member with the exact signature.
An IRepository with 25 methods forces every implementation to write them all. Split into smaller interfaces (Interface Segregation Principle).
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?
Show solution
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#
How many interfaces can a C# class implement?
Want more? Practise all C# MCQs · C# interview questions
My notes#
Saved in this browser.