CodeGym /Courses /C# SELF /The Concept of Abstraction in Programming

The Concept of Abstraction in Programming

C# SELF
Level 22 , Lesson 0
Available

1. Introduction

You use a computer or smartphone every day—without really thinking about it. You open a browser, go to a website, take a photo. You don’t need to know how the processor works, how RAM operates, or what signals are running through the chips. That’s the user level—convenient, intuitive, hiding all the extra stuff from you.

But what if something goes wrong? Say, an app stops launching. To reinstall it, you need a bit more knowledge—at least where to download it and how to install it. That’s another level of abstraction—the system, technical level. And if it’s a hardware issue—a failed drive or motherboard—you can’t get by without understanding the physical structure of the device.

There can be lots of abstraction levels, and each one hides complexity, giving you only what you really need to solve your task.

Programming works the same way. Imagine you get in a taxi and say, “Take me to Programmer Street, house 42.” You don’t care what route the driver takes, how he shifts gears, or what fuel is in the tank. You just want to get there. Everything else is hidden. And that’s not laziness, that’s pure abstraction: you interact with the system through a clear interface, without digging into the implementation details.

Another example—a camera on your phone. You tap the icon, take a picture, and it shows up in your gallery. You don’t need to know how light passes through the lens, how the sensor and chip work, or how the data gets into memory. All that is hidden under a handy interface. That’s abstraction—the ability to use a powerful tool without understanding its inner workings.

In the programming world, especially in C# and .NET, abstraction isn’t just a convenience—it’s a must-have. Without it, it’s tough to build big, understandable, maintainable projects. It lets programmers speak the same language, without drowning in the details of every module’s implementation.

2. Why do we need abstraction in programming?

"Okay, sounds smart, but why do I, a future code guru, need this?" Great question! Abstraction isn’t for abstraction’s sake—it gives you some very real, super practical benefits:

  • Simplifying complex systems: Our brains can’t keep all the details of a huge program in mind at once. Abstraction lets us break a complex task into smaller, manageable pieces. Each piece “hides” its internal complexity, giving us only what’s really important. It’s like Lego: each piece is simple, but you can build anything out of them without worrying about how each brick is made.
  • Making code easier to read and maintain: Code built on abstraction principles is way easier to read and understand. When you see a method call like device.TurnOn(), you instantly get what it does, without digging into hundreds of lines that explain how it works for a lightbulb or a fan. That, in turn, makes the code easier to fix and add new features to.
  • Reducing coupling between modules: Imagine your code that turns on a flashlight directly calls a bunch of low-level operations specific to just one flashlight model. What happens if you want to swap that model for another? You’d have to rewrite all the code! Abstraction lets you work with “any flashlight” through a common interface. If you swap out the “guts” of the flashlight, the code that turns it on won’t even “notice.” That’s because it works with its abstract representation.
  • Flexibility and painless changes: Thanks to abstraction, you can change the internal implementation of a component without touching the rest of the program that interacts with it. That’s priceless in big projects, where dev teams can work on different parts of the system at the same time without stepping on each other’s toes.
  • Separation of responsibility: Every element of your program (class, method) gets its own clearly defined responsibility. The LightBulb class handles its own stuff, and the SmartHomeManager class manages devices without knowing their nitty-gritty details.

Abstraction isn’t something you “add” to a program like an ingredient. It’s more of a way of thinking when designing code. It’s your ability to see the common features and hide the differences.

3. How does abstraction show up in C# (and OOP in general)?

You might be surprised, but we’ve already been using abstraction, even without calling it by name! It shows up at different levels in our C# programs:

Classes and Objects

The very concept of a class is already abstraction. The LightBulb class abstracts the idea of a “lamp” that can be turned on, turned off, and have a certain brightness level. When we create an object LightBulb myLamp = new LightBulb();, we’re working with that abstraction, not with a specific set of electrons and atoms.

Example: Check out our LightBulb class. It has a TurnOn() method. You call myLamp.TurnOn(), and the lamp turns on. But you’re not writing code that directly controls electricity, opens microscopic shutters, or starts a thermonuclear reaction in the filament (just kidding!). All those details are hidden inside the TurnOn() method’s implementation.

Access modifiers: Using private fields and methods is a direct example of encapsulation, which is one of the main ways to achieve abstraction. We make some data or operations unavailable from the outside, “abstracting” the class user from the internal complexity. For example, in a banking app, the _updateBalance() method (private, with an underscore to show it’s an internal detail) can handle complex balance update logic, while only Deposit() or Withdraw() are available to the outside world. That’s abstraction.

Methods and Functions

Every method call is using abstraction. You trust the method to do a job, but you don’t care how it does it.

For example, remember our good old Console.WriteLine("Hello, world!");? You just call this method and expect the text to show up on the screen. You don’t need to know the implementation details: how the OS allocates a memory buffer, how fonts turn into pixels, how the graphics adapter puts them on the monitor.

Let’s be real—if you had to think about all that, every short program would take hours to write.

Console.WriteLine is a powerful abstraction. It hides a ton of work behind the scenes.

Inheritance and Polymorphism

Here’s where abstraction really shines! When we create a base class Animal and its derived classes Dog and Cat, we’re abstracting the general idea of an “animal” that can “make a sound.”

For example, you write Animal myPet = new Dog(); and then myPet.MakeSound();. Here, you’re working with the Animal abstraction. You’ve “abstracted away” the fact that myPet is actually a Dog. Polymorphism lets this abstract call MakeSound() show up differently for different concrete types (the dog barks, the cat meows). You programmed “what to do” (make a sound), but left the “how to do it” to the specific classes. That’s abstraction winning!

Interfaces (just a mention for now, details later)

We haven’t learned them yet, but remember: interfaces in C# are the purest way to express abstraction—“what without how.” An interface is basically a contract that describes a set of methods, properties, or events, but doesn’t provide any implementation. It says, “Anyone who implements this interface must be able to do this and that.” We’ll talk about them more in Lecture 111, but just know: this is the peak of abstraction in C#.

Abstract classes (also just a mention, details in the next lecture)

Abstract classes are something between regular classes and interfaces. They can have both implemented methods and abstract methods (like we briefly saw in Lecture 105), which have no body and must be implemented in derived classes. Abstract classes are used to create a general skeleton, a common set of functionality, but with “holes” (abstract methods) that must be filled in by concrete implementations in subclasses. We’ll talk about them in detail in the next lecture!

So, to sum up: abstraction isn’t just some single syntax element in C#. It’s a powerful concept that runs through every level of our code, from simple methods to complex class hierarchies.

4. Code Example: Smart Home Management System

Let’s get back to our app and see how abstraction helps us make it more flexible. Imagine we’re building a “Smart Home” system. At first, we just have lamps and fans:


public class LightBulb
{
    public string Name;
    public LightBulb(string name) => Name = name;
    public void TurnOn() => Console.WriteLine($"{Name}: light is on");
    public void ChangeBrightness(int level) => Console.WriteLine($"{Name}: brightness {level}%");
}

public class Fan
{
    public string Name;
    public Fan(string name) => Name = name;
    public void TurnOn() => Console.WriteLine($"{Name}: fan is on");
    public void AdjustSpeed(int speed) => Console.WriteLine($"{Name}: speed {speed}");
}

class Program
{
    static void Main()
    {
        var lamp = new LightBulb("Kitchen");
        var fan = new Fan("Bedroom");

        lamp.TurnOn();
        lamp.ChangeBrightness(75);

        fan.TurnOn();
        fan.AdjustSpeed(3);
    }
}

This code works great as long as we’re dealing with each device separately. But what if we want to make our smart home really smart and control all devices centrally? Like, turn everything on before you get home?

If we try to create an object[] and store our devices in it, we’ll hit a problem: the object type doesn’t know about the TurnOn() method. To call it, we’d have to check each object’s type and cast it, which is super clunky and ugly:


// without abstraction and polymorphism:
foreach (object device in allDevices)
{
    if (device is LightBulb bulb)
    {
       bulb.TurnOn();
    }
    else if (device is Fan fan)
    {
        fan.TurnOn();
    }
    // And so on for every new device type... yikes!
}

Here’s where inheritance and polymorphism come in, which, along with encapsulation, are tools for achieving abstraction. Let’s create a base class SmartDevice that will abstract the general idea of a “smart device,” and inherit the lamp and fan from it.


class SmartDevice
{
    public string Name;
    public SmartDevice(string name) => Name = name;
    public virtual void TurnOn()  => Console.WriteLine($"{Name}: device is on");
    public virtual void TurnOff() => Console.WriteLine($"{Name}: device is off");
}

class LightBulb : SmartDevice
{
    public LightBulb(string name) : base(name) { }
    public override void TurnOn()  => Console.WriteLine($"{Name}: light is on");
    public override void TurnOff() => Console.WriteLine($"{Name}: light is off");
    public void ChangeBrightness(int x) => Console.WriteLine($"{Name}: brightness {x}%");
}

class Fan : SmartDevice
{
    public Fan(string name) : base(name) { }
    public override void TurnOn()  => Console.WriteLine($"{Name}: fan is on");
    public override void TurnOff() => Console.WriteLine($"{Name}: fan is off");
    public void AdjustSpeed(int s) => Console.WriteLine($"{Name}: speed {s}");
}

class Program
{
    static void Main()
    {
        SmartDevice[] devices =
        {
            new LightBulb("Kitchen"),
            new Fan("Bedroom"),
            new SmartDevice("Sensor")
        };

        foreach (var d in devices) d.TurnOn();
        foreach (var d in devices) d.TurnOff();

        // Demo of calling specific methods
        foreach (var d in devices)
        {
            if (d is LightBulb b)
                b.ChangeBrightness(50);
            if (d is Fan f)
                f.AdjustSpeed(2);
        }
    }
}

See how much cleaner and more flexible our code is now! We can add any new device type (like SmartTV, SmartThermostat) to smartHomeDevices as long as it inherits from SmartDevice, and our foreach (SmartDevice device in smartHomeDevices) loop will work without changes. That’s the awesome power of abstraction in action. We’ve abstracted away from the specific device type, focusing on its general ability to “turn on” and “turn off.”

This example clearly shows how inheritance and polymorphism, which we learned earlier, are tools for achieving abstraction. We created a generalized representation (SmartDevice) that lets us work with different concrete devices (LightBulb, Fan) in a unified way.

But there’s a catch: in our current SmartDevice, the TurnOn() and TurnOff() methods have a “general implementation” that just prints “Device is turning on/off (general implementation).” But what if we don’t have a meaningful “general implementation” for all devices? For example, a “general device” (SmartDevice directly) could be just a temperature sensor, which doesn’t have an ON/OFF button. Or what if we want to force all child classes to provide their own implementation of these methods? That’s where abstract classes and abstract methods come in, which we’ll talk about in detail in the next lecture. They’re an even more powerful way to apply the abstraction principle, making sure some methods must be implemented in subclasses.

That’s it for our dive into abstraction as a fundamental OOP principle. In the next lecture, we’ll dig into how C# gives us special tools—abstract classes and abstract methods—to enforce this concept in code. Get ready, it’s gonna get even cooler!

2
Task
C# SELF, level 22, lesson 0
Locked
A simple abstraction with classes
A simple abstraction with classes
2
Task
C# SELF, level 22, lesson 0
Locked
Polymorphism and Abstraction
Polymorphism and Abstraction
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION