Abstract Classes and Interfaces

Abstract classes and interfaces can be used for similar purposes but it's important to understand the differences between the two.

Abstract Classes

An abstract class is a base class that has zero or more members that are abstract, meaning that they are declared but are not defined. Unlike regular classes, abstract classes cannot be instantiated and must be inherited.

abstract class Car
{
    public abstract string FuelType { get; }
}

class TeslaModelS : Car
{
    public override string FuelType => "Electricity";
}

class FordFusion : Car
{
    public override string FuelType => "Gas";
}
Abstract Class Example

You can make a class abstract by simply using the abstract keyword before the class keyword. Once you've done so, any of the class members (properties, fields, and methods) can also be declared abstract. An abstract member can't have an implementation. Note in the example below that the method has the abstract keyword in its declaration and thus is not allowed to have a body (implementation).

abstract class Mammal
{
    public abstract void Walk();
}
Abstract Method Example

One strength of abstract classes is that they allow you to have baseline functionality in your class but they can also require users of your class to implement their own aspects of the code. In the example below, there's an EntityBase class that contains some default behavior like IsNew and IsValid because those methods have working implementations, but the Validate method is abstract and requires the inheriting class to provide its own implementation. Notice how the IsValid method even makes use of the Validate method, even though it's not defined.

/// <summary>
/// A base class for all entities.
/// </summary>
abstract class EntityBase
{
    /// <summary>
    /// The unique identifier for
    /// this entity.
    /// </summary>
    public Guid Id { get; set; }
    
    /// <summary>
    /// Indicates if this instance has
    /// been saved or not by checking
    /// to see if the <see cref="Id"/>
    /// is a blank value.
    /// </summary>
    /// <returns></returns>
    public bool IsNew() => Id == Guid.Empty;
    
    /// <summary>
    /// Returns true if <see cref="Validate"/>
    /// returns no error messages.
    /// </summary>
    /// <returns></returns>
    public bool IsValid() => !Validate().Any();
    
    /// <summary>
    /// Validates this entity and returns
    /// error messages for any validation
    /// problems.
    /// </summary>
    /// <returns></returns>
    public abstract IEnumerable<string> Validate();
}

/// <summary>
/// Represents a person entity.
/// </summary>
class Person : EntityBase
{
    /// <summary>
    /// The full name of the person.
    /// </summary>
    public string Name { get; set; }
    
    /// <inheritdoc />
    public override IEnumerable<string> Validate()
    {
        if (string.IsNullOrWhiteSpace(Name))
            yield return "A name is required!";
    }
}
Entity Example With Abstract Class

Another strength of abstract classes is that you can make non-public members abstract. In other words, you can force the user of your class to implement protected and internal members, in addition to public ones.

A weakness of abstract classes is that you cannot inherit from more than one of them.

Interface

An interface is a contract that has zero or more members that must be implemented by any class that implements the interface. As of C# 8.0, interfaces may contain default implementations, in which case, the implementing class will not be required to implement members for which there are default implementations. Like an abstract class, interfaces cannot be instantiated.

interface ICar
{
    string FuelType { get; }
}

class TeslaModelS : ICar
{
    public string FuelType => "Electricity";
}

class FordFusion : ICar
{
    public string FuelType => "Gas";
}
Interface Example

To implement an interface, you simply specify the interface after the class name as shown below. In the example below, there's a Person class that implements an IEntity interface. The interface defines some members that the class must implement to be a valid IEntity.

/// <summary>
/// The contract to which all entities
/// must adhere.
/// </summary>
interface IEntity
{
    /// <summary>
    /// The unique identifier
    /// for this entity.
    /// </summary>
    Guid Id { get; set; }
    
    /// <summary>
    /// Indicates if this instance has
    /// been saved or not by checking
    /// to see if the <see cref="Id"/>
    /// is a blank value.
    /// </summary>
    /// <returns></returns>
    bool IsNew();
    
    /// <summary>
    /// Returns true if <see cref="Validate"/>
    /// returns no error messages.
    /// </summary>
    /// <returns></returns>
    bool IsValid();

    /// <summary>
    /// Validates this entity and returns
    /// error messages for any validation
    /// problems.
    /// </summary>
    /// <returns></returns>
    IEnumerable<string> Validate();
}

/// <summary>
/// Represents a person entity.
/// </summary>
class Person : IEntity
{
    /// <summary>
    /// The full name of the person.
    /// </summary>
    public string Name { get; set; }

    /// <inheritdoc />
    public Guid Id { get; set; }

    /// <inheritdoc />
    public bool IsNew() => Id == Guid.Empty;

    /// <inheritdoc />
    public bool IsValid() => !Validate().Any();

    /// <inheritdoc />
    public IEnumerable<string> Validate()
    {
        if (string.IsNullOrWhiteSpace(Name))
            yield return "A name is required!";
    }
}
Entity Example With Interface

One strength of interfaces is that you can implement as many of them as you'd like. So you could have a FordFusion class that implements both ICar and IGasPoweredVehicle. This would allow you to pass an instance of your FordFusion class into any methods that accept ICar or IGasPoweredVehicle.

Members of interfaces are always public. This could be considered a weakness, but it's also kind of the defining characteristic of interfaces, so it's hard to call it such. Either way, it's definitely something to be aware of.

A weakness of interfaces is that, before C# 8.0, they couldn't have default implementations. As of C# 8.0, you can have default implementations in interfaces, but said behavior can only interact with members that the interface has declared. And because interface members can only be public, this means that your default implementations can only deal with members that are also public.

Here's the same interface example as above, but this time, we're taking advantage of C# 8.0's default implementations support. Notice how we don't have to declare the IsNew or IsValid methods on the Person class because IEntity contains default implementations of those members. We could still declare those methods if we wanted to supply our own implementations, but we're no longer required to.

/// <summary>
/// The contract to which all entities
/// must adhere.
/// </summary>
interface IEntity
{
    /// <summary>
    /// The unique identifier
    /// for this entity.
    /// </summary>
    Guid Id { get; set; }
    
    /// <summary>
    /// Indicates if this instance has
    /// been saved or not by checking
    /// to see if the <see cref="Id"/>
    /// is a blank value.
    /// </summary>
    /// <returns></returns>
    bool IsNew() => Id == Guid.Empty;
    
    /// <summary>
    /// Returns true if <see cref="Validate"/>
    /// returns no error messages.
    /// </summary>
    /// <returns></returns>
    bool IsValid() => !Validate().Any();

    /// <summary>
    /// Validates this entity and returns
    /// error messages for any validation
    /// problems.
    /// </summary>
    /// <returns></returns>
    IEnumerable<string> Validate();
}

/// <summary>
/// Represents a person entity.
/// </summary>
class Person : IEntity
{
    /// <summary>
    /// The full name of the person.
    /// </summary>
    public string Name { get; set; }

    /// <inheritdoc />
    public Guid Id { get; set; }

    /// <inheritdoc />
    public IEnumerable<string> Validate()
    {
        if (string.IsNullOrWhiteSpace(Name))
            yield return "A name is required!";
    }
}
Entity Example With Default Interface Implementations

Comparison

The following table is an at-a-glance comparison between the two code constructs.

Feature Abstract Class Interface
Multiple Inheritance No. Yes.
Default Implementations Yes. Yes (in C# 8.0).
Access Modifiers Yes. No, everything is public.

When to Use

Use an abstract class if:

  • You have a base class that needs to utilize private, protected, or internal members.

Use an interface if:

  • You want to support multiple inheritance.
  • You want default implementations that may or may not make use of other interface members. (Only applies to C# 8.0 or greater)
  • You want to apply to a struct.