Skip to main content

C# Multiple Interfaces

Introduction

One of the most powerful features of C# interfaces is the ability for a class to implement multiple interfaces simultaneously. Unlike class inheritance, where a class can only inherit from a single base class (C# does not support multiple inheritance for classes), a class can implement any number of interfaces. This flexibility allows developers to design more versatile and extensible applications.

In this tutorial, we'll explore how to implement multiple interfaces in C#, understand the benefits and challenges, and see real-world examples of when this capability becomes particularly valuable.

Implementing Multiple Interfaces: The Basics

When a class implements multiple interfaces, it must provide implementations for all the members defined in each interface. The syntax is straightforward - simply list all the interfaces separated by commas after the class name.

csharp
public class MyClass : Interface1, Interface2, Interface3
{
// Implementation of all members from Interface1, Interface2, and Interface3
}

Let's look at a basic example:

csharp
public interface IDrawable
{
void Draw();
}

public interface IResizable
{
void Resize(int width, int height);
}

public class Rectangle : IDrawable, IResizable
{
public int Width { get; set; }
public int Height { get; set; }

public void Draw()
{
Console.WriteLine($"Drawing a rectangle of width {Width} and height {Height}");
}

public void Resize(int width, int height)
{
Width = width;
Height = height;
Console.WriteLine($"Rectangle resized to width {Width} and height {Height}");
}
}

In this example, the Rectangle class implements both the IDrawable and IResizable interfaces, meaning it must provide implementations for the Draw() method from IDrawable and the Resize() method from IResizable.

Why Use Multiple Interfaces?

Implementing multiple interfaces offers several advantages:

  1. Modular Design: Each interface can represent a specific capability or behavior.

  2. Flexibility: A class can "pick and choose" which behaviors it wants to implement.

  3. Contract Compliance: A class can satisfy multiple contracts, allowing it to be used in different contexts.

  4. Avoiding Diamond Problem: Unlike multiple inheritance in some languages, interfaces avoid the "diamond problem" since they don't contain implementation details.

Handling Interface Method Conflicts

When implementing multiple interfaces, you might encounter situations where different interfaces declare methods with the same signature. This creates an ambiguity that needs to be resolved. C# provides a technique called explicit interface implementation to address this issue.

csharp
public interface ILogger
{
void Log(string message);
}

public interface IAuditor
{
void Log(string message); // Same signature as ILogger.Log
}

public class SystemMonitor : ILogger, IAuditor
{
// Explicit implementation for ILogger
void ILogger.Log(string message)
{
Console.WriteLine($"Logger: {message}");
}

// Explicit implementation for IAuditor
void IAuditor.Log(string message)
{
Console.WriteLine($"Auditor: {message}");
}

// You can also provide a general implementation if desired
public void Log(string message)
{
Console.WriteLine($"General Log: {message}");
}
}

When using explicit implementation, you can call the specific interface method by casting the object to the appropriate interface:

csharp
SystemMonitor monitor = new SystemMonitor();

monitor.Log("This calls the general implementation");

((ILogger)monitor).Log("This calls the ILogger implementation");
((IAuditor)monitor).Log("This calls the IAuditor implementation");

Output:

General Log: This calls the general implementation
Logger: This calls the ILogger implementation
Auditor: This calls the IAuditor implementation

Implementing Interface Hierarchies

Interfaces can inherit from other interfaces, forming an interface hierarchy. When a class implements an interface that inherits from another interface, it must implement all members from both interfaces.

csharp
public interface IBase
{
void BaseMethod();
}

public interface IDerived : IBase
{
void DerivedMethod();
}

public class MyImplementation : IDerived
{
public void BaseMethod()
{
Console.WriteLine("Implementing BaseMethod from IBase");
}

public void DerivedMethod()
{
Console.WriteLine("Implementing DerivedMethod from IDerived");
}
}

In this example, MyImplementation must implement both BaseMethod() and DerivedMethod() because it implements IDerived, which inherits from IBase.

Real-World Example: Building a Media Player

Let's look at a more practical example of how multiple interfaces can be used to build a flexible media player application:

csharp
public interface IPlayable
{
void Play();
void Pause();
void Stop();
}

public interface IRecordable
{
void StartRecording();
void StopRecording();
}

public interface IEqualizeable
{
void SetEqualizer(int[] bands);
}

public interface IStreamable
{
void Stream(string url);
}

public class AudioPlayer : IPlayable, IEqualizeable
{
public void Play()
{
Console.WriteLine("Playing audio...");
}

public void Pause()
{
Console.WriteLine("Audio paused");
}

public void Stop()
{
Console.WriteLine("Audio stopped");
}

public void SetEqualizer(int[] bands)
{
Console.WriteLine("Setting audio equalizer bands...");
}
}

public class VideoPlayer : IPlayable, IEqualizeable, IStreamable
{
public void Play()
{
Console.WriteLine("Playing video...");
}

public void Pause()
{
Console.WriteLine("Video paused");
}

public void Stop()
{
Console.WriteLine("Video stopped");
}

public void SetEqualizer(int[] bands)
{
Console.WriteLine("Setting video equalizer bands...");
}

public void Stream(string url)
{
Console.WriteLine($"Streaming video from {url}...");
}
}

public class VoiceRecorder : IPlayable, IRecordable
{
public void Play()
{
Console.WriteLine("Playing recording...");
}

public void Pause()
{
Console.WriteLine("Recording playback paused");
}

public void Stop()
{
Console.WriteLine("Recording playback stopped");
}

public void StartRecording()
{
Console.WriteLine("Starting voice recording...");
}

public void StopRecording()
{
Console.WriteLine("Voice recording stopped");
}
}

Now we can interact with these classes through their interfaces:

csharp
public static void Main()
{
// Create a list of playable items
List<IPlayable> mediaItems = new List<IPlayable>
{
new AudioPlayer(),
new VideoPlayer(),
new VoiceRecorder()
};

// Play all items
foreach (var item in mediaItems)
{
item.Play();
item.Pause();
item.Stop();
Console.WriteLine();
}

// Work with equalizeable items
List<IEqualizeable> equItems = new List<IEqualizeable>
{
new AudioPlayer(),
new VideoPlayer()
};

// Set equalizer for all equalizeable items
foreach (var item in equItems)
{
item.SetEqualizer(new int[] { 0, 3, 6, 9, 12 });
}

// Stream a video
VideoPlayer videoPlayer = new VideoPlayer();
videoPlayer.Stream("http://example.com/video.mp4");

// Use voice recorder
VoiceRecorder recorder = new VoiceRecorder();
recorder.StartRecording();
recorder.StopRecording();
recorder.Play();
}

Output:

Playing audio...
Audio paused
Audio stopped

Playing video...
Video paused
Video stopped

Playing recording...
Recording playback paused
Recording playback stopped

Setting audio equalizer bands...
Setting video equalizer bands...
Streaming video from http://example.com/video.mp4...
Starting voice recording...
Voice recording stopped
Playing recording...

This example shows how multiple interfaces allow you to:

  • Group objects by capability (all IPlayable items)
  • Add specific functionality only where needed
  • Use polymorphism effectively
  • Create specialized combinations of behaviors

Common Scenarios for Multiple Interfaces

Here are some common scenarios where implementing multiple interfaces proves beneficial:

  1. UI Components: A button class might implement both IClickable and IDrawable.

  2. Data Access: A repository class might implement IQueryable, IEnumerable, and IDisposable.

  3. Service Classes: A service might implement both INotifyPropertyChanged and IDataErrorInfo.

  4. Plugin Systems: Plugins often need to implement several interfaces for different aspects of functionality.

  5. Testing: Implementing interfaces like IMockable alongside regular interfaces can help with testing.

Best Practices for Using Multiple Interfaces

When implementing multiple interfaces, keep these practices in mind:

  1. Interface Segregation: Keep interfaces small and focused (following the Interface Segregation Principle from SOLID).

  2. Consistent Naming: Use consistent naming conventions for interface methods to avoid confusion.

  3. Consider Composition: Sometimes composition is better than implementing many interfaces directly.

  4. Document Interface Purpose: Make it clear what each interface is for.

  5. Handle Conflicts Carefully: Use explicit implementation when needed to avoid ambiguity.

  6. Don't Overdo It: If a class implements too many interfaces, it might be violating the Single Responsibility Principle.

Summary

Implementing multiple interfaces in C# is a powerful technique that allows classes to fulfill multiple contracts without the complications of multiple inheritance. This approach provides flexibility, modularity, and better code organization. By understanding when and how to implement multiple interfaces, you can create more maintainable and extensible code.

Remember these key points:

  • A class can implement any number of interfaces
  • Each interface represents a distinct capability or behavior
  • Explicit implementation resolves method conflicts
  • Interface hierarchies must be fully implemented
  • Multiple interfaces facilitate powerful polymorphism

Exercises

  1. Create a document processing system with interfaces like IPrintable, ISaveable, IConvertible, and IEncryptable. Implement these interfaces in document classes like TextDocument, SpreadsheetDocument, and PresentationDocument.

  2. Design a game system where game entities implement combinations of interfaces like IMovable, IDamageable, IAttacker, and ICollectable.

  3. Create a notification system that uses interfaces like IEmailNotification, ISMSNotification, and IPushNotification, then implement a NotificationService that can process any notification type.

Additional Resources



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