Jun 8, 2023 2:00 AM

Composition vs. inheritance in OOP and C#

Understand the key concepts behind composition and inheritance that allow you to build reusable and flexible types in your .NET applications.

Two of the key concepts in object-oriented programming (OOP) are inheritance and composition. While both can help you reuse code, there are key differences between them. Inheritance establishes common behavior and interfaces for your classes, while composition combines and reuses existing classes to create more complex objects.

In this article, we’ll dive into these two concepts of OOP and understand when, why, and how to use them in our .NET applications. To work with the code examples provided in this article, you should have Visual Studio 2022 installed in your system. If you don’t already have a copy, you can download Visual Studio 2022 here.

Create a console application project in Visual Studio

First off, let’s create a .NET Core console application project in Visual Studio. Assuming Visual Studio 2022 is installed in your system, follow the steps outlined below to create a new .NET Core console application project in Visual Studio.

  1. Launch the Visual Studio IDE.
  2. Click on “Create new project.”
  3. In the “Create new project” window, select “Console App (.NET Core)” from the list of templates displayed.
  4. Click Next.
  5. In the “Configure your new project” window shown next, specify the name and location for the new project.
  6. Click Next
  7. In the “Additional information” window shown next, choose “.NET 7.0 (Standard Term Support)” as the Framework version you would like to use.
  8. Click Create.

We’ll use this project to work with the examples of inheritance and composition in the subsequent sections of this article.

Reusability in object-oriented programming

Since the dawn of computer programming, different programming challenges have spawned different approaches, paradigms, and architectural styles. One paradigm that has been around for decades is object-oriented programming. The OOP paradigm offers several benefits including maintainability, extensibility, and code reuse.

One of the key benefits of OOP is code reusability. You can achieve this in two different ways, either by inheritance (is-a relationship) or by composition (has-a relationship). The concepts of composition and inheritance are both fundamental to OOP, but their approaches and implications are different.

The debate over the choice between composition and inheritance is decades old. Composition is usually preferred over inheritance for several reasons, but you should know the advantages and disadvantages of both before deciding.

What is inheritance in OOP? Why should you use it?

In inheritance, a class inherits properties and behaviors from another class, except for those that are private. The inheritance relationship between classes is described as an “is a” relationship (a customer is a person), where a subclass is considered a specialized version of its superclass.

The use of inheritance facilitates code reuse because base classes define common methods that can be extended or replaced by derived classes. Objects of different derived types can be handled interchangeably by their common superclass thanks to their hierarchical structure and polymorphism.

Inheritance offers several advantages:

  • Inheritance promotes extensibility by allowing you to create base classes that contain common functions that derived classes should inherit.
  • Inheritance helps you map real-world objects and their relationships into abstract types.
  • Inheritance minimizes code redundancy and lowers development and maintenance costs because you can reuse existing code.
  • Inheritance ensures that subclasses follow a standard interface.

However, inheritance also has disadvantages:

  • Inheritance increases the coupling between a base type and its derived types. If you change your base class, all subclasses are also affected.
  • Inheritance breaks encapsulation because the methods and attributes of the base are exposed to its derived classes

Use inheritance in OOP and C#

To implement inheritance in C#, you should use the extends keyword as shown in the code snippet given below.

public class Person
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string PostalCode { get; set; }
    public string Country { get; set; }
}
public class Customer: Person
{
    //Write the members of the customer class here
}
public class Supplier : Person
{
    //Write the members of the supplier class here
}

What is composition in OOP? Why should you use it?

Composition is a mechanism that allows an instance or object of a class to contain instances of the same or other classes. It establishes a "has a" relationship between classes (an author has a book), where one class contains an object of another class.

Composition combines existing classes to create more complex classes, which promotes code reuse. Because objects can be dynamically composed at runtime and easily replaced or modified without affecting the entire system, composition allows for greater flexibility and modularity.

Use composition in OOPS and C#

The following code snippet illustrates how you can implement composition in C#.

public class Book
{
    public int Id { get; set; }
    public string Title { get; set; }
}
public class Author
{
    protected List<Book> books = new List<Book>();
    //Other members
}

Why prefer composition over inheritance?

When you design a base class from which one or more classes will inherit properties and behaviors, you provide a common interface for the subclasses. However, these benefits come at the cost of a number of negative effects:

  • All subclasses of the base class will be forced to adhere to the implementations of the interface.
  • The implementation of the base class may become difficult to change over time.
  • The tight coupling between the base class and all its subclasses may hinder development.

There are ways to solve these problems such as by using the SOLID principles of object-oriented programming. However, doing so can overcomplicate your design and type hierarchy. A better alternative is replacing inheritance with composition unless there is a specific reason to use inheritance.

In most cases, you should prefer using composition over inheritance because it will make your source code less tightly coupled. However, to say you should always favor composition over inheritance would be an oversimplification. Just remember the “is a” and “has a” rule when designing your types.

Prefer using composition over inheritance when you need to reuse code and the types don’t have an “is a” relationship. Additionally, if your types don’t have an “is a” relationship but you need polymorphism, use composition with interfaces.

As a rule, you should decide whether composition or inheritance is more suitable for your needs based on the specific requirements. A good choice is to combine composition and inheritance when developing systems to get the best of both worlds. Ultimately, the decision between composition and inheritance will depend on the type of application you are building, the relationships between the classes, and the features you want.