Let’s explore the differences between abstraction and encapsulation in C#.

Abstraction and encapsulation constitute fundamental concepts in object-oriented programming (OOP) and coexist in C#. Each of them fulfills key roles in the design and implementation of classes and objects, although they serve different purposes.

To download the source code for this article, you can visit our GitHub repository.

Let’s take an in-depth exploration, commencing with abstraction in C#.  

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

Abstraction in C#

Object-oriented programming relies on abstraction and C# provides various ways to achieve this concept. Abstraction involves hiding complex implementation details by exposing only the essential object features. This idea is commonly put into practice using abstract classes and interfaces.

Abstract classes serve as templates that cannot be instantiated directly. They can consist of abstract methods that act as base methods and require implementation by the classes that inherit from them. Also, interfaces can define declarations for methods, properties, indexers, and events which is the responsibility of the implementing classes.

To learn more about interfaces, be sure to check out our great article C# Intermediate – Interfaces.

Starting with C#8, interfaces may have default implementations that derived classes can use without explicitly implementing their version, which promotes flexibility and adaptability in designing organized, maintainable, and scalable code.

Let’s understand abstraction better with an example:

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

    public abstract void MakeSound();
}

public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Bark bark!");
    }
}

public class Cat : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Meow!");
    }
}

Here, we define an abstract class, Animal, that contains properties for Name and Age, and an abstract method MakeSound(). From this, we create two concrete classes, Dog and Cat, which both inherit from our abstract class and provide their implementation of the MakeSound() method that describes the sound each animal makes.

Now we can see our classes in action in the Program class:

Animal dog = new Dog();
dog.Name = "Buddy";
dog.Age = 3;
Console.WriteLine($"{dog.Name} says:");
dog.MakeSound();

Animal cat = new Cat();
cat.Name = "Whiskers";
cat.Age = 2;
Console.WriteLine($"{cat.Name} says:");
cat.MakeSound();

We create an instance of Animal and assign it a new Dog class. Then we set the properties Name and Age to “Buddy” and 3, respectively. We do the same thing but with the Cat class, setting the properties to “Whiskers” and 2, respectively. Finally, we output each animal’s name and what sound it makes.

Let’s check our console output:

Buddy says:
Bark bark!
Whiskers says:
Meow!

We see that our Cat and Dog classes both use their MakeSound() method overrides.

Advantages of Abstraction

Abstraction significantly boosts code reusability by establishing shared interfaces or base classes, facilitating the inheritance of multiple classes from a common abstraction, and minimizing redundancy.

This methodology fosters adaptable designs, enabling the expansion of functionality without necessitating modifications to existing code. Programming to interfaces or abstract classes eases incorporating new features, avoiding disruptions to an existing codebase. The employment of abstraction streamlines testing and debugging processes. Additionally, well-defined interfaces contribute to crafting mock objects for testing and isolating components for debugging, ultimately enhancing the overall software quality.

Let’s move on to a deeper exploration of encapsulation – delving into its conceptual foundations, practical use cases, and overarching goals.

Encapsulation in C#

Encapsulation is one of the main pillars of object-oriented programming (OOP). It represents a key concept that consists of attributes and methods that work together within a class. Encapsulation limits external access to the internal state of the object and with that access limits direct manipulation of object data outside the class. It hides an object’s internal state, permitting access only through defined interfaces (public methods or properties).

Access modifiers like public, private, protected, and internal in C# enforce encapsulation. Private members are accessible only within the class, public members allow external access, and a protected modifier is accessible inside and in derived classes. An internal modifier is used within an assembly, which is a DLL or EXE created by compiling one or more code files.

Read more about access modifiers in our article C# Access Modifiers.

Let’s create a class to demonstrate encapsulation:

public class BankAccount
{
    public string AccountNumber { get; }
    public decimal Balance { get; private set; }

    public BankAccount(string accountNumber, decimal initialBalance = 0)
    {
        AccountNumber = accountNumber;
        Balance = initialBalance;
    }

    public void Deposit(decimal amount)
    {
        if (amount > 0)
        {
            Balance += amount;
            Console.WriteLine($"Deposit successful. New account balance: {Balance}");
        }
        else
        {
            Console.WriteLine("Invalid amount for deposit.");
        }
    }

    public void Withdraw(decimal amount)
    {
        if (amount > 0 && amount <= Balance)
        {
            Balance -= amount;
            Console.WriteLine($"Withdrawal successful. New account balance: {Balance}");
        }
        else
        {
            Console.WriteLine("Invalid amount for withdrawal or insufficient funds.");
        }
    }
}

Here, we create a BankAccount class to demonstrate the principle of encapsulation. Within this class, we expose the AccountNumber as a read-only property and C# allows the value to be set through the constructor. Also, the Balance property has a getter that enables retrieving the value and a private setter that prevents external modifications. Furthermore, our constructor facilitates the initialization of the account by accepting an account number and, optionally, an initial balance. We have methods for depositing and withdrawing, each integrating suitable validations and delivering messages. Ultimately, our BankAccount class adheres to the principles of encapsulation by restricting direct modification of the balance and follows established best practices for object-oriented design.

Now, let’s use our BankAccount class in the Program class:

BankAccount account = new BankAccount("1234567890");

account.Deposit(500);
account.Withdraw(200);

Console.WriteLine($"Final Account Information - Account Number: {account.AccountNumber}, Balance: {account.Balance}");

Here, we create an instance of the BankAccount class, setting the account number in the constructor. Then, we use Deposit() to add 500 to the account and use Withdraw() to remove 200.

Let’s inspect the console output:

Deposit successful. New account balance: 500
Withdrawal successful. New account balance: 300
Final Account Information - Account Number: 1234567890, Balance: 300

Advantages of Encapsulation

In C#, envision encapsulation as a robust defense mechanism, differentiating it from abstraction. Differences between abstraction and encapsulation in C# highlight the distinct roles each plays in shaping code structure and functionality. For instance, encapsulation prevents external entities from directly tampering with an object’s data and functionalities. Additionally, encapsulation plays a pivotal role in fortifying program security and mitigating the risk of unintended interference or unauthorized access to critical information.

Now, let’s delve into the technical magic: encapsulation isn’t merely a security enhancement; it’s a game-changer for software maintenance. By employing encapsulation, classes seamlessly allow modifications to their internals without causing cascading changes in other code sections. This augments flexibility and contributes to a scalable and adaptable application structure.

Moreover, encapsulation serves as a debugging wizard. By precisely localizing potential issues within specific classes, it simplifies the debugging process and enhances the efficiency of identifying and resolving problems. This approach streamlines both the development and maintenance phases, ensuring a more robust and resilient codebase.

Therefore, encapsulation isn’t just a security feature; it’s a strategic move for crafting secure, highly maintainable, flexible, and efficient code.

Comparing Abstraction and Encapsulation in C#

Now that we understand abstraction and encapsulation, let’s understand the differences.

Abstraction in C# involves simplifying complex systems by creating classes that utilize the appropriate level of inheritance to solve a given problem. This emphasizes defining the essential features of an object while concealing unnecessary details. This practice offers a twofold advantage: firstly, it reduces code complexity; secondly, it promotes code reusability. Moreover, abstraction accommodates multiple implementations while adhering to a standardized interface.

In contrast, encapsulation focuses on bundling data and methods that operate on the data into a single unit, often a class, and controlling access to the internal state through access modifiers. This approach not only ensures data security but also promotes modularity and streamlines maintenance by confining components within well-defined boundaries.

Benefits of Using Abstraction and Encapsulation Together

While abstraction is about establishing a clear and conceptual structure, encapsulation is about hiding implementation details and providing a well-defined external interface for interacting with a class.

Together, they contribute to effectively managing complexity, improving code reuse, and enhancing organization and security in software design.

Conclusion

In this article, we started by gaining an understanding of abstraction and encapsulation in C#, exploring both with an example and looking at some of the advantages. From this understanding, we were then able to explore the differences between the two concepts.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!