C# Interfaces Basics
Introduction
Interfaces are a fundamental concept in object-oriented programming and play a crucial role in C# development. An interface can be thought of as a contract that classes must follow, defining a set of methods, properties, events, or indexers that implementing classes must provide.
In this tutorial, we'll explore the basics of C# interfaces - from defining them to understanding why they are so valuable in building maintainable, flexible code.
What is an Interface?
An interface in C# is defined using the interface
keyword. It contains declarations of methods, properties, events, or indexers, but doesn't provide implementations for them.
Think of an interface as a blueprint that classes agree to follow. When a class implements an interface, it promises to provide concrete implementations for all the members defined in that interface.
Defining an Interface
Here's how to define a simple interface in C#:
// Interface names typically start with 'I' by convention
public interface IPlayable
{
// Method declaration (no implementation)
void Play();
// Property declaration
bool IsPlaying { get; }
// Method with parameters
void SetVolume(int level);
}
Notice that:
- Interface names conventionally start with the letter 'I'
- Members don't have access modifiers (they are implicitly public)
- Methods and properties don't have implementations (no method bodies)
- You can't define fields in interfaces
Implementing an Interface
To implement an interface, a class uses the colon (:
) syntax and must provide implementations for all interface members:
public class MusicPlayer : IPlayable
{
private bool _isCurrentlyPlaying = false;
// Implementing the Play method
public void Play()
{
_isCurrentlyPlaying = true;
Console.WriteLine("Music is now playing");
}
// Implementing the IsPlaying property
public bool IsPlaying
{
get { return _isCurrentlyPlaying; }
}
// Implementing the SetVolume method
public void SetVolume(int level)
{
Console.WriteLine($"Volume set to {level}");
}
}
Let's see how to use this class:
class Program
{
static void Main()
{
// Create an instance of MusicPlayer
MusicPlayer player = new MusicPlayer();
// Call methods defined in the interface
player.Play();
player.SetVolume(7);
// Access property defined in the interface
Console.WriteLine($"Is music playing? {player.IsPlaying}");
// We can also reference the object through the interface type
IPlayable playableDevice = new MusicPlayer();
playableDevice.Play();
}
}
Output:
Music is now playing
Volume set to 7
Is music playing? True
Music is now playing
Interface vs. Abstract Class
Beginners often confuse interfaces with abstract classes. Here are the key differences:
Interface | Abstract Class |
---|---|
Can only declare functionality | Can declare and implement functionality |
No implementation code | Can have implementation code for some methods |
A class can implement multiple interfaces | A class can inherit from only one abstract class |
No fields | Can have fields |
No constructors | Can have constructors |
Implementing Multiple Interfaces
A powerful feature of interfaces is that a class can implement multiple interfaces:
public interface IPlayable
{
void Play();
void Stop();
}
public interface IRecordable
{
void StartRecording();
void StopRecording();
}
// Class implementing both interfaces
public class MediaPlayer : IPlayable, IRecordable
{
public void Play()
{
Console.WriteLine("Playing media");
}
public void Stop()
{
Console.WriteLine("Playback stopped");
}
public void StartRecording()
{
Console.WriteLine("Recording started");
}
public void StopRecording()
{
Console.WriteLine("Recording stopped");
}
}
Usage example:
class Program
{
static void Main()
{
MediaPlayer player = new MediaPlayer();
// Use as IPlayable
IPlayable playable = player;
playable.Play();
playable.Stop();
// Use as IRecordable
IRecordable recordable = player;
recordable.StartRecording();
recordable.StopRecording();
}
}
Output:
Playing media
Playback stopped
Recording started
Recording stopped
Explicit Interface Implementation
Sometimes a class might need to implement two interfaces that have methods with the same name. In such cases, you can use explicit interface implementation:
public interface IControl
{
void Paint();
}
public interface IWindow
{
void Paint(); // Same name as the method in IControl
}
public class Form : IControl, IWindow
{
// Explicit implementation for IControl
void IControl.Paint()
{
Console.WriteLine("Painting control");
}
// Explicit implementation for IWindow
void IWindow.Paint()
{
Console.WriteLine("Painting window");
}
// Class can also have its own Paint method
public void Paint()
{
Console.WriteLine("Form's own Paint method");
}
}
Here's how to use explicit implementations:
class Program
{
static void Main()
{
Form form = new Form();
// This calls the form's own Paint method
form.Paint();
// To call the interface implementations, cast to the specific interface
((IControl)form).Paint();
((IWindow)form).Paint();
// Or use an interface reference
IControl control = form;
control.Paint();
}
}
Output:
Form's own Paint method
Painting control
Painting window
Painting control
Real-World Example: The Repository Pattern
Let's look at a practical example showing how interfaces are used in real-world applications. The repository pattern is commonly used in applications to separate data access logic:
// Define an entity
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
// Define the repository interface
public interface ICustomerRepository
{
Customer GetById(int id);
List<Customer> GetAll();
void Add(Customer customer);
void Update(Customer customer);
void Delete(int id);
}
// Concrete implementation for SQL Server
public class SqlCustomerRepository : ICustomerRepository
{
public Customer GetById(int id)
{
// In a real app, this would query the database
Console.WriteLine($"Getting customer {id} from SQL Server");
return new Customer { Id = id, Name = "Sample Customer", Email = "[email protected]" };
}
public List<Customer> GetAll()
{
Console.WriteLine("Getting all customers from SQL Server");
return new List<Customer>
{
new Customer { Id = 1, Name = "John Doe", Email = "[email protected]" },
new Customer { Id = 2, Name = "Jane Smith", Email = "[email protected]" }
};
}
public void Add(Customer customer)
{
Console.WriteLine($"Adding customer {customer.Name} to SQL Server");
}
public void Update(Customer customer)
{
Console.WriteLine($"Updating customer {customer.Id} in SQL Server");
}
public void Delete(int id)
{
Console.WriteLine($"Deleting customer {id} from SQL Server");
}
}
// Different implementation for MongoDB
public class MongoCustomerRepository : ICustomerRepository
{
public Customer GetById(int id)
{
Console.WriteLine($"Getting customer {id} from MongoDB");
return new Customer { Id = id, Name = "Sample Customer", Email = "[email protected]" };
}
// Other methods implemented similarly...
public List<Customer> GetAll()
{
Console.WriteLine("Getting all customers from MongoDB");
return new List<Customer>();
}
public void Add(Customer customer)
{
Console.WriteLine($"Adding customer {customer.Name} to MongoDB");
}
public void Update(Customer customer)
{
Console.WriteLine($"Updating customer {customer.Id} in MongoDB");
}
public void Delete(int id)
{
Console.WriteLine($"Deleting customer {id} from MongoDB");
}
}
This pattern allows us to swap database implementations without changing the business logic:
class Program
{
static void Main()
{
// We can easily switch implementations
ICustomerRepository repository = new SqlCustomerRepository();
// ICustomerRepository repository = new MongoCustomerRepository();
// Business logic works with any implementation
Customer customer = repository.GetById(1);
Console.WriteLine($"Got customer: {customer.Name}");
repository.Add(new Customer { Name = "New Customer", Email = "[email protected]" });
}
}
Output (with SqlCustomerRepository):
Getting customer 1 from SQL Server
Got customer: Sample Customer
Adding customer New Customer to SQL Server
Best Practices for Using Interfaces
-
Keep interfaces focused: Follow the Interface Segregation Principle (ISP) and create small, specific interfaces rather than large, general-purpose ones.
-
Name interfaces clearly: Use descriptive names that reflect the behavior they define (e.g.,
IComparable
,IEnumerable
,IDisposable
). -
Use interfaces for abstraction: Interfaces are great for hiding implementation details and providing a stable API.
-
Design for testability: Interfaces make it easier to create mock implementations for testing.
-
Version interfaces carefully: Adding members to an interface breaks existing implementations, so design interfaces thoughtfully from the start.
Summary
Interfaces are a powerful feature in C# that enable:
- Creating contracts that classes must adhere to
- Building more flexible, modular, and testable code
- Implementing multiple inheritance in a controlled way
- Defining clear boundaries between components
By mastering interfaces, you'll be able to create more maintainable applications that are easier to extend and modify over time.
Practice Exercises
-
Create an interface called
ILogger
with methods for logging information, warnings, and errors. Then implement this interface in two different classes:ConsoleLogger
andFileLogger
. -
Design a simple application that uses the Strategy Pattern with interfaces. Create an interface called
IPaymentMethod
with different implementations likeCreditCardPayment
,PayPalPayment
, andBitcoinPayment
. -
Implement an interface called
IShape
with methods to calculate area and perimeter. Create several classes (Circle, Rectangle, Triangle) that implement this interface.
Additional Resources
💡 Found a typo or mistake? Click "Edit this page" to suggest a correction. Your feedback is greatly appreciated!