DEV Community

mohamed Tayel
mohamed Tayel

Posted on

Mastering C# Fundamentals: Inheritance

Meta Descripation:Inheritance is a foundational concept in object-oriented programming (OOP) that allows us to create a new class that builds upon an existing class. It helps to organize code efficiently by sharing common functionality and properties across different classes, reducing redundancy and improving maintainability.

In this article, we'll explore inheritance with a practical example, focusing on vehicles, to demonstrate how and why inheritance is useful in building real-world applications. We'll also go in-depth into the use of the protected access modifier, a key aspect of inheritance.

Real-Life Scenario: Vehicles

Imagine you are designing software for a vehicle management system. The company makes different types of vehicles, such as Cars, Bicycles, and Trucks. While each vehicle type has its own unique features, they all share some common properties and behaviors:

  • All vehicles have a name, a speed, and a way to start and stop.
  • Cars may have air conditioning and different seating capacities.
  • Bicycles have bells but do not use fuel.
  • Trucks can carry heavy loads and have different load capacities.

Instead of repeating the shared properties and behaviors in each class, we can use inheritance. This way, we can define the common functionality in a base class and inherit from it to add specific features in derived classes.

Step 1: Creating the Base Class

We start by creating a base class named Vehicle. This class will contain all the properties and methods common to every type of vehicle.

public class Vehicle
{
    public string Name { get; set; }
    public int Speed { get; set; }

    protected int FuelLevel { get; set; }  // Protected: Only accessible by Vehicle and its derived classes

    public void Start()
    {
        Console.WriteLine($"{Name} is starting.");
    }

    public void Stop()
    {
        Console.WriteLine($"{Name} is stopping.");
    }

    public void Accelerate(int amount)
    {
        Speed += amount;
        Console.WriteLine($"{Name} is accelerating to {Speed} km/h.");
    }

    public void Refuel(int amount)
    {
        FuelLevel += amount;
        Console.WriteLine($"{Name} has been refueled by {amount} liters. Current fuel level: {FuelLevel} liters.");
    }
}
Enter fullscreen mode Exit fullscreen mode

Here, we defined:

  • Name and Speed as public properties.
  • FuelLevel as a protected property, meaning it can be accessed by the Vehicle class and any derived class but not from external classes.
  • Methods like Start(), Stop(), Accelerate(), and Refuel() represent the common actions a vehicle can take.

Step 2: Creating the Derived Classes

Now, we create specific types of vehicles that inherit from the Vehicle base class. These derived classes will add specific features that are unique to each vehicle type.

Car Class (inherits from Vehicle)
public class Car : Vehicle
{
    public bool HasAirConditioning { get; set; }

    public void TurnOnAirConditioning()
    {
        if (HasAirConditioning)
        {
            Console.WriteLine($"{Name}'s air conditioning is now on.");
        }
        else
        {
            Console.WriteLine($"{Name} doesn't have air conditioning.");
        }
    }

    public void Drive(int distance)
    {
        if (FuelLevel >= distance / 10)
        {
            FuelLevel -= distance / 10;  // Use protected FuelLevel
            Console.WriteLine($"{Name} drove {distance} km. Remaining fuel: {FuelLevel} liters.");
        }
        else
        {
            Console.WriteLine($"Not enough fuel for {Name} to drive {distance} km.");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
Bicycle Class (inherits from Vehicle)
public class Bicycle : Vehicle
{
    public bool HasBell { get; set; }

    public void RingBell()
    {
        if (HasBell)
        {
            Console.WriteLine($"{Name} is ringing the bell!");
        }
        else
        {
            Console.WriteLine($"{Name} doesn't have a bell.");
        }
    }

    public void Pedal(int distance)
    {
        Speed += 5;
        Console.WriteLine($"{Name} is pedaling {distance} km at speed {Speed} km/h.");
    }
}
Enter fullscreen mode Exit fullscreen mode
Truck Class (inherits from Vehicle)
public class Truck : Vehicle
{
    public int LoadCapacity { get; set; }  // Load capacity in kg

    public void LoadCargo(int weight)
    {
        if (weight <= LoadCapacity)
        {
            Console.WriteLine($"{Name} is now loaded with {weight} kg.");
        }
        else
        {
            Console.WriteLine($"{Name} cannot carry more than {LoadCapacity} kg.");
        }
    }

    public void DriveWithCargo(int distance)
    {
        if (FuelLevel >= distance / 5)
        {
            FuelLevel -= distance / 5;  // Use protected FuelLevel
            Console.WriteLine($"{Name} drove {distance} km with cargo. Remaining fuel: {FuelLevel} liters.");
        }
        else
        {
            Console.WriteLine($"Not enough fuel for {Name} to drive {distance} km with cargo.");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Using the Classes

Now that we have defined our classes, let’s see how inheritance works in action.

public static void Main(string[] args)
{
    // Create a Car instance
    Car car = new Car() { Name = "Sedan", Speed = 0, HasAirConditioning = true };
    car.Refuel(50);  // Refuel using a public method from Vehicle
    car.Start();     // Use Start method from Vehicle
    car.Drive(200);  // Use Drive method from Car, which utilizes protected FuelLevel
    car.TurnOnAirConditioning(); // Use Car-specific method
    car.Stop();      // Use Stop method from Vehicle

    Console.WriteLine();

    // Create a Bicycle instance
    Bicycle bicycle = new Bicycle() { Name = "Mountain Bike", Speed = 0, HasBell = true };
    bicycle.Start(); // Use Start method from Vehicle
    bicycle.Pedal(15); // Use Pedal method from Bicycle
    bicycle.RingBell(); // Use Bicycle-specific method
    bicycle.Stop();  // Use Stop method from Vehicle

    Console.WriteLine();

    // Create a Truck instance
    Truck truck = new Truck() { Name = "Heavy Loader", Speed = 0, LoadCapacity = 10000 };
    truck.Refuel(100); // Refuel using a public method from Vehicle
    truck.Start();     // Use Start method from Vehicle
    truck.LoadCargo(8000); // Use Truck-specific method
    truck.DriveWithCargo(150); // Use Truck-specific method to drive with cargo
    truck.Stop();      // Use Stop method from Vehicle
}
Enter fullscreen mode Exit fullscreen mode

Understanding the protected Access Modifier in Inheritance

The protected access modifier is crucial in inheritance. It sits between public and private in terms of accessibility. Here’s a detailed breakdown:

  • public: The member is accessible from anywhere.
  • private: The member is only accessible within the class itself.
  • protected: The member is accessible within the class it is defined in and any of its derived classes.

Think of protected as a way to allow sharing specific details between a class and its "children," but to keep those details hidden from the rest of the world.

When to Use protected

protected is useful when you have a base class that has members needing to be shared with derived classes, but you want to prevent direct access from outside classes. For example, internal logic, data, or behaviors that are common for derived classes but should not be exposed publicly.

Example: Employees

Consider an Employee base class and specific derived classes like Manager and Intern. All employees have a salary that is managed internally, but each employee type may have different calculations for bonuses or pay increases.

  1. Base Class (Employee):
public class Employee
{
    public string Name { get; set; }
    protected decimal BaseSalary { get; set; } // Protected salary

    public Employee(string name, decimal baseSalary)
    {
        Name = name;
        BaseSalary = baseSalary;
    }

    public void ShowSalary()
    {
        Console.WriteLine($"{Name}'s base salary is {BaseSalary}.");
    }
}
Enter fullscreen mode Exit fullscreen mode
  1. Derived Class (Manager):
public class Manager : Employee
{
    public Manager(string name, decimal baseSalary) : base(name, baseSalary) { }

    public void ApplyBonus()
    {
        // Using the protected member BaseSalary
        BaseSalary += 2000;
        Console.WriteLine($"{Name} received a bonus. New salary: {BaseSalary}.");
    }
}
Enter fullscreen mode Exit fullscreen mode
  1. Usage:
public static void Main(string[] args)
{
    Manager manager = new Manager("Alice", 50000);

    manager.ShowSalary();  // Public method, can be accessed from anywhere
    manager.ApplyBonus();  // Apply bonus
    manager.ShowSalary();  // Show updated salary
}
Enter fullscreen mode Exit fullscreen mode

Benefits of protected

  1. Controlled Access for Derived Classes: protected allows derived classes to access critical properties and methods without exposing them publicly. This is useful when you want derived classes to extend or alter certain behaviors.

  2. Encapsulation: It keeps members safe from outside interference. For example, the FuelLevel should only be modified in a controlled manner, either by derived classes or by public methods of the base class.

  3. Code Reuse and Flexibility: protected allows flexibility in derived classes to utilize or extend behaviors defined in the base class, supporting reuse and reducing redundancy.

Assignments

Easy Level

  1. Create a new derived class named Motorcycle that inherits from Vehicle. Add a property HasSideCar and a method ToggleSideCar().
  2. Write code to create an instance of Motorcycle, set its properties, and call its methods.

Medium Level

  1. Modify the Bicycle class to add a new property called GearCount and a method named ChangeGear(int newGear) to adjust the bicycle's gear.
  2. Create an instance of Bicycle, set the number of gears, and write code to change gears while pedaling.

Difficult Level

  1. Create a Bus class that inherits from Vehicle with a property PassengerCapacity and a method PickUpPassengers(int passengers).
  2. Implement a feature to track the number of passengers on board, and include logic to prevent exceeding the passenger capacity.
  3. Write code to create a Bus instance, pick up passengers, and simulate driving a certain distance while managing the fuel level.

Conclusion

By understanding and applying inheritance, we can create well-structured, scalable, and maintainable applications. The key is to find the right balance between sharing functionality through the base class and customizing behavior in derived classes, all while ensuring encapsulation through access modifiers like protected.

Top comments (0)