Skip to main content

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#:

csharp
// 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:

csharp
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:

csharp
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:

InterfaceAbstract Class
Can only declare functionalityCan declare and implement functionality
No implementation codeCan have implementation code for some methods
A class can implement multiple interfacesA class can inherit from only one abstract class
No fieldsCan have fields
No constructorsCan have constructors

Implementing Multiple Interfaces

A powerful feature of interfaces is that a class can implement multiple interfaces:

csharp
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:

csharp
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:

csharp
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:

csharp
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:

csharp
// 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:

csharp
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

  1. Keep interfaces focused: Follow the Interface Segregation Principle (ISP) and create small, specific interfaces rather than large, general-purpose ones.

  2. Name interfaces clearly: Use descriptive names that reflect the behavior they define (e.g., IComparable, IEnumerable, IDisposable).

  3. Use interfaces for abstraction: Interfaces are great for hiding implementation details and providing a stable API.

  4. Design for testability: Interfaces make it easier to create mock implementations for testing.

  5. 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

  1. Create an interface called ILogger with methods for logging information, warnings, and errors. Then implement this interface in two different classes: ConsoleLogger and FileLogger.

  2. Design a simple application that uses the Strategy Pattern with interfaces. Create an interface called IPaymentMethod with different implementations like CreditCardPayment, PayPalPayment, and BitcoinPayment.

  3. Implement an interface called IShape with methods to calculate area and perimeter. Create several classes (Circle, Rectangle, Triangle) that implement this interface.

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)