Object-Oriented Programming (OOP) in C#

What is Object-Oriented Programming (OOP) in C#?

Object-Oriented Programming (OOP) in C# is a fundamental programming paradigm that revolves around the concept of organizing code into objects, each encapsulating both data and the functions that operate on that data. In C#, classes serve as blueprints for creating these objects, defining their structure (attributes or properties) and behavior (methods or functions).

This approach mimics real-world entities, making it easier to model complex systems and interactions. Encapsulation ensures that the internal workings of an object are hidden from the outside, promoting security and modularity. Inheritance allows classes to inherit attributes and behaviors from other classes, fostering code reuse and hierarchical relationships. Polymorphism enables objects to be treated as instances of their base class, facilitating dynamic behavior. By abstracting complex systems into manageable classes and leveraging concepts like interfaces, constructors, and access modifiers, developers can create efficient, modular, and scalable applications in C# that align closely with real-world scenarios.

Four Pillars of Object-Oriented Programming (OOP)

1. Encapsulation
2. Inheritance
3. Polymorphism
4. Abstraction

Let's start with class and object.

What is a Class?

  • A class is a blueprint or template for creating objects.
  • It defines the structure and behavior of objects that belong to that class. A class encapsulates data (attributes) and methods (functions) that operate on that data.
  • It serves as a model for creating instances, which are individual objects that follow the characteristics and behaviors defined by the class.
using System;

// Define a class named "Car"
class Car
{
    // Attributes/Fields
    public string Make;
    public string Model;
    public string Color;
    public int Year;

    // Constructor
    public Car(string make, string model, string color, int year)
    {
        Make = make;
        Model = model;
        Color = color;
        Year = year;
    }

    // Method
    public void StartEngine()
    {
        Console.WriteLine("Engine started for the " + Make + " " + Model);
    }
}

class Program
{
    static void Main(string[] args)
    {
        // Create instances of the Car class
        Car car1 = new Car("Toyota", "Camry", "Blue", 2022);
        Car car2 = new Car("Ford", "Mustang", "Red", 2023);

        // Access attributes and methods of the instances
        Console.WriteLine("Car 1: " + car1.Make + " " + car1.Model + ", " + car1.Color + ", " + car1.Year);
        car1.StartEngine();

        Console.WriteLine("Car 2: " + car2.Make + " " + car2.Model + ", " + car2.Color + ", " + car2.Year);
        car2.StartEngine();
    }
}

Car class is defined with attributes (Make, Model, Color, Year), a constructor that initializes these attributes, and a method (StartEngine) that prints a message indicating the engine has started. The Main method in the Program class demonstrates how to create instances of the Car class and interact with their attributes and methods.

Object

  • The object is an instance of a class. A class defines a blueprint that describes the structure and behavior of objects, while an object is a concrete entity created based on that blueprint.
  • In other words, a class is like a template or a blueprint, and an object is a specific realization of that template with its own unique data and state.
  • An object has attributes (also called properties, fields, or member variables) that store data, and it can have methods (also called member functions) that define its behavior. When you create an object from a class, you're essentially creating a specific instance of that class with its own set of attribute values.

For example, if you have a Car class, an object of that class could represent a specific car with its make, model, color, and other attributes. The methods of the class might allow you to perform actions on the car, like starting the engine or driving.

Car car1 = new Car("Toyota", "Camry", "Blue", 2022); // Creating an object of the Car class
car1.StartEngine(); // Calling the StartEngine method on the car1 object

Car car2 = new Car("Ford", "Mustang", "Red", 2023); // Creating another object of the Car class
car2.StartEngine(); // Calling the StartEngine method on the car2 object

car1 and car2 are objects of the Car class. They are distinct instances of the class, each with its own set of attribute values. The StartEngine method is called on each object to perform the specified action for each car individually.

Encapsulation

Encapsulation in C# is a fundamental concept in object-oriented programming that involves bundling data (attributes) and methods (functions) that operate on that data into a single unit, known as a class. The idea is to hide the internal details of an object and provide controlled access to its properties and behaviors.

A real-world example of encapsulation can be understood using the analogy of a television:

Imagine you have a television at home. The television has various buttons and settings, but you don't need to understand the intricate details of how it works internally to operate it. The manufacturer has encapsulated the complex electronics and components inside a casing, providing you with a remote control and a user interface (the buttons) to interact with the TV.

In C#, encapsulation is achieved by defining classes with private fields (attributes) and public methods (behaviors) to interact with those fields. The private fields are not directly accessible from outside the class, ensuring that the internal state is protected. Public methods provide controlled access to the internal data, allowing users of the class to interact with it in a well-defined manner.

public class Television
{
    private bool isOn;
    private int currentChannel;

    public Television()
    {
        isOn = false;
        currentChannel = 1;
    }

    public void TurnOn()
    {
        isOn = true;
        Console.WriteLine("TV is now on.");
    }

    public void TurnOff()
    {
        isOn = false;
        Console.WriteLine("TV is now off.");
    }

    public void ChangeChannel(int channel)
    {
        if (isOn)
        {
            currentChannel = channel;
            Console.WriteLine("Changed to channel: " + channel);
        }
        else
        {
            Console.WriteLine("TV is off. Cannot change channel.");
        }
    }
}

Television the class encapsulates the internal state (whether it's on or off, the current channel) and provides methods to interact with it (turning on/off, changing channels). The user of the class can only interact with the TV through these defined methods.

Inheritance

To create a new class (called the subclass or derived class) based on an existing class (called the superclass or base class). The subclass inherits attributes and methods from the superclass, and it can also add its own unique attributes and methods or override existing ones. Inheritance promotes code reuse and helps in organizing classes in a hierarchy.

For example, Imagine you have a base class called Animal that defines common attributes and behaviors for all animals. Then, you can create specific animal types (subclasses) that inherit from the Animal class and add their own specialized attributes and behaviors.

  1. Base Class (Superclass) - Animal: This class defines attributes common to all animals, like name and age, and methods common to all animals, like Eat() and Sleep().

  2. Subclasses (Derived Classes) - Dog and Cat: These classes inherit from the Animal class. They automatically have attributes and methods from the base class. Additionally, they can have their own unique attributes (e.g., breed for dogs) and behaviors (e.g., Bark() for dogs and Meow() for cats).

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

    public void Eat()
    {
        Console.WriteLine(Name + " is eating.");
    }

    public void Sleep()
    {
        Console.WriteLine(Name + " is sleeping.");
    }
}

class Dog : Animal
{
    public string Breed { get; set; }

    public void Bark()
    {
        Console.WriteLine(Name + " is barking.");
    }
}

class Cat : Animal
{
    public void Meow()
    {
        Console.WriteLine(Name + " is meowing.");
    }
}

With this inheritance structure, you can create instances of Dog and Cat objects, and they inherit the common attributes and methods from the Animal class:

Dog dog = new Dog();
dog.Name = "Buddy";
dog.Age = 3;
dog.Breed = "Golden Retriever";

Cat cat = new Cat();
cat.Name = "Whiskers";
cat.Age = 2;

dog.Eat();     // Outputs: Buddy is eating.
dog.Bark();    // Outputs: Buddy is barking.

cat.Eat();     // Outputs: Whiskers is eating.
cat.Meow();    // Outputs: Whiskers is meowing.

Single Inheritance

In single inheritance, a subclass can inherit from only one superclass. This is the simplest form of inheritance.

class Animal
{
    public void Eat()
    {
        Console.WriteLine("Animal is eating.");
    }
}

class Dog : Animal
{
    public void Bark()
    {
        Console.WriteLine("Dog is barking.");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Dog dog = new Dog();
        dog.Eat();   // Outputs: Animal is eating.
        dog.Bark();  // Outputs: Dog is barking.
    }
}

Multiple Inheritance through Interfaces

C# supports multiple inheritance through interfaces. An interface defines a contract that classes can implement. A class can implement multiple interfaces, effectively inheriting behaviors from multiple sources.

interface ICanFly
{
    void Fly();
}

class Bird : ICanFly
{
    public void Fly()
    {
        Console.WriteLine("Bird is flying.");
    }
}

class Airplane : ICanFly
{
    public void Fly()
    {
        Console.WriteLine("Airplane is flying.");
    }
}

class FlyingTest
{
    static void Main(string[] args)
    {
        Bird bird = new Bird();
        Airplane airplane = new Airplane();

        bird.Fly();       // Outputs: Bird is flying.
        airplane.Fly();   // Outputs: Airplane is flying.
    }
}

Multilevel Inheritance

In multilevel inheritance, a class inherits from another class, which in turn inherits from another class. This forms a hierarchy of classes.

Imagine you're building a software system to model various types of vehicles. You want to create a hierarchy of classes that represent different kinds of vehicles, each with their own unique characteristics and behaviors. Let's consider three levels of inheritance: Vehicle, Car, and ElectricCar.

Vehicle (Base Class):

class Vehicle
{
    public string Brand { get; set; }
    public int Year { get; set; }

    public void Start()
    {
        Console.WriteLine("Vehicle starting.");
    }

    public void Stop()
    {
        Console.WriteLine("Vehicle stopping.");
    }
}

Car (Derived from Vehicle):

The Car class inherits from Vehicle and adds specific car-related attributes and behaviors

class Car : Vehicle
{
    public string Model { get; set; }

    public void Accelerate()
    {
        Console.WriteLine("Car accelerating.");
    }

    public void Brake()
    {
        Console.WriteLine("Car braking.");
    }
}

ElectricCar (Derived from Car):

  1. The ElectricCar class inherits from Car and adds behaviors related to electric cars.
class ElectricCar : Car
{
    public int BatteryCapacity { get; set; }

    public void Charge()
    {
        Console.WriteLine("Electric car is charging.");
    }

    public void DriveSilently()
    {
        Console.WriteLine("Electric car is driving silently.");
    }
}

These classes in a simple program:

class Program
{
    static void Main(string[] args)
    {
        ElectricCar teslaModelS = new ElectricCar
        {
            Brand = "Tesla",
            Model = "Model S",
            Year = 2023,
            BatteryCapacity = 100
        };

        teslaModelS.Start();       // Outputs: Vehicle starting.
        teslaModelS.Accelerate();  // Outputs: Car accelerating.
        teslaModelS.Charge();      // Outputs: Electric car is charging.
        teslaModelS.DriveSilently();// Outputs: Electric car is driving silently.
        teslaModelS.Stop();        // Outputs: Vehicle stopping.
    }
}
  • ElectricCar inherits all attributes and behaviors from Car, which in turn inherits from Vehicle.
  • You create an instance of ElectricCar, set its properties, and call methods from various levels of the inheritance hierarchy.
  • The multilevel inheritance allows you to model a more specific type of vehicle (electric car) while benefiting from the characteristics of the broader vehicle hierarchy.

Polymorphism

Polymorphism, in simple terms, is the ability of different objects to respond to the same method call in their own unique ways.

It enables you to write code that can work with objects of various types in a seamless and flexible manner.

There are two main types of polymorphism in C#


1) Compile-Time Polymorphism (Static Polymorphism):

This is achieved through method overloading and operator overloading. Method overloading allows you to define multiple methods in the same class with the same name but different parameter lists. The appropriate method to be called is determined at compile time-based on the number and types of arguments passed.

class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }
    
    public double Add(double a, double b)
    {
        return a + b;
    }
}

2) Run-Time Polymorphism (Dynamic Polymorphism):

Method overriding allows a subclass to provide a specific implementation of a method that is already defined in its base class. This enables you to call the overridden method on objects of the subclass while still adhering to the common interface of the base class.

class Shape
{
    public virtual void Draw()
    {
        Console.WriteLine("Drawing a shape.");
    }
}

class Circle : Shape
{
    public override void Draw()
    {
        Console.WriteLine("Drawing a circle.");
    }
}

the Circle the class overrides the Draw method from the Shape class. When you call the Draw method on an Circle object, the overridden method in the Circle class is executed.

Abstraction

It allows you to define the essential characteristics and behaviors of an object while hiding unnecessary details.

It helps in creating more understandable and maintainable code by focusing on what an object does rather than how it does it

Suppose we are building a simple application for a zoo. In this application, we have different types of animals, each of which can make a sound and move. We want to implement abstraction to define the common behaviors of animals while allowing specific animal types to provide their own implementations.

using System;

// Abstract class representing the concept of an Animal
abstract class Animal
{
    public string Name { get; set; }

    // Abstract methods representing behaviors
    public abstract void MakeSound();
    public abstract void Move();
}

// Concrete class representing a specific type of Animal: Lion
class Lion : Animal
{
    public Lion(string name)
    {
        Name = name;
    }

    public override void MakeSound()
    {
        Console.WriteLine($"{Name} the Lion roars.");
    }

    public override void Move()
    {
        Console.WriteLine($"{Name} the Lion prowls around.");
    }
}

// Concrete class representing another type of Animal: Eagle
class Eagle : Animal
{
    public Eagle(string name)
    {
        Name = name;
    }

    public override void MakeSound()
    {
        Console.WriteLine($"{Name} the Eagle screeches.");
    }

    public override void Move()
    {
        Console.WriteLine($"{Name} the Eagle soars in the sky.");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Lion lion = new Lion("Simba");
        lion.MakeSound();
        lion.Move();

        Eagle eagle = new Eagle("Aquila");
        eagle.MakeSound();
        eagle.Move();
    }
}
  1. The Animal class is an abstract class that defines two abstract methods: MakeSound() and Move(). It also has a property Name.

  2. The Lion and Eagle classes are concrete classes that inherit from the Animal class. They provide specific implementations for the MakeSound() and Move() methods.

Summary

Object-Oriented Programming (OOP) is a programming paradigm that revolves around the concept of objects, which are instances of classes acting as blueprints for creating these objects. OOP principles include encapsulation, where data and methods are bundled together within a class, promoting data security and controlled access.

Through these principles, OOP provides a structured approach for modeling real-world scenarios, creating modular and maintainable code, and supporting collaborative software development endeavors.