DEV Community

Cover image for Abstract vs Interface: Deep dive.
Serhii Korol
Serhii Korol

Posted on

Abstract vs Interface: Deep dive.

Most developers are familiar with the differences between abstract classes and interfaces—at least, I hope so!

At a high level, the key differences are:

Abstract Classes:
  • Cannot be instantiated
  • Must be inherited by other classes
  • Can contain abstract and non-abstract methods
  • Can include fields and properties
  • Do not support multiple inheritance
  • Can have constructors
Interfaces:
  • Support multiple inheritance
  • Can have only default implementations (from C# 8.0 onward)
  • Do not have fields or properties (except static or default implementations)
  • Cannot have constructors
  • Use a separate table (ITable) for method dispatch

While these differences are visible on the surface, the real distinction lies in how the Common Language Runtime (CLR) handles abstract classes and interfaces under the hood. Let's dive deeper into these internal differences.

A Practical Example

Consider the following code snippet demonstrating an abstract class and an interface:

public abstract class SomeAbstractClass
{
    public abstract void Print();
}

public interface ISomeInterface
{
    public void Print();
}

public class ConcreteClass1 : SomeAbstractClass
{
    public override void Print()
    {
        Console.WriteLine("This is concrete class 1");
    }
}

public class ConcreteClass2 : ISomeInterface
{
    public void Print()
    {
        Console.WriteLine("This is concrete class 2");
    }
}

var class1 = new ConcreteClass1();
class1.Print();
var class2 = new ConcreteClass2();
class2.Print();

Enter fullscreen mode Exit fullscreen mode

How the CLR Handles Abstract Classes

When we call the Print() method on an abstract class, the CLR uses the callvirt instruction. This instruction looks up the virtual method table (vtable) and calls the correct implementation:

    IL_0006: ldloc.0      // class1
    IL_0007: callvirt     instance void AbstractVsInterface.SomeAbstractClass::Print()
    IL_000c: nop
Enter fullscreen mode Exit fullscreen mode

In an abstract class, the slot for the abstract method is created in the vtable, but the method's address is initially set to null. When a derived class overrides the method, the actual implementation is stored in the corresponding vtable entry.

How the CLR Handles Interfaces

When calling a method through an interface, the CLR also uses the callvirt instruction, but there is a crucial difference. Instead of the vtable, the method resolution occurs through an interface table (ITable).

    IL_0013: ldloc.1      // class2
    IL_0014: callvirt     instance void AbstractVsInterface.ConcreteClass2::Print()
    IL_0019: nop
Enter fullscreen mode Exit fullscreen mode

The CLR creates a separate ITable for each interface a class implements. This table maps interface methods to their corresponding implementations in the derived class.

Method Implementation in Derived Classes

In the derived class of an abstract class, the method is marked as virtual, and its implementation is stored in the vtable:

.method public hidebysig virtual instance void
    Print() cil managed
  {
    .maxstack 8

    // [5 37 - 5 82]
    IL_0000: ldstr        "This is concrete class 1"
    IL_0005: call         void [System.Console]System.Console::WriteLine(string)
    IL_000a: nop
    IL_000b: ret

  } // end of method ConcreteClass1::Print
Enter fullscreen mode Exit fullscreen mode

For interfaces, the derived class uses the newslot keyword. This indicates that a new entry is created in the ITable for this implementation:

.method public final hidebysig virtual newslot instance void
    Print() cil managed
  {
    .maxstack 8

    // [10 28 - 10 73]
    IL_0000: ldstr        "This is concrete class 2"
    IL_0005: call         void [System.Console]System.Console::WriteLine(string)
    IL_000a: nop
    IL_000b: ret

  } // end of method ConcreteClass2::Print
Enter fullscreen mode Exit fullscreen mode

Object Inheritance and Method Resolution

Another key difference is how the CLR handles inheritance. Abstract classes are directly inherited from the System.Object, while interfaces do not. Here is an excerpt from the metadata of an abstract class:

.class public abstract auto ansi beforefieldinit
  AbstractVsInterface.SomeAbstractClass
    extends [System.Runtime]System.Object
{

  .method public hidebysig virtual newslot abstract instance void
    Print() cil managed
  {
    // Can't find a body
  } // end of method SomeAbstractClass::Print

  .method family hidebysig specialname rtspecialname instance void
    .ctor() cil managed
Enter fullscreen mode Exit fullscreen mode

abstract

In contrast, the metadata for an interface shows that it does not extend the System.Object directly:

.class interface public abstract auto ansi beforefieldinit
  AbstractVsInterface.ISomeInterface
{

  .method public hidebysig virtual newslot abstract instance void
    Print() cil managed
  {
    // Can't find a body
  } // end of method ISomeInterface::Print
} // end of class AbstractVsInterface.ISomeInterface
Enter fullscreen mode Exit fullscreen mode

interface

Performance Considerations

From a performance perspective, abstract classes are typically faster than interfaces due to the way method resolution is handled:

  • Abstract Classes: Since there is only one vtable, method lookup is more efficient.
  • Interfaces: The CLR needs to:
    1. Identify the correct interface.
    2. Locate the appropriate ITable.
    3. Perform the method lookup.

When a class implements multiple interfaces, the CLR must search through multiple interface tables, making the resolution process slower compared to abstract classes.

Summary

  • Abstract Classes use a vtable for method dispatch, while Interfaces use an ITable.
  • Abstract methods must be overridden, while interface methods must be implemented.
  • Abstract classes inherit from System.Object, while interfaces do not.
  • Performance: Abstract classes are generally faster due to more straightforward method lookup.

Understanding these low-level differences can help you make better design decisions and optimize your applications more effectively.

I hope this article was helpful—happy coding!

Buy Me A Beer

Top comments (0)