C# Interfaces — Defning Contracts, Implementation, and Default Interface Methods
In this tutorial, you will learn about C# Interfaces. We cover key concepts, practical examples, and best practices to help you master this topic.
C# interfaces define contracts that classes and structs can implement, enabling polymorphic behavior across unrelated types and supporting default method implementations since C# 8.
What You'll Learn
You will master interfaces in C#: defining interface contracts, implementing interfaces in classes and structs, using explicit interface implementation for disambiguation, leveraging default interface methods introduced in C# 8, and understanding how interfaces enable .NET features like dependency injection and mocking.
Why It Matters
Interfaces are fundamental to modern C# development. They enable loose coupling, testability through mocking, dependency injection, and the ability to write code that works with any type that satisfies a contract. Frameworks like ASP.NET Core are built entirely around interfaces. Without interfaces, you cannot effectively unit test or swap implementations.
Real-World Use
ASP.NET Core uses IServiceCollection, ILogger<T>, IHostedService for extensibility. Entity Framework Core uses IDbContextFactory<T>. LINQ integrates with IEnumerable<T>. Repository patterns use interfaces for testability. Logging frameworks abstract behind ILogger. HttpClient uses HttpMessageHandler.
Learning Path
graph LR
A["13: Polymorphism"] --> B["14: Interfaces"]
B --> C["15: Records"]
C --> D["16: Structs"]
D --> E["17: Strings"]
style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
style D fill:#4a90d9,stroke:#2c5f8a,color:#fff
style E fill:#4a90d9,stroke:#2c5f8a,color:#fff
Defining and Implementing Interfaces
public interface ILogger
{
void Log(string message);
void LogError(string message, Exception? ex = null);
}
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($"[INFO] {message}");
}
public void LogError(string message, Exception? ex = null)
{
Console.WriteLine($"[ERROR] {message}");
if (ex != null) Console.WriteLine($" Exception: {ex.Message}");
}
}
public class FileLogger : ILogger
{
private readonly string _path;
public FileLogger(string path) => _path = path;
public void Log(string message)
{
File.AppendAllText(_path, $"[INFO] {DateTime.UtcNow}: {message}\n");
}
public void LogError(string message, Exception? ex = null)
{
File.AppendAllText(_path, $"[ERROR] {DateTime.UtcNow}: {message}\n");
if (ex != null) File.AppendAllText(_path, $" Exception: {ex.Message}\n");
}
}
// Polymorphic usage
ILogger logger = new ConsoleLogger();
logger.Log("Application started");
logger = new FileLogger("app.log");
logger.Log("Logging to file");
Interface Members
Interfaces can declare:
public interface IRepository<T>
{
// Method
T GetById(int id);
// Property
bool IsReadOnly { get; }
// Indexer
T this[int id] { get; }
// Event
event EventHandler<T>? ItemAdded;
// Default method (C# 8+)
void LogAccess(string operation)
{
Console.WriteLine($"Access: {operation} on {typeof(T).Name}");
}
}
Explicit Interface Implementation
Used when implementing the same method signature from multiple interfaces:
public interface IWriter
{
void Write(string text);
}
public interface IFormatter
{
void Write(string text);
}
public class Document : IWriter, IFormatter
{
private string _content = "";
// Explicit implementation for IWriter
void IWriter.Write(string text)
{
_content += text;
Console.WriteLine($"Writer: {text}");
}
// Explicit implementation for IFormatter
void IFormatter.Write(string text)
{
_content += $"<formatted>{text}</formatted>";
Console.WriteLine($"Formatter: <formatted>{text}</formatted>");
}
// Public method that delegates
public void Append(string text) => ((IWriter)this).Write(text);
public string Content => _content;
}
var doc = new Document();
// doc.Write("test"); // Error: ambiguous
((IWriter)doc).Write("Hello"); // Writer: Hello
((IFormatter)doc).Write("World"); // Formatter: <formatted>World</formatted>
doc.Append("Direct"); // Writer: Direct
Console.WriteLine($"Content: {doc.Content}");
Default Interface Methods (C# 8)
Interfaces can now provide default implementations:
public interface IEntity
{
int Id { get; }
DateTime CreatedAt { get; }
// Default implementation
bool IsNew => Id == 0;
string GetDescription() => $"Entity #{Id} (created: {CreatedAt:yyyy-MM-dd})";
}
public class User : IEntity
{
public int Id { get; init; }
public DateTime CreatedAt { get; init; } = DateTime.UtcNow;
public string Name { get; set; } = "";
// Optional: override default
public string GetDescription() => $"User: {Name} (ID: {Id})";
}
public class Product : IEntity
{
public int Id { get; init; }
public DateTime CreatedAt { get; init; } = DateTime.UtcNow;
public string ProductName { get; set; } = "";
// Uses default GetDescription()
}
var user = new User { Id = 1, Name = "Alice" };
var product = new Product { Id = 100, ProductName = "Widget" };
Console.WriteLine(user.GetDescription()); // User: Alice (ID: 1)
Console.WriteLine(product.GetDescription()); // Entity #100 (created: 2026-06-28)
Console.WriteLine($"User is new: {user.IsNew}"); // False
Interface Inheritance
public interface IReadOnlyRepository<T>
{
T? GetById(int id);
IEnumerable<T> GetAll();
}
public interface IRepository<T> : IReadOnlyRepository<T>
{
void Add(T entity);
void Update(T entity);
void Delete(int id);
}
public class Repository<T> : IRepository<T>
{
private readonly Dictionary<int, T> _data = new();
private int _nextId = 1;
public T? GetById(int id) =>
_data.TryGetValue(id, out var item) ? item : default;
public IEnumerable<T> GetAll() => _data.Values;
public void Add(T entity)
{
// Add implementation
}
public void Update(T entity) { }
public void Delete(int id) => _data.Remove(id);
}
Multiple Interface Inheritance
A class can implement multiple interfaces:
public interface IPrintable
{
void Print();
}
public interface ISerializable
{
string Serialize();
}
public interface IExportable : IPrintable, ISerializable
{
void Export(string filePath);
}
public class Report : IExportable
{
public string Title { get; set; }
public string Content { get; set; }
public void Print()
{
Console.WriteLine($"=== {Title} ===");
Console.WriteLine(Content);
}
public string Serialize()
{
return $"{{\"title\":\"{Title}\",\"content\":\"{Content}\"}}";
}
public void Export(string filePath)
{
File.WriteAllText(filePath, Serialize());
Console.WriteLine($"Exported to {filePath}");
}
}
Interfaces vs Abstract Classes
// Interface: contract only
public interface IMovable
{
void Move(int x, int y);
double Speed { get; }
}
// Abstract class: shared implementation
public abstract class Vehicle
{
public string Name { get; set; }
public abstract void Start();
public virtual void Stop() => Console.WriteLine("Vehicle stopped");
}
Choose interfaces when:
- Defining a contract across unrelated types
- Supporting multiple inheritance
- You only need behavior, no shared state
Choose abstract classes when:
- Providing shared implementation
- Having shared fields or constructors
- The types are related
Common Mistakes
Mistake 1: Defining Interfaces That Are Too Large
Interfaces should follow the Interface Segregation Principle. Large interfaces force implementing classes to provide methods they do not need. Split into smaller, focused interfaces.
Mistake 2: Using Interfaces When a Delegate Suffices
For single-method contracts, consider Action, Func, or custom delegates instead of defining an interface.
Mistake 3: Not Using Explicit Implementation When Required
When a class implements two interfaces with the same member, use explicit implementation to disambiguate. Otherwise, calling code cannot differentiate.
Mistake 4: Adding Members to Published Interfaces Without Default Implementations
Adding a method to a public interface breaks all existing implementations. Default interface methods (C# 8+) solve this but should be used carefully.
Mistake 5: Confusing Interface Inheritance with Class Inheritance
Interface inheritance is about extending contracts, not inheriting implementation. A class that implements a derived interface must implement all members from all interfaces in the hierarchy.
Mistake 6: Forgetting That Interfaces Cannot Have State
Interfaces cannot contain fields, auto-properties with backing fields, or constructors. They can only define method signatures, properties (without body), events, and indexers.
Practice Questions
- What is the difference between an interface and an abstract class?
- When would you use explicit interface implementation?
- How do default interface methods affect existing implementations?
- Why would you define an interface instead of using a concrete class?
- Write an
IDataSource<T>interface with methods for CRUD operations and a default method for logging.
Challenge
Design an IPaymentGateway interface with methods for ProcessPayment, Refund, and GetTransactionStatus. Implement it with StripeGateway and PayPalGateway classes. Add a default LogTransaction method to the interface.
FAQ
Mini Project
Create a plugin-based document processing system using interfaces:
public interface IDocumentProcessor
{
string Name { get; }
bool CanProcess(string fileName);
string Process(string content);
}
public class MarkdownProcessor : IDocumentProcessor
{
public string Name => "Markdown Processor";
public bool CanProcess(string fileName) =>
fileName.EndsWith(".md", StringComparison.OrdinalIgnoreCase);
public string Process(string content)
{
// Simple Markdown-to-HTML conversion
var html = content
.Replace("**", "<strong>").Replace("**", "</strong>")
.Replace("*", "<em>").Replace("*", "</em>");
return $"<h1>Markdown Output</h1>\n{html}";
}
}
public class CsvProcessor : IDocumentProcessor
{
public string Name => "CSV Processor";
public bool CanProcess(string fileName) =>
fileName.EndsWith(".csv", StringComparison.OrdinalIgnoreCase);
public string Process(string content)
{
var lines = content.Split('\n');
var headers = lines[0].Split(',');
var output = "<table>\n <tr>";
foreach (var h in headers) output += $"<th>{h.Trim()}</th>";
output += "</tr>\n";
for (int i = 1; i < lines.Length; i++)
{
if (string.IsNullOrWhiteSpace(lines[i])) continue;
output += " <tr>";
foreach (var cell in lines[i].Split(','))
output += $"<td>{cell.Trim()}</td>";
output += "</tr>\n";
}
output += "</table>";
return output;
}
}
public class DocumentPipeline
{
private readonly List<IDocumentProcessor> _processors = new();
public void RegisterProcessor(IDocumentProcessor processor)
{
_processors.Add(processor);
}
public string ProcessDocument(string fileName, string content)
{
var processor = _processors.FirstOrDefault(p => p.CanProcess(fileName));
if (processor == null)
throw new NotSupportedException($"No processor found for {fileName}");
Console.WriteLine($"Processing with: {processor.Name}");
return processor.Process(content);
}
}
var pipeline = new DocumentPipeline();
pipeline.RegisterProcessor(new MarkdownProcessor());
pipeline.RegisterProcessor(new CsvProcessor());
var mdResult = pipeline.ProcessDocument("readme.md", "# Hello\nThis is **bold** text");
Console.WriteLine(mdResult);
Console.WriteLine();
var csvResult = pipeline.ProcessDocument("data.csv", "Name,Age\nAlice,30\nBob,25");
Console.WriteLine(csvResult);
Expected output:
Processing with: Markdown Processor
<h1>Markdown Output</h1>
<h1>Hello</h1>
This is <strong>bold</strong> text
Processing with: CSV Processor
<table>
<tr><th>Name</th><th>Age</th></tr>
<tr><td>Alice</td><td>30</td></tr>
<tr><td>Bob</td><td>25</td></tr>
</table>
What's Next
You have mastered interfaces in C#. The next lesson covers records: record class, record struct, positional syntax, and with expressions for immutable Data Modeling.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro