When and Why to Use Types Instead of Interfaces in C#

Nile BitsNile Bits
6 min read

https://www.nilebits.com/blog/2025/05/types-instead-of-interfaces-csharp/

C# developers have long relied on interfaces to build abstractions, promote loose coupling, and enable testability. While interfaces offer valuable capabilities in the right contexts, they are often overused—or misused—when concrete types (classes or abstract classes) could provide a more expressive, maintainable, and robust solution.

In this article, we'll explore why you should prefer types over interfaces in C#, when to make that decision, and how it affects performance, code readability, testability, and maintainability. We'll also examine real-world scenarios where types outshine interfaces, and provide best practices to guide your architecture decisions.


What Are Interfaces and Types in C#?

Before we dive into why types are often the better choice, let’s clarify what we mean by interfaces and types in C#.

Interfaces

An interface defines a contract that classes can implement:

public interface IAnimal
{
    void Speak();
}

Any class implementing IAnimal must provide the Speak() method.

Types

Types typically refer to classes or abstract classes in C#. They encapsulate data and behavior:

public abstract class Animal
{
    public abstract void Speak();
    public void Eat()
    {
        Console.WriteLine("Eating...");
    }
}

The Overuse of Interfaces

In modern C# development—especially in enterprise or layered architectures—it has become common to define interfaces for everything, even when there’s only a single implementation:

public interface IUserRepository
{
    User GetUserById(int id);
}

public class UserRepository : IUserRepository
{
    public User GetUserById(int id) { ... }
}

While this approach enables flexibility, it can quickly become boilerplate-heavy, hard to read, and harder to maintain, especially when the abstraction is unnecessary.


The Case for Using Types Instead

Let’s look at several compelling reasons to prefer types (abstract classes or concrete classes) over interfaces in many common scenarios.


1. Default Implementation and Code Reuse

Interfaces cannot include implementation prior to C# 8. Even with default interface members introduced in C# 8, abstract classes are far better at encapsulating shared logic.

Example: Abstract Class with Default Implementation

public abstract class Vehicle
{
    public abstract void StartEngine();

    public void StopEngine()
    {
        Console.WriteLine("Stopping engine...");
    }
}

You can now avoid repeating the StopEngine logic in every class that inherits from Vehicle.

Why It's Better Than Interface

An interface cannot define the StopEngine() logic without C# 8, and even then, default implementations break some design principles (e.g., Single Responsibility) and can lead to brittle APIs.

For more information: Microsoft Docs – Abstract Classes


2. Better Refactorability and Discoverability

Types are easier to refactor

Modern IDEs like Visual Studio or Rider provide better tooling for navigating and refactoring class hierarchies than interface implementations.

When using a base class, developers can:

  • Navigate through inheritance trees more easily

  • Locate default logic in one place

  • Use "Find All References" more effectively

Example

public abstract class FileProcessor
{
    public void Process()
    {
        OpenFile();
        ReadData();
        CloseFile();
    }

    protected abstract void OpenFile();
    protected abstract void ReadData();
    protected virtual void CloseFile()
    {
        Console.WriteLine("Closing file...");
    }
}

Refactoring this FileProcessor logic is centralized and much simpler than juggling multiple interfaces with scattered implementations.


3. Reducing Cognitive Load

When you open a file containing:

public class EmailSender : IEmailSender

You’re forced to switch files to see what IEmailSender does. This adds friction. In contrast, using types with concrete base classes makes the behavior easier to trace.

Moreover, if there's only one implementation of an interface, the interface becomes an unnecessary indirection.


4. Performance Considerations

Interfaces introduce virtual dispatch, and in certain tight loops or high-performance code (such as game engines, real-time analytics, or finance apps), this can cause performance degradation.

Using concrete types can help the compiler perform better inlining and JIT optimizations.


5. Easier Unit Testing with Abstract Classes

Contrary to popular belief, you don’t need interfaces to write testable code.

Example: Abstract Class with Virtual Methods

public abstract class NotificationService
{
    public virtual void Send(string message)
    {
        Console.WriteLine("Sending notification: " + message);
    }
}

In your unit test, you can override Send():

public class MockNotificationService : NotificationService
{
    public override void Send(string message)
    {
        // Capture or log for test
    }
}

6. Rich Metadata and Behavior Encapsulation

Types can contain:

  • Constructors

  • Fields

  • Events

  • Static methods

  • Protected/internal logic

Interfaces can’t do this.

Example: Dependency Injection Simplified

Instead of:

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

public class Logger : ILogger
{
    public void Log(string message) { ... }
}

Use:

public class Logger
{
    public void Log(string message) { ... }
}

Only introduce an interface if you need multiple implementations, e.g., a FileLogger vs DatabaseLogger.


7. Interfaces Are Overkill for Internal Use

Interfaces are excellent for public APIs or SDKs, where backward compatibility and multiple implementations matter.

But if you're designing internal libraries, and your classes aren't going to be extended by third-party consumers, interfaces can become unnecessary noise.

Use internal types or abstract classes instead.


When Should You Still Use Interfaces?

While we've made a strong case for preferring types, interfaces remain crucial in some scenarios:

  1. Multiple inheritance of behavior (C# does not allow multiple base classes)

  2. Dependency injection abstraction

  3. Plugin architectures

  4. Mocking frameworks that require interfaces (e.g., Moq)

  5. Cross-cutting concerns like logging, caching

Use interfaces when polymorphism and loose coupling are essential.


Guidelines: When to Use Types vs Interfaces

CriteriaPrefer Abstract Class (Type)Prefer Interface
Single implementation
Need default logic❌ (unless C# 8+)
Refactoring & discoverability
Multiple inheritance required
Public extensible API
Performance-sensitive context

Real-World Scenario: Replacing Interfaces with Abstract Classes

Before:

public interface IDataExporter
{
    void Export();
}

public class CsvExporter : IDataExporter
{
    public void Export()
    {
        // Export logic
    }
}

After:

public abstract class DataExporter
{
    public abstract void Export();

    public void LogExport()
    {
        Console.WriteLine("Export complete.");
    }
}

public class CsvExporter : DataExporter
{
    public override void Export()
    {
        // Export logic
        LogExport();
    }
}

Now you’ve consolidated shared behavior and improved maintainability.


Final Thoughts

Interfaces are powerful and important—but overusing them results in bloated code, unnecessary abstractions, and reduced developer productivity.

Modern C# development, especially when using tools like ASP.NET Core, benefits from a more balanced approach: use interfaces for boundaries, plugins, and extensibility—but favor abstract classes or concrete types when designing internal or straightforward systems.

https://www.nilebits.com/blog/2025/05/types-instead-of-interfaces-csharp/

0
Subscribe to my newsletter

Read articles from Nile Bits directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Nile Bits
Nile Bits

Nile Bits is a software company, focusing on outsourcing software development and custom software solutions. Our outsourcing software services and solutions are designed with a focus on secure, scalable, expandable and reliable business systems. Via our low cost, high quality and reliable outsourcing software services, we provide to our clients value for money and therefore client satisfaction.