An interface is a contract. It lists the methods and properties a type promises to provide, without saying how they work. Any class or struct that implements the interface must provide every member it lists. An IPaymentProvider interface might require a Charge(amount) method; a card provider, a PayPal provider and a test provider can all implement it, each in their own way.
Interfaces are one of the most important tools in professional C#. Unlike base classes, a type can implement many interfaces, so you can describe separate abilities, such as IComparable for sorting and IDisposable for clean-up. Code that depends on an interface rather than a specific class can work with any implementation, which makes it flexible and easy to test: in a unit test, you pass in a fake implementation instead of a real database or payment service.
By convention, interface names start with a capital I. In this lesson you'll write and implement interfaces, use them as types, meet the built-in interfaces you'll use constantly, and see how interfaces make dependency injection possible.
Declaring and implementing an interface
interface INotifier
{
string Channel { get; }
void Send(string to, string message);
}
class EmailNotifier : INotifier
{
public string Channel => "Email";
public void Send(string to, string message) =>
Console.WriteLine($"Emailing {to}: {message}");
}- Interface members are public by default and usually have no body.
- The implementing class uses the same
:syntax as inheritance. - If a member is missing, the compiler reports error CS0535 and names it.
- A class can inherit one base class and implement many interfaces:
class Report : Document, IPrintable, IExportable.
Interfaces as types
A variable, parameter or list can have an interface type and hold any implementation:
List<INotifier> notifiers = [new EmailNotifier(), new SmsNotifier()];
foreach (INotifier n in notifiers)
{
n.Send("ada@example.com", "Your order shipped");
}Code written against INotifier doesn't know or care which class it gets. Adding a PushNotifier later requires no changes to this loop.
Modern C# also allows default interface methods, members with a body that implementing types can use or replace. They let library authors add members to an interface without breaking existing implementations.
Built-in interfaces
| Interface | What it promises | Used by |
|---|---|---|
IEnumerable<T> | Items can be looped over | foreach, LINQ |
IComparable<T> | A natural sort order via CompareTo | Sort(), OrderBy |
IEquatable<T> | Value equality via Equals | Collections, comparisons |
IDisposable | A Dispose() method that releases resources | using statements |
using var file = File.OpenRead("data.txt"); calls Dispose automatically at the end of the scope, closing the file even if an exception happens.
Explicit interface implementation
Normally an interface member is implemented as an ordinary public method. You can instead implement it explicitly by writing the interface name in front of the member:
interface IPrintable { void Print(); }
class Report : IPrintable
{
void IPrintable.Print() => Console.WriteLine("Printing report");
}
var report = new Report();
// report.Print(); // error: Report has no public Print
IPrintable printable = report;
printable.Print(); // Printing reportAn explicit member has no access modifier and can only be called through a variable of the interface type. There are two common reasons to use it. First, when two interfaces declare a member with the same name but different meanings, explicit implementation lets you give each its own version. Second, it keeps a class's public surface tidy when an interface member is only meant for code that works through the interface, which is how some .NET collections hide rarely used members.
C# Object-Oriented Programming Syntax
Interface syntax
interface IShape
{
double Area();
string Name => GetType().Name; // default implementation
}
class Square(double side) : IShape
{
public double Area() => side * side;
}
class Job : IComparable<Job>, IDisposable
{
public int Priority { get; init; }
public int CompareTo(Job? other) => Priority.CompareTo(other?.Priority);
public void Dispose() { }
}
using var res = new Job();C# Object-Oriented Programming Summary
- An interface lists members that implementing types must provide; names start with I.
- A class can implement many interfaces as well as inheriting one base class.
- Interface-typed variables and parameters accept any implementation.
- IEnumerable, IComparable, IEquatable and IDisposable are everyday built-in interfaces.
- Depending on interfaces makes code flexible and easy to test with fakes.
C# Object-Oriented Programming Examples
Each example shows the code, the output it produces and what each part does.
One loop, many notifiers
List<INotifier> notifiers = [new EmailNotifier(), new SmsNotifier("+44")];
foreach (INotifier n in notifiers)
{
n.Send("Ada", "Your order has shipped");
}
Console.WriteLine(notifiers[1].Describe());
interface INotifier
{
string Channel { get; }
void Send(string to, string message);
string Describe() => $"Sends messages by {Channel}";
}
class EmailNotifier : INotifier
{
public string Channel => "email";
public void Send(string to, string message) =>
Console.WriteLine($"[email] To {to}: {message}");
}
class SmsNotifier(string countryCode) : INotifier
{
public string Channel => "SMS";
public void Send(string to, string message) =>
Console.WriteLine($"[sms {countryCode}] To {to}: {message[..10]}...");
}[email] To Ada: Your order has shipped
[sms +44] To Ada: Your order...
Sends messages by SMSLine-by-line explanation
- Line 1The list's type is the interface, so it can hold any implementation.
- Line 5The same call works for every notifier.
- Line 7Describe is a default interface method, so it's called through the interface type.
- Line 13Default methods have a body that implementations can use as is.
- Line 27SMS messages are shortened to their first 10 characters.
IComparable for sorting
var versions = new List<AppVersion>
{
new(2, 1), new(1, 10), new(1, 2), new(2, 0)
};
versions.Sort();
Console.WriteLine(string.Join(", ", versions));
Console.WriteLine(versions.Max());
record AppVersion(int Major, int Minor) : IComparable<AppVersion>
{
public int CompareTo(AppVersion? other)
{
if (other is null) return 1;
int byMajor = Major.CompareTo(other.Major);
return byMajor != 0 ? byMajor : Minor.CompareTo(other.Minor);
}
public override string ToString() => $"v{Major}.{Minor}";
}v1.2, v1.10, v2.0, v2.1
v2.1Line-by-line explanation
- Line 3Target-typed new creates AppVersion objects without repeating the type name.
- Line 6Sort uses CompareTo, because AppVersion implements IComparable.
- Line 10A record is a compact class, covered in a later lesson.
- Lines 12 to 17CompareTo returns a negative number, zero or a positive number for before, equal and after.
- Line 16Minor versions only matter when the major versions are equal.
Testing with a fake through an interface
var fakeClock = new FixedClock(new DateTime(2026, 12, 24, 18, 0, 0));
var greeter = new Greeter(fakeClock);
Console.WriteLine(greeter.Greet("Sam"));
var morning = new Greeter(new FixedClock(new DateTime(2026, 5, 1, 9, 30, 0)));
Console.WriteLine(morning.Greet("Sam"));
interface IClock
{
DateTime Now { get; }
}
class SystemClock : IClock
{
public DateTime Now => DateTime.Now;
}
class FixedClock(DateTime time) : IClock
{
public DateTime Now => time;
}
class Greeter(IClock clock)
{
public string Greet(string name) =>
clock.Now.Hour < 12 ? $"Good morning, {name}" : $"Good evening, {name}";
}Good evening, Sam
Good morning, SamLine-by-line explanation
- Line 1In tests, a fixed clock replaces the real one so results are predictable.
- Lines 13 to 16The real app would pass a SystemClock instead.
- Line 23Greeter only knows about IClock, so any implementation works.
- Lines 25 to 26The greeting depends on the hour the clock reports.
Common C# Object-Oriented Programming Mistakes
Forgetting to implement a member
class SmsNotifier : INotifier
{
public string Channel => "SMS";
} // error CS0535: does not implement 'INotifier.Send(string, string)'class SmsNotifier : INotifier
{
public string Channel => "SMS";
public void Send(string to, string message) { /* ... */ }
}Trying to create an interface
var n = new INotifier(); // error CS0144: cannot create an instance of an interfaceINotifier n = new EmailNotifier();Creating dependencies inside the class
class OrderService
{
private readonly SqlDatabase db = new(); // impossible to swap in tests
}class OrderService(IOrderRepository repository)
{
// the repository is passed in and can be a fake in tests
}C# Object-Oriented Programming in Real Projects
Interfaces in real projects
ASP.NET Core applications are built around interfaces: ILogger<T> for logging, IConfiguration for settings, and your own IOrderRepository or IEmailSender, all supplied by dependency injection. Unit tests pass fakes or mocks created with libraries like NSubstitute or Moq. Every LINQ query works because collections implement IEnumerable<T>, and IDisposable ensures files, database connections and HTTP responses are released promptly.
C# Object-Oriented Programming FAQ
What is the difference between an interface and an abstract class in C#?
An abstract class can hold fields, constructors and shared code, but a class can inherit from only one. An interface describes a set of abilities with no instance state, and a class can implement many. Use an abstract class for closely related types that share real implementation, and an interface for a capability that unrelated types can have.
Why do C# interface names start with I?
It's a naming convention from Microsoft's design guidelines, not a language rule. The I prefix, as in IDisposable or INotifier, tells readers at a glance that a type is an interface rather than a class. Following it makes your code look like the rest of .NET.
Can I create an instance of an interface?
No. new INotifier() is a compile error, because an interface has no complete implementation to run. Create an object of a class that implements it and store it in a variable of the interface type: INotifier n = new EmailNotifier();.
Can an interface have properties in C#?
Yes. An interface can declare properties, such as string Channel { get; }, and every implementing class must provide them. The implementing class can add a setter even when the interface only asks for a getter.
Why can't I call a default interface method on my class variable?
A default interface method belongs to the interface, not to the class, unless the class implements the member itself. Call it through a variable of the interface type, for example IShape s = new Square(); s.Describe();. If you want it callable on the class directly, add the member to the class.
C# Object-Oriented Programming Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Complete the declaration of the interface.
IPrintable
{
void Print();
}
By convention, what do C# interface names start with?
How many interfaces can a class implement?
What does this print?
IGreeter g = new Polite();
Console.WriteLine(g.Hello());
interface IGreeter { string Hello(); }
class Polite : IGreeter { public string Hello() => "Good day"; }
What does using var conn = OpenConnection(); guarantee?
C# Object-Oriented Programming Quiz
5 questions to check what you learned.
Can you create an object directly from an interface with new?
- Yes
- Only with default methods
- No
- Only for generic interfaces
Which interface lets a type be used in foreach?
- IComparable
- IEnumerable<T>
- IDisposable
- IEquatable<T>
What's one advantage of interfaces over abstract classes?
- They're faster
- They can store fields
- They have constructors
- A class can implement many interfaces but inherit only one class
Why do interfaces make code easier to test?
- Tests run faster
- A fake implementation can replace a real database or service
- Interfaces can't fail
- They remove the need for tests
What does IComparable<T>.CompareTo return when this comes before other?
- true
- Zero
- A negative number
- A positive number