Yesterday you learned about inheritance — one class saying "I'm based on you, Mom." Today we flip the script. Interfaces aren't about family trees; they're about contracts. Think of an interface as a legal document that says, "If you want to call yourself an ICanFly, you'd better implement a Fly() method — or the compiler will see you in court."
1. What Is an Interface?
An interface is a promise. It says, "Any class that implements me guarantees it will have these methods and properties." It doesn't say how — that's your problem.
Think of it like a job listing:
Wanted: Someone who can
Cook(),Clean(), andDoLaundry(). We don't care how you do them. We just need them done.
Any class that "signs" this contract (implements the interface) is telling the world: "I can do all of those things." The interface doesn't contain a single line of actual logic — it's purely a specification.
If inheritance is "is-a" (a Dog is an Animal), then interfaces are "can-do" (a Dog can IFetch, IBark, and IBeAdorable).
2. Your First Interface
// Define the contract
interface IGreeter
{
string Greet(string name);
}
// Sign the contract
class FriendlyGreeter : IGreeter
{
public string Greet(string name)
{
return $"Hey {name}! Great to see you! 🎉";
}
}
class FormalGreeter : IGreeter
{
public string Greet(string name)
{
return $"Good day, {name}. I trust you are well.";
}
}
// Use the contract
IGreeter greeter = new FriendlyGreeter();
Console.WriteLine(greeter.Greet("Farhad"));
// Output: Hey Farhad! Great to see you! 🎉
greeter = new FormalGreeter();
Console.WriteLine(greeter.Greet("Farhad"));
// Output: Good day, Farhad. I trust you are well.
Let's break it down:
interface IGreeter— declares the contract. By convention, interface names start with a capitalI. This isn't optional in C# land; skip theIand your teammates will look at you like you just microwaved fish in the office.class FriendlyGreeter : IGreeter— this class implements the interface. It must provide a body for every member declared inIGreeter.IGreeter greeter = ...— you can store any implementing class in an interface-typed variable. This is polymorphism again, just like yesterday — but without needing a shared base class.
3. Interface Rules — The Fine Print
Here's what the compiler enforces:
- No instance fields. Interfaces can't hold data. They describe behavior, not state.
- Members are
publicby default. You don't writepublicin the interface — it's implied. - A class MUST implement every member. Miss one and the compiler throws a tantrum.
- A class can implement multiple interfaces. Unlike inheritance (one base class only), you can sign as many contracts as you like.
- Interfaces can inherit from other interfaces. Contracts can extend contracts.
interface IAnimal
{
string Name { get; }
void Speak();
}
interface IPet : IAnimal
{
void ComeWhenCalled();
}
class Dog : IPet
{
public string Name => "Rex";
public void Speak() => Console.WriteLine("Woof!");
public void ComeWhenCalled() => Console.WriteLine("*sprints toward you*");
}
Dog must implement everything from IPet and IAnimal because IPet extends IAnimal. Contracts are cumulative — you can't dodge the parent's terms.
4. Multiple Interfaces — The Real Superpower
Remember how C# only allows single inheritance? One base class, that's it. Interfaces blow that limitation wide open:
interface ICanSwim
{
void Swim();
}
interface ICanFly
{
void Fly();
}
interface ICanWalk
{
void Walk();
}
class Duck : ICanSwim, ICanFly, ICanWalk
{
public void Swim() => Console.WriteLine("Paddle paddle 🦆");
public void Fly() => Console.WriteLine("Flap flap!");
public void Walk() => Console.WriteLine("Waddle waddle");
}
A Duck can swim, fly, and walk. Try doing that with single inheritance — you'd need some terrifying SwimmingFlyingWalkingAnimalBase class. Interfaces keep things modular and clean.
You can pass a Duck anywhere an ICanSwim is expected:
void GoSwimming(ICanSwim swimmer)
{
swimmer.Swim();
}
GoSwimming(new Duck()); // Paddle paddle 🦆
The method doesn't care that it's a Duck. It only cares that the object can swim. This is called programming to an interface and it's a foundational design principle.
5. Interface vs Abstract Class — The Eternal Debate
This comes up in every C# interview ever, so let's nail it:
- Abstract class: Use when classes share a common identity and some implementation. A
Dogis anAnimal. - Interface: Use when unrelated classes share a common capability. A
Duckand aSubmarinecan both implementICanSwim, but they have nothing else in common.
Key differences:
- A class can inherit from one abstract class but implement many interfaces.
- Abstract classes can have fields, constructors, and implemented methods. Interfaces traditionally cannot (with one exception — see next section).
- Abstract classes can have
protectedmembers. Interface members are always effectivelypublic.
Rule of thumb: Start with an interface. Only reach for an abstract class when you genuinely need shared state or implementation that child classes inherit.
6. Default Interface Methods (C# 8+)
Here's the plot twist. Since C# 8, interfaces can actually contain method implementations:
interface ILogger
{
void Log(string message);
// Default implementation — classes get this for free
void LogError(string message)
{
Log($"ERROR: {message}");
}
}
class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($"[LOG] {message}");
}
// LogError is inherited from the interface — no need to implement it!
}
Wait — didn't we just say interfaces can't have implementations? Welcome to default interface methods (DIMs). They let you add new methods to an existing interface without breaking every class that already implements it. It's an evolution mechanism.
Important caveat: Default methods are only accessible through the interface type, not the class type:
ConsoleLogger logger = new ConsoleLogger();
// logger.LogError("Oops"); // ❌ Compiler error!
ILogger iLogger = logger;
iLogger.LogError("Oops"); // ✅ Works through the interface
This is a deliberate design choice. Default interface methods are meant for backward compatibility, not for replacing abstract classes.
7. A Taste of IDisposable — Cleaning Up After Yourself
One interface you'll meet constantly in .NET is IDisposable. It has exactly one method:
public interface IDisposable
{
void Dispose();
}
Classes that hold expensive resources (database connections, file handles, network sockets) implement IDisposable to promise: "Call Dispose() on me and I'll clean up properly."
C# gives you a nice shorthand for this — the using statement:
using StreamReader reader = new StreamReader("data.txt");
string content = reader.ReadToEnd();
Console.WriteLine(content);
// reader.Dispose() is called automatically when 'reader' goes out of scope
No need to call Dispose() manually — the using keyword does it for you. Think of it as a safety net for forgetful developers (which is all of us).
We'll go deeper into IDisposable and resource management later in the series. For now, just know: if a class implements IDisposable, wrap it in using.
8. Your Homework: Build a Notification System
Create a console app with:
- An
INotificationSenderinterface with a methodvoid Send(string recipient, string message). - Three classes that implement it:
EmailSender,SmsSender, andPushNotificationSender. Each should print a different message to the console. - A method
void NotifyAll(List<INotificationSender> senders, string recipient, string message)that loops through all senders and callsSend. - In
Main, create a list with all three senders and callNotifyAll.
interface INotificationSender
{
void Send(string recipient, string message);
}
class EmailSender : INotificationSender
{
public void Send(string recipient, string message)
{
Console.WriteLine($"📧 Email to {recipient}: {message}");
}
}
class SmsSender : INotificationSender
{
public void Send(string recipient, string message)
{
Console.WriteLine($"📱 SMS to {recipient}: {message}");
}
}
class PushNotificationSender : INotificationSender
{
public void Send(string recipient, string message)
{
Console.WriteLine($"🔔 Push to {recipient}: {message}");
}
}
// Usage
List<INotificationSender> senders =
[
new EmailSender(),
new SmsSender(),
new PushNotificationSender()
];
foreach (INotificationSender sender in senders)
{
sender.Send("Farhad", "Your pizza is ready!");
}
Bonus challenge: Add a fourth sender — DiscordSender — without modifying any existing code. Notice how you just add a new class and drop it in the list? That's the power of interfaces.
Summary of Day 12
- Interfaces are contracts — they declare what a class can do without specifying how.
- Interface names start with
Iby convention (IGreeter,ICanSwim). - A class can implement multiple interfaces but inherit from only one base class.
- Program to an interface, not an implementation — this makes your code flexible and testable.
- Default interface methods (C# 8+) let you add methods with implementations to interfaces for backward compatibility.
IDisposableis the most common .NET interface — useusingto auto-callDispose().- Interfaces + polymorphism = code that can handle objects it hasn't even met yet.
Tomorrow: we'll talk about Enums and Structs — lightweight types for when classes feel like overkill. Sometimes you don't need a whole house; a tent will do just fine. ⛺
See you on Day 13!