An interface is a contract — a promise that a class will do certain things, without saying how.
Imagine you're building a system that needs to print documents. You have a Document class, an Image class, and a Spreadsheet class. Each can be printed, but each prints differently. You want a single method Print() that works on any of them, without caring about the specific type.
In C#, you can define an interface — a list of method signatures — and have each class implement that interface. Then you can write code that works with the interface, and it will work with any class that implements it. This is one of the most powerful tools for building flexible, maintainable, and testable applications.
In this lesson, you'll learn what interfaces are, why they exist, how they differ from abstract classes, and how to use them effectively in your .NET applications.
An interface is like a contract. It says: "If you want to be a printable thing, you must have a Print() method." It doesn't say how that method works — that's up to the class that signs the contract.
In C#, an interface is a reference type that defines a set of abstract members — methods, properties, events, or indexers — that a class or struct must implement. Interfaces contain no implementation (except for default interface methods introduced in C# 8.0, but we'll focus on the classic case).
Without interfaces, you'd have to write code that knows about every concrete type. For example, a Printer class would need to check: "Is this a Document? Then call Document.Print(). Is it an Image? Then call Image.Print()." Every time you add a new printable type, you'd have to modify the Printer class. This is tight coupling and violates the Open/Closed principle.
Interfaces invert the dependency. The Printer class depends on the IPrintable interface, not on concrete types. New printable classes can be added without changing Printer — as long as they implement the interface. This is dependency inversion and enables polymorphism without inheritance.
IPrintable (interface)
Document
Image
Spreadsheet
IPrintablepublic interface IPrintable
{
void Print();
string Title { get; }
}
Print() and property Titlepublic class Document : IPrintable
{
public string Title { get; set; }
public void Print() => Console.WriteLine($"Printing: {Title}");
}
Document : IPrintable, IStorable)void PrintAll(IPrintable[] items)
{
foreach (var item in items)
item.Print();
}
IPrintablePrint() is called at runtimeImage and Spreadsheet classes that also implement IPrintablePrintAll() works with them immediately — no changes needed// ─── Define the interface ───
public interface IAnimal
{
string Speak();
}
// ─── Implement the interface ───
public class Dog : IAnimal
{
public string Speak() => "Woof!";
}
public class Cat : IAnimal
{
public string Speak() => "Meow!";
}
public class Duck : IAnimal
{
public string Speak() => "Quack!";
}
// ─── Use the interface ───
public class AnimalSoundMaker
{
public void MakeSound(IAnimal animal)
{
Console.WriteLine(animal.Speak());
}
}
// ─── Usage ───
var maker = new AnimalSoundMaker();
maker.MakeSound(new Dog()); // "Woof!"
maker.MakeSound(new Cat()); // "Meow!"
maker.MakeSound(new Duck()); // "Quack!"
Code → Meaning → Result
IAnimal interface defines a contract: any animal must be able to Speak().Speak().AnimalSoundMaker works with the interface, not concrete types. It doesn't care if it's a dog, cat, or duck — it just calls Speak().Consider an e-commerce system with multiple payment gateways. You want to support Stripe, PayPal, and a bank transfer, but your order processing logic should be agnostic.
// ─── Interface ───
public interface IPaymentProcessor
{
bool ProcessPayment(decimal amount, string currency);
}
// ─── Implementations ───
public class StripeProcessor : IPaymentProcessor
{
public bool ProcessPayment(decimal amount, string currency)
{
Console.WriteLine($"Stripe: processing {amount} {currency}");
// Stripe API call...
return true;
}
}
public class PayPalProcessor : IPaymentProcessor
{
public bool ProcessPayment(decimal amount, string currency)
{
Console.WriteLine($"PayPal: processing {amount} {currency}");
// PayPal API call...
return true;
}
}
public class BankTransferProcessor : IPaymentProcessor
{
public bool ProcessPayment(decimal amount, string currency)
{
Console.WriteLine($"Bank transfer: processing {amount} {currency}");
// Bank transfer logic...
return true;
}
}
// ─── Order Service ───
public class OrderService
{
private readonly IPaymentProcessor _paymentProcessor;
public OrderService(IPaymentProcessor paymentProcessor)
{
_paymentProcessor = paymentProcessor;
}
public void Checkout(decimal amount, string currency)
{
if (_paymentProcessor.ProcessPayment(amount, currency))
Console.WriteLine("Order completed!");
else
Console.WriteLine("Payment failed.");
}
}
// ─── Usage ───
var orderService = new OrderService(new StripeProcessor());
orderService.Checkout(99.99m, "USD");
orderService = new OrderService(new PayPalProcessor());
orderService.Checkout(49.50m, "EUR");
What's happening here?
IPaymentProcessor defines the contract for all payment gateways.OrderService receives the processor via its constructor. It depends on the abstraction (interface), not the concretion.OrderService. This makes the system pluggable and testable (you can mock the interface in unit tests).Think of an interface as a USB port on your computer. The port defines a standard shape and electrical protocol (the interface). Any device that conforms to that standard — a mouse, a keyboard, a flash drive, a printer — can plug into the port. The computer doesn't know what device it is, but it knows how to communicate with it through the port.
In the same way, an interface defines a standard set of methods. Any class that implements that interface can be used wherever the interface is expected. The calling code doesn't need to know the concrete type — it just knows the contract.
new Dog() assigned to IAnimal)This is the most common point of confusion. Both define contracts, but they serve different purposes.
Use an interface when:
Use an abstract class when:
If you implement an interface but forget a method, you get a compiler error.
public interface IWorker
{
void Work();
void Rest();
}
public class Employee : IWorker
{
public void Work() => Console.WriteLine("Working...");
// Missing Rest() → compiler error
}
public keyword in an interface Interface members are implicitly public. Adding public is redundant and causes a warning.
public interface IExample
{
public void DoWork(); // "public" is not needed
}
When you create tightly coupled code, you lose testability and flexibility.
public class OrderService
{
private StripeProcessor _processor = new StripeProcessor(); // concrete dependency
// Hard to test, hard to swap.
}
Instead, depend on the interface and inject it.
Adding a new method to an existing interface breaks all implementing classes. This is a binary breaking change. If you need to add functionality, consider a new interface or use default interface methods (C# 8+).
You've seen how interfaces define contracts, enable polymorphism, and decouple your code. Let's see if you can apply this knowledge.
1. Which of the following best describes the primary purpose of an interface in C#?
Correct: B
Why B is correct: An interface is a contract. It specifies a set of members (methods, properties, events, etc.) that any implementing class must provide. This ensures consistency and enables polymorphism.
Why A is incorrect: Interfaces do not provide implementation (except default interface methods in C# 8+, but that's not the primary purpose). Abstract classes are better suited for providing default implementations.
Why C is incorrect: C# does not support multiple inheritance of classes. A class can implement multiple interfaces, but it can inherit from only one base class.
Why D is incorrect: Interfaces cannot contain fields. Abstract classes can contain fields and state.
Reinforcement: The primary purpose of an interface is to define a contract, not to provide implementation or store state.
2. A class can implement multiple interfaces. What is the main benefit of this feature?
Correct: B
Why B is correct: The ability to implement multiple interfaces allows a single class to fulfill multiple contracts. This is a form of multiple inheritance of behavior (but not state). It makes the design more flexible and enables mix-in style patterns.
Why A is incorrect: C# does not support multiple inheritance of classes. Interfaces provide a way to achieve multiple inheritance of behavior, not of base classes.
Why C is incorrect: Interface method calls may have a slight overhead compared to direct calls; the benefit is design flexibility, not performance.
Why D is incorrect: Interfaces do not provide default implementations (except in C# 8+ with default interface methods, but that is optional). The class must implement all members unless it uses a default.
Reinforcement: Multiple interface implementation is a key feature that enables flexible, composable designs.
3. Which of the following is NOT a valid difference between an interface and an abstract class?
Correct: C
Why C is correct: This statement is false, making it the correct answer to "which is NOT a valid difference." Interfaces (prior to C# 8) contain no implementation; they define only signatures. Abstract classes can contain concrete methods, fields, and other implementation details.
Why A is incorrect: This is a valid difference — interfaces cannot have instance fields; abstract classes can.
Why B is incorrect: This is a valid difference — multiple interface implementation is allowed; multiple inheritance of classes is not.
Why D is incorrect: This is a valid difference — interface members are public by default and cannot have modifiers; abstract class members can have various access modifiers.
Reinforcement: Interfaces define contracts (what), abstract classes can provide shared implementation (how). They are complementary, not interchangeable.
4. You are building a logging library. You want to support different log destinations (console, file, database). You also want to allow users to add custom destinations without modifying your core code. Which design should you use?
Correct: B
Why B is correct: Defining an ILogger interface is the classic way to support pluggable log destinations. The core library depends on the interface, and users can implement their own destinations. This follows the Open/Closed principle and enables dependency injection.
Why A is incorrect: Static methods would lock you into a fixed set of destinations; adding new ones would require modifying the library.
Why C is incorrect: An abstract base class could work, but interfaces are more flexible because they allow unrelated classes to implement logging (e.g., an existing class could implement ILogger without inheriting from a specific base). Also, interfaces are better for multiple implementation constraints.
Why D is incorrect: A switch would require recompilation every time a new destination is added, violating the Open/Closed principle.
Reinforcement: Interfaces are the ideal tool for creating extensible, pluggable architectures.
5. Consider the following code. What will happen when you try to compile it?
public interface IHasName
{
string Name { get; set; }
}
public class Person : IHasName
{
public string Name { get; set; }
}
public class Animal : IHasName
{
// Notice: no Name property
}
Correct: C
Why C is correct: The Animal class declares that it implements IHasName but does not provide the required Name property. The compiler will generate an error: "Animal does not implement interface member IHasName.Name".
Why A is incorrect: Animal does not inherit from Person; it is a separate class. Inheritance of implementation does not happen through interfaces.
Why B is incorrect: Interfaces are not optional when a class declares that it implements them; the class must fulfill the contract.
Why D is incorrect: Person correctly implements the interface.
Reinforcement: When a class implements an interface, it must provide implementations for all members of that interface, otherwise the code won't compile.
You now have a solid foundation in interfaces — you can define them, implement them, and use them to build flexible, decoupled systems!
dotnetmadeeasy.com — Learn C# and .NET, the right way.