C# Interfaces for Beginners: Contracts Explained
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.
What Is It?
The Simple Explanation
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.
The Technical Definition
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).
- Members are implicitly public and abstract — you don't use access modifiers.
- A class can implement multiple interfaces — this is C#'s way of achieving multiple inheritance of behavior.
- Interfaces can inherit from other interfaces, creating a hierarchy of contracts.
- Structs can also implement interfaces, not just classes.
Why Does It Exist?
The Problem
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.
The Solution
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.
Big Picture
(uses interface)
IPrintable (interface)
Document
Image
Spreadsheet
IPrintableHow It Works
public interface IPrintable
{
void Print();
string Title { get; }
}
- Contains method signature
Print()and propertyTitle - No implementation — just declarations
public class Document : IPrintable
{
public string Title { get; set; }
public void Print() => Console.WriteLine($"Printing: {Title}");
}
- Class must provide implementations for all members
- Can implement multiple interfaces (e.g.,
Document : IPrintable, IStorable)
void PrintAll(IPrintable[] items)
{
foreach (var item in items)
item.Print();
}
- The method accepts any object that implements
IPrintable - Polymorphism — the correct
Print()is called at runtime
- Create
ImageandSpreadsheetclasses that also implementIPrintable PrintAll()works with them immediately — no changes needed
Simple Example
// ─── 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
- The
IAnimalinterface defines a contract: any animal must be able toSpeak(). - Each class provides its own implementation of
Speak(). AnimalSoundMakerworks with the interface, not concrete types. It doesn't care if it's a dog, cat, or duck — it just callsSpeak().- This is polymorphism via interfaces — a powerful alternative to inheritance-based polymorphism.
Real-World Example
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?
- Interface —
IPaymentProcessordefines the contract for all payment gateways. - Implementations — each gateway provides its own logic, but all obey the same interface.
- Dependency Injection —
OrderServicereceives the processor via its constructor. It depends on the abstraction (interface), not the concretion. - Flexibility — you can switch payment processors without changing
OrderService. This makes the system pluggable and testable (you can mock the interface in unit tests).
Analogy
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.
Under the Hood
- An interface variable holds a reference to an object that implements the interface
- The object's type information includes a list of interfaces it implements
- When you call an interface method, the runtime looks up the interface method table on the object
- This is similar to virtual method dispatch but uses a separate table
- In .NET, each type has a vtable for its own virtual methods and an interface map for interface implementations
- Interface method calls are slightly slower than direct virtual calls, but the difference is negligible in most applications
- The JIT can sometimes devirtualize interface calls when the concrete type is known at compile time (e.g.,
new Dog()assigned toIAnimal) - In .NET 10, interface method dispatch continues to be optimized
- C# 8.0 introduced default implementations in interfaces
- This allows you to add new methods to an interface without breaking existing implementations
- Classes can choose to override the default or use it
- This feature is most useful for library authors
Common Confusion
1. Interface vs Abstract Class
This is the most common point of confusion. Both define contracts, but they serve different purposes.
2. Interfaces vs Base Classes — Which to choose?
Use an interface when:
- You want to define a contract that can be implemented by unrelated classes
- You need multiple inheritance of behavior
- You're building a plug-in architecture
Use an abstract class when:
- You want to share code among closely related classes
- You need to define common fields or state
- You want to provide a default implementation that can be overridden
Common Mistakes
Mistake 1 — Forgetting to implement all interface members
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
}
Mistake 2 — Using 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
}
Mistake 3 — Not using interfaces for dependency injection
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.
Mistake 4 — Changing an interface after it's published
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+).
When Should I Use It?
Use an interface when:
- You want to define a contract that multiple classes can fulfill, especially unrelated ones
- You need multiple inheritance of behavior (a class can implement many interfaces)
- You're building a pluggable architecture (e.g., plugins, payment gateways, logging providers)
- You want to enable dependency injection and unit testing by mocking dependencies
- You're designing a library or framework and want to decouple your API from implementation details
When you might not need an interface:
- If you have only one implementation and it's unlikely to change, an interface may be overkill (but still good practice for testability)
- If you need to share implementation code, an abstract class is more appropriate
- If the contract is very small and you're writing a quick script, you might skip it
Mental Model
Implementing class = the signatory
Client code = the user of the contract
Remember:
· An interface says what must be done, not how
· Any class can implement an interface — they don't need to be related
· Interfaces enable polymorphism without inheritance
· Use interfaces to decouple your code and make it more testable
Key Takeaway
- Interfaces define contracts — they list members that implementing classes must provide.
- Interfaces enable polymorphism — you can write code that works with any implementation of an interface.
- Classes can implement multiple interfaces — this is C#'s way of achieving multiple inheritance of behavior.
- Interfaces decouple your code — they reduce dependencies and make your application more maintainable and testable.
- Use interfaces for dependency injection — depend on abstractions, not concretions.
- Interfaces are not the same as abstract classes — they serve different purposes. Use interfaces for contracts, abstract classes for shared implementation.
Check Your Understanding
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#?
Show answer
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?
Show answer
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?
Show answer
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?
Show answer
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
}
Show answer
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!