CodeGym /Courses /C# SELF /Comparing Interfaces and Abstract Classes

Comparing Interfaces and Abstract Classes

C# SELF
Level 24 , Lesson 2
Available

1. Quick Recap of Classic Differences

If someone says, "An interface is just a set of signatures," ask them, "Which C# version are you coding in?" Starting from C# 8 and up, interfaces got way more powerful. It's time to compare them to abstract classes—not just by the classic properties, but with all the new .NET platform goodies.

If you go back a few years—to C# 7—things were simple. An abstract class could define fields and partially implemented methods, while an interface could only have signatures (methods, properties, events, indexers).
Inheritance from an abstract class is about the "is-a" relationship (Is-a), while interfaces are about multiple inheritance of behavior ("can-do", can-do).

Characteristic Abstract Class Interface (before C# 8)
Relationship is-a can-do
Inheritance Only one Multiple
Fields Can have Can't have
Method implementation Can have Can't have
Constructors Can have Can't have
Access modifiers Different (public, protected, ...) Only implicitly public

As you can see, abstract classes used to be the "big brothers" of interfaces—more powerful and flexible. But things change!

2. Interfaces with Default Implementation

With C# 8 (and especially in C# 14 and .NET 9), interfaces got a new superpower—methods with default implementation, called "Default Interface Methods" (DIM).

What does it look like?


public interface IAnimal
{
    void SayHello();

    // Method with default implementation!
    void Walk()
    {
        Console.WriteLine("I'm walking...");
    }
}

Wow! Suddenly, an interface can have a method implementation. And not just one, but as many as you want. But there's a catch: these methods must be explicitly declared with a body, and everything else (fields, private methods, constructors) is still not allowed.

3. Modern Interface Features

New features every modern .NET dev should know:

  • Methods with default implementation.
  • Private methods inside the interface (for helper purposes only, available only to other methods in the same interface).
  • Static methods (since C# 8).
  • Properties with default implementation.
  • Static fields (since C# 14—"static interface members").
  • Abstract static members ("abstract static members"—yep, now an interface can require certain static methods in the implementation!).

Example of a full modern interface:


public interface ILogger
{
    static int LoggerCount { get; set; } // C# 14

    void Log(string message); // Signature (contract)

    // Default implementation
    void LogWarning(string warning)
    {
        Log("[WARNING]: " + warning);
    }
    
    // Private helper method in interface (C# 8+)
    private void FormatAndLog(string level, string msg)
    {
        Log($"{level}: {msg}");
    }

    // Static method in interface (C# 8+)
    static void PrintLoggerInfo()
    {
        Console.WriteLine("ILogger interface—your best buddy!");
    }
}

Just imagine—this used to be impossible, like letting a cat work as a server guard.

4. Abstract Classes: What's New?

Abstract classes... how to put it... haven't really evolved much in the last decade. They can still have:

  • Fields (including private, protected, and static).
  • Implemented and abstract methods.
  • Constructors (yep, you can make abstract classes with initialization logic).
  • Properties, events, indexers.
  • Static and instance members.

Example of an abstract class:


public abstract class Animal
{
    public string Name { get; set; }

    public abstract void Speak();

    public virtual void Walk()
    {
        Console.WriteLine($"{Name} is walking on paws!");
    }

    protected void Eat()
    {
        Console.WriteLine($"{Name} is eating food.");
    }
}

An abstract class is still a great place to keep shared logic, state, and behavior for a class hierarchy.

5. Modern Comparison: Table with New Features

Characteristic Abstract Class Interface (C# 14+, .NET 9)
Relationship is-a can-do
Inheritance Only one Multiple
Fields Yes, any Only static* (C# 14+)
Constructors Yes No
Method implementation Yes (virtual/abstract) Yes (default, static, abstract static)
Properties with implementation Yes Yes (default implementation)
Private members Yes Yes (methods only, C# 8+)
Static members Yes Yes (C# 8+, with limitations)
Static fields Yes Yes * (C# 14+)
Access modifiers Any By default public or private

* — In interfaces, static fields are usually used in special cases, and this is a super fresh language feature.

6. Where to Use an Interface and Where an Abstract Class: Modern Tips

Interfaces (now with default implementation too) are a tool for creating contracts between components. Their key feature: multiplicity. Your class can implement like ten different interfaces, making it a universal soldier.

Abstract class is still your pick if:

  • You need shared state (fields), logic, and behavior that other classes inherit.
  • You want standard but overridable logic (use virtual).
  • You want to centralize initialization via a constructor.

In real projects, you often see this pattern: "pure contracts" are defined in interfaces, and if you need shared code or infrastructure for subclasses—you make an abstract base class.


.                     ┌────────────────────────┐
                      │      Interface         │
                      │  (contract: what it can do) │
                      └─────────┬──────────────┘
                                │
                 ┌──────────────┼──────────────┐
                 │              │              │
           Implementation 1   Implementation 2 ...  Implementation N
             MyLogger     CloudLogger     FileLogger
    
        (can be combined with abstract class inheritance)
Diagram: where and what to use

7. Scenarios—When Each Wins

Multiple implementation:
Say you have an interface IDrivable and an abstract class Vehicle. Now the class Car can inherit the base—Vehicle—and at the same time implement several interfaces (IDrivable, IRepairable, IInsurable). If you had an abstract class Repairable, you'd have to pick—either Vehicle or Repairable! Interfaces clearly win here.

Shared logic and state:
Say all "vehicles" have a "number" field. That should be a field in the abstract class. In an interface, fields (except static ones) aren't allowed.

API evolution:
One of the revolutionary things about Default Interface Methods—you can now evolve interfaces without breaking existing consumers.
For example, you add a new method with default implementation to the interface—everything works, all old implementations of the interface don't break! Before, this was painful (or, if you forget the pain, just impossible).

8. Practical Examples

In our learning app, logging is gradually showing up. Let's make our own ILogger interface with default implementation:


public interface ILogger
{
    void Log(string message);

    // Default implementation available to all interface implementations!
    void LogInfo(string info)
    {
        Log("[INFO] " + info);
    }

    // Static method in interface
    static void PrintHelp()
    {
        Console.WriteLine("Use ILogger for logging events");
    }
}

public class ConsoleLogger : ILogger
{
    public void Log(string message)
    {
        Console.WriteLine(message);
    }
}

// Somewhere in the code:
ILogger logger = new ConsoleLogger();
logger.LogInfo("System started!"); // works thanks to default implementation

// Calling the static method of the interface
ILogger.PrintHelp();

If we added a new method with default implementation to the interface, all existing implementations (like ConsoleLogger) would automatically get this new method—no panic and no broken code.

9. Mistakes and Gotchas: Practice and Pitfalls

You should know it's not all sunshine and rainbows. For example, if your interface has a default implementation, but the consumer accesses the object via the class type, not the interface type, the default implementation is only available through the interface.


ConsoleLogger log = new ConsoleLogger();
log.LogInfo("Hello"); // Won't compile: LogInfo not defined in the class!

ILogger log2 = log;
log2.LogInfo("Hello"); // All good!

This is kinda like a special case of explicit interface implementation. Sometimes this is handy for hiding "extra" API, sometimes—surprising for newbies.

2
Task
C# SELF, level 24, lesson 2
Locked
Implementing an interface with default method implementations
Implementing an interface with default method implementations
2
Task
C# SELF, level 24, lesson 2
Locked
Using a static method in an interface
Using a static method in an interface
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION