C# tutorials > Core C# Fundamentals > Object-Oriented Programming (OOP) > What is the difference between abstract classes and interfaces (including default interface methods)?

What is the difference between abstract classes and interfaces (including default interface methods)?

This tutorial explores the key differences between abstract classes and interfaces in C#, including the use of default interface methods. Understanding these differences is crucial for designing robust and maintainable object-oriented applications.

Core Differences: Abstract Classes vs. Interfaces

Both abstract classes and interfaces are fundamental tools for achieving abstraction in C#, but they serve different purposes and have distinct characteristics:

  • Inheritance: A class can inherit from only one abstract class, but it can implement multiple interfaces. This is a crucial difference when considering design flexibility.
  • Implementation: Abstract classes can have both abstract and non-abstract (implemented) members. Interfaces, prior to C# 8.0, could only define abstract members (methods, properties, events, indexers) without implementation.
  • Access Modifiers: Members of an abstract class can have any access modifier (public, private, protected, internal, etc.). Interface members, by default, are implicitly public.
  • Fields: Abstract classes can contain fields (member variables). Interfaces cannot contain fields (only constants before C# 8).
  • Constructors: Abstract classes can have constructors, but interfaces cannot.
  • Purpose: Abstract classes are typically used to define a common base class for a hierarchy of related classes, providing a partial implementation. Interfaces are used to define a contract that unrelated classes can implement, ensuring they adhere to a specific set of behaviors.

Abstract Class Example

This code defines an abstract class called Shape. It includes an abstract method Area() which must be implemented by any class that inherits from Shape. It also includes a virtual method Description(), which provides a default implementation that can be overridden by derived classes. Furthermore, it demonstrates the use of a property and a constructor, features not available in interfaces (before default interface methods).

public abstract class Shape
{
    public abstract double Area();

    public virtual string Description() 
    {
        return "This is a shape.";
    }

    public string Color { get; set; }

    public Shape(string color)
    {
        Color = color;
    }
}

Interface Example

This code defines an interface called IPrintable. It declares a single method, Print(). Any class that implements IPrintable must provide an implementation for the Print() method.

public interface IPrintable
{
    void Print();
}

Default Interface Methods (C# 8.0 and later)

C# 8.0 introduced default interface methods, allowing interfaces to provide a default implementation for methods. This enables interfaces to evolve without breaking existing implementations. In this example, the ILogger interface defines a LogInformation method with a default implementation. Classes implementing ILogger can use the default implementation or provide their own override.

public interface ILogger
{
    void Log(string message);

    // Default implementation
    void LogInformation(string message) => Log($"Information: {message}");

    void LogWarning(string message);

    void LogError(string message);

}

Implementing Abstract Class and Interface

This code demonstrates how a class, Circle, can inherit from an abstract class, Shape, and implement an interface, IPrintable. It must provide an implementation for the abstract Area() method from the Shape class and the Print() method from the IPrintable interface. It also overrides the virtual Description() method to provide a specific description for a circle. The constructor shows how the base class constructor is called using : base(color).

public class Circle : Shape, IPrintable
{
    public double Radius { get; set; }

    public Circle(double radius, string color) : base(color)
    {
        Radius = radius;
    }

    public override double Area()
    {
        return Math.PI * Radius * Radius;
    }

    public void Print()
    {
        Console.WriteLine($"Circle Area: {Area()}, Color: {Color}");
    }

    public override string Description()
    {
        return $"This is a circle with radius {Radius}.";
    }
}

Concepts Behind the Snippets

  • Abstraction: Hiding complex implementation details and exposing only essential information. Both abstract classes and interfaces facilitate abstraction.
  • Inheritance: Creating new classes based on existing classes, inheriting their properties and methods. Abstract classes support inheritance with a base class.
  • Polymorphism: The ability of an object to take on many forms. Abstract classes and interfaces enable polymorphism by allowing derived classes to be treated as instances of their base class or implemented interface.
  • Contracts: Interfaces define a contract that classes must adhere to. This ensures that classes implementing the interface provide specific functionality.

Real-Life Use Case Section

Abstract Class: Imagine developing a game with different types of characters (e.g., enemy, player, NPC). You can define an abstract class Character with common properties like Health, Position, and abstract methods like Attack(), Defend(). Each character type would then inherit from Character and implement the abstract methods in its own way.

Interface: Consider a scenario where you have different types of payment processors (e.g., credit card, PayPal, bank transfer). You can define an interface IPaymentProcessor with methods like ProcessPayment(), RefundPayment(). Each payment processor class would implement IPaymentProcessor, providing its specific implementation for processing payments.

Best Practices

  • Favor interfaces when defining a contract for unrelated classes to implement.
  • Use abstract classes when creating a base class for a hierarchy of related classes, especially when providing a partial implementation.
  • Use default interface methods sparingly. Overuse can blur the lines between interfaces and abstract classes. They are best suited for adding functionality to interfaces without breaking existing implementations.
  • Consider the Single Responsibility Principle when designing interfaces and abstract classes. Each should have a clear and focused purpose.

Interview Tip

When asked about the difference between abstract classes and interfaces, highlight the core differences related to inheritance (single vs. multiple), implementation (partial vs. none), fields, and constructors. Also, mention the introduction of default interface methods in C# 8.0 and later, and how they allow interfaces to provide default implementations.

When to Use Them

Abstract Class:

  • When you want to define a common base class with some shared implementation.
  • When you want to enforce a certain structure or behavior on derived classes.
  • When you need to use fields, properties, or constructors, which are not directly supported in interfaces (without default interface methods introducing limited state).

Interface:

  • When you want to define a contract that multiple, potentially unrelated classes can implement.
  • When you want to achieve polymorphism without being constrained by single inheritance.
  • When you need to decouple the implementation from the interface definition.

Memory Footprint

The memory footprint difference between using abstract classes and interfaces is typically negligible. The primary factor influencing memory usage is the data stored within the objects created from classes that implement interfaces or inherit from abstract classes. However, the virtual method table (vtable) used for dynamic dispatch can introduce a minor overhead, but this is usually insignificant compared to the object's overall size.

Alternatives

  • Composition: Instead of inheritance (either abstract or concrete), you can use composition to achieve code reuse and flexibility. This involves creating classes that hold instances of other classes (as fields) and delegate functionality to them.
  • Mixins (with default interface methods): By leveraging default interface methods, you can achieve a mixin-like behavior, where classes can 'mix in' functionality from multiple interfaces. This can provide a more flexible alternative to single abstract class inheritance.

Pros and Cons: Abstract Classes

Pros:

  • Can provide partial implementation, reducing code duplication in derived classes.
  • Can have fields, properties, constructors, and any access modifier.
  • Enforces a hierarchical relationship.
Cons:
  • Single inheritance limitation.
  • Can lead to tight coupling between base and derived classes.

Pros and Cons: Interfaces

Pros:

  • Supports multiple inheritance of behavior.
  • Promotes loose coupling between classes.
  • Facilitates polymorphism.
Cons:
  • Before C# 8.0, no implementation could be provided within the interface itself (default interface methods now allow this).
  • Can lead to interface pollution if not designed carefully.

FAQ

  • Can I inherit from multiple abstract classes?

    No, C# only supports single inheritance for classes. You can only inherit from one abstract class. However, you can implement multiple interfaces.
  • Can an interface have a constructor?

    No, interfaces cannot have constructors. Constructors are used to initialize the state of an object, and interfaces define a contract without specifying an implementation.
  • What are default interface methods and when should I use them?

    Default interface methods (introduced in C# 8.0) allow you to add new methods to an interface without breaking existing implementations. Use them when you need to evolve an interface without forcing all implementing classes to immediately provide an implementation for the new method. Use them sparingly and consider the overall design carefully.
  • When should I choose an abstract class over an interface?

    Choose an abstract class when you have a clear 'is-a' relationship between classes and want to provide some common implementation and state (fields, properties). Also when you need to enforce a specific structure or behavior. Choose an interface when you want to define a contract that multiple, potentially unrelated classes can implement, focusing on behavior and decoupling.