Paramclasses Academy - Blog

Tutorials Details

←
Post 12 of 30
→

What is Object-Oriented Programming in C# ?

What is Object-Oriented Programming in C# ?

October 26, 2026

Introduction

Object-Oriented Programming (OOP) is a programming approach that organizes code around objects and classes. Instead of writing an entire application as a collection of unrelated instructions, OOP helps developers structure programs into reusable components that combine data and behavior.

C# is an object-oriented programming language that supports the major OOP concepts, including encapsulation, inheritance, polymorphism, and abstraction.

For example, imagine you are building a school management system. The application may contain students, teachers, courses, and examinations. Each of these can be represented using classes and objects, with properties describing their data and methods describing their behavior.

In this tutorial, you will learn the fundamentals of OOP, how to implement its four main principles in C#, and how to combine these concepts in a practical project.

What you will learn:

  • What Object-Oriented Programming is and why it is useful

  • Classes and objects in C#

  • Fields, properties, constructors, and methods

  • Encapsulation and data protection

  • Inheritance and code reuse

  • Polymorphism and method overriding

  • Abstraction using abstract classes and interfaces

  • Static and instance members

  • Composition and relationships between objects

  • A practical OOP-based C# application

1. What Is Object-Oriented Programming?

Object-Oriented Programming is a software development paradigm in which programs are structured around objects. An object combines state (data) and behavior (operations).

For example, a Student object might contain:

Data:

  • Student name

  • Roll number

  • Age

  • Course

Behavior:

  • Display student information

  • Update contact details

  • Calculate a result

In C#, a class defines the structure and behavior that its objects can have.

Why do developers use OOP?

  1. Reusability: Existing classes can be reused in different parts of an application.

  2. Maintainability: Related data and behavior are organized together.

  3. Encapsulation: Internal state can be protected from invalid changes.

  4. Extensibility: Inheritance and interfaces allow new implementations to be introduced.

  5. Testability: Well-designed components can be tested independently.

  6. Scalability: Large applications can be divided into manageable components.

OOP is widely used in desktop applications, web applications, enterprise software, games, and many other kinds of software.

2. Understanding Classes and Objects

A class is a blueprint that defines the members an object can have. An object is an instance of a class.

Consider a class representing a student.

using System;

class Student
{
    public string Name { get; set; } = "";
    public int Age { get; set; }

    public void DisplayInfo()
    {
        Console.WriteLine($"Name: {Name}");
        Console.WriteLine($"Age: {Age}");
    }
}

class Program
{
    static void Main()
    {
        Student student1 = new Student();

        student1.Name = "Aman";
        student1.Age = 20;

        student1.DisplayInfo();
    }
}

Output:

Name: Aman
Age: 20

Understanding the code

  • class Student declares a class named Student.

  • Name and Age are properties that represent student data.

  • DisplayInfo() is a method that displays the object's data.

  • new Student() creates a new instance of the class.

  • student1 is a variable that refers to that object.

You can create multiple independent objects from the same class:

Student student1 = new Student
{
    Name = "Aman",
    Age = 20
};

Student student2 = new Student
{
    Name = "Priya",
    Age = 21
};

student1.DisplayInfo();
student2.DisplayInfo();

Each object maintains its own instance property values.

3. Fields, Properties, and Methods

Classes commonly contain fields, properties, and methods.

Fields

A field is a variable declared inside a type.

class Product
{
    private decimal price;
}

Fields are often private implementation details used to store an object's internal state.

Properties

Properties provide a controlled way to read or update data.

class Product
{
    public string Name { get; set; } = "";
    public decimal Price { get; set; }
}

The get accessor reads the value, and the set accessor assigns it. Auto-implemented properties let C# manage the backing storage automatically.

Methods

Methods define behavior.

class Product
{
    public string Name { get; set; } = "";
    public decimal Price { get; set; }

    public void Display()
    {
        Console.WriteLine($"{Name}: {Price:C}");
    }
}

Keeping data and related behavior together makes the class easier to understand.

4. The Four Main Principles of OOP

The four commonly taught principles of OOP are:

<text>1. Encapsulation — control access to an object's internal state.</text>

<text>2. Inheritance — derive a class from another class to reuse or specialize behavior.</text>

<text>3. Polymorphism — use a common interface or base type with different implementations.</text>

<text>4. Abstraction — expose essential operations while hiding unnecessary implementation details.</text>

These principles often work together rather than being used independently.

5. Encapsulation in C#

Encapsulation means keeping an object's internal state and the rules governing that state together, while controlling how other code accesses or changes it.

For example, a bank account should not allow arbitrary code to set its balance to a negative value without applying business rules.

using System;

class BankAccount
{
    public string AccountHolder { get; }

    public decimal Balance { get; private set; }

    public BankAccount(string accountHolder, decimal openingBalance)
    {
        if (openingBalance < 0)
        {
            throw new ArgumentOutOfRangeException(
                nameof(openingBalance),
                "Opening balance cannot be negative.");
        }

        AccountHolder = accountHolder;
        Balance = openingBalance;
    }

    public void Deposit(decimal amount)
    {
        if (amount <= 0)
        {
            throw new ArgumentOutOfRangeException(
                nameof(amount),
                "Deposit amount must be positive.");
        }

        Balance += amount;
    }

    public bool Withdraw(decimal amount)
    {
        if (amount <= 0)
        {
            throw new ArgumentOutOfRangeException(
                nameof(amount),
                "Withdrawal amount must be positive.");
        }

        if (amount > Balance)
        {
            return false;
        }

        Balance -= amount;
        return true;
    }
}

class Program
{
    static void Main()
    {
        BankAccount account = new BankAccount("Aman", 1000m);

        account.Deposit(500m);
        bool successful = account.Withdraw(200m);

        Console.WriteLine($"Balance: {account.Balance}");
        Console.WriteLine($"Withdrawal successful: {successful}");
    }
}

Output:

Balance: 1300
Withdrawal successful: True

The balance is exposed through a public getter but has a private setter. External code cannot directly assign a new balance; it must use the methods that enforce the account's rules.

This is a practical use of encapsulation.

6. Inheritance in C#

Inheritance allows a class to derive from another class. The derived class inherits accessible members and can add or specialize functionality.

For example, Car and Motorcycle can both derive from a common Vehicle class.

using System;

class Vehicle
{
    public string Brand { get; set; } = "";

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

class Car : Vehicle
{
    public int NumberOfDoors { get; set; }
}

class Motorcycle : Vehicle
{
    public bool HasSidecar { get; set; }
}

class Program
{
    static void Main()
    {
        Car car = new Car
        {
            Brand = "Toyota",
            NumberOfDoors = 4
        };

        Motorcycle bike = new Motorcycle
        {
            Brand = "Honda",
            HasSidecar = false
        };

        car.Start();
        bike.Start();
    }
}

Output:

Toyota is starting.
Honda is starting.

Both derived classes can use the inherited Brand property and Start() method.

Important inheritance facts

  • C# classes support single class inheritance: a class can have one direct base class.

  • A class can implement multiple interfaces.

  • A derived class can add new members or override suitable inherited virtual members.

  • Constructors are not inherited as ordinary members, although a derived constructor can invoke a base constructor.

  • Inheritance should represent a meaningful relationship, not simply be used to avoid writing a few lines of code.

7. Polymorphism in C#

Polymorphism means that code can work through a common type while different objects provide different behavior.

In C#, runtime polymorphism is commonly implemented using virtual methods and overriding.

using System;

class Animal
{
    public virtual void MakeSound()
    {
        Console.WriteLine("Animal makes a sound.");
    }
}

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

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

class Program
{
    static void Main()
    {
        Animal animal1 = new Dog();
        Animal animal2 = new Cat();

        animal1.MakeSound();
        animal2.MakeSound();
    }
}

Output:

Dog barks.
Cat meows.

Although the variables are declared using the Animal type, the overridden method belonging to the actual object executes.

Compile-time polymorphism

Method overloading is another form of polymorphism commonly taught in C#.

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

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

The compiler chooses the applicable overload based on the arguments and overload-resolution rules.

Remember: Overloading uses different parameter lists. Overriding provides a derived implementation of an eligible inherited virtual member.

8. Abstraction in C#

Abstraction exposes the operations that callers need while hiding the details of how those operations are implemented.

C# supports abstraction through abstract classes and interfaces.

Abstract classes

An abstract class cannot be instantiated directly. It can define common members and require derived classes to implement abstract members.

using System;

abstract class Shape
{
    public abstract double CalculateArea();

    public void Display()
    {
        Console.WriteLine($"Area: {CalculateArea():F2}");
    }
}

class Circle : Shape
{
    public double Radius { get; }

    public Circle(double radius)
    {
        if (radius <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(radius));
        }

        Radius = radius;
    }

    public override double CalculateArea()
    {
        return Math.PI * Radius * Radius;
    }
}

class Program
{
    static void Main()
    {
        Shape shape = new Circle(5);
        shape.Display();
    }
}

Output:

Area: 78.54

The caller works with a Shape reference without needing to know the area formula for every possible shape.

Interfaces

An interface defines a contract that implementing types agree to support.

using System;

interface IPayment
{
    void Pay(decimal amount);
}

class CardPayment : IPayment
{
    public void Pay(decimal amount)
    {
        Console.WriteLine($"Paid {amount:C} by card.");
    }
}

class UpiPayment : IPayment
{
    public void Pay(decimal amount)
    {
        Console.WriteLine($"Paid {amount:C} using UPI.");
    }
}

class Program
{
    static void ProcessPayment(IPayment payment, decimal amount)
    {
        payment.Pay(amount);
    }

    static void Main()
    {
        ProcessPayment(new CardPayment(), 500m);
        ProcessPayment(new UpiPayment(), 300m);
    }
}

Output: The program displays a card payment message followed by a UPI payment message.

The ProcessPayment() method depends on the IPayment contract instead of depending on one specific payment implementation.

Abstract class vs interface

Feature Abstract class Interface
Main purpose Share a common base and related behavior Define a contract
Class inheritance A class can derive from one direct base class A class can implement multiple interfaces
State Can contain instance fields and state Does not provide ordinary per-instance fields
Constructors Can have constructors Cannot be instantiated and does not have instance constructors
Typical use Closely related types with shared implementation Types that need to support a common capability

Modern C# interfaces can also contain certain implemented members, so the distinction is not simply that every interface member must always be abstract.

9. Access Modifiers in OOP

Access modifiers determine where members can be accessed.

Modifier General meaning
public Accessible wherever the containing type and member are accessible
private Accessible only within the containing type
protected Accessible within the containing type and suitable derived types
internal Accessible within the same assembly
protected internal Accessible within the same assembly or from derived types
private protected Accessible within the containing assembly and derived types

Example:

class Employee
{
    public string Name { get; set; } = "";

    private decimal salary;

    public void SetSalary(decimal amount)
    {
        if (amount >= 0)
        {
            salary = amount;
        }
    }

    public decimal GetSalary()
    {
        return salary;
    }
}

The salary field cannot be accessed directly from outside the class. The class controls access through its public methods.

In real applications, properties and methods should enforce the business rules relevant to the data they manage.

10. Constructors in OOP

A constructor initializes an object when it is created. It typically establishes the object's initial state.

using System;

class Employee
{
    public string Name { get; }
    public string Department { get; }

    public Employee(string name, string department)
    {
        Name = name;
        Department = department;
    }

    public void Display()
    {
        Console.WriteLine($"{Name} works in {Department}.");
    }
}

class Program
{
    static void Main()
    {
        Employee employee = new Employee("Priya", "IT");
        employee.Display();
    }
}

Output:

Priya works in IT.

Constructors are useful for making sure an object begins in a valid state.

11. Static Members vs Instance Members

An instance member belongs to an object. A static member belongs to the type itself.

using System;

class Employee
{
    public string Name { get; set; } = "";

    public static int EmployeeCount { get; private set; }

    public Employee(string name)
    {
        Name = name;
        EmployeeCount++;
    }
}

class Program
{
    static void Main()
    {
        Employee e1 = new Employee("Aman");
        Employee e2 = new Employee("Priya");

        Console.WriteLine(e1.Name);
        Console.WriteLine(e2.Name);
        Console.WriteLine(Employee.EmployeeCount);
    }
}

Output:

Aman
Priya
2

Each employee has an individual name, while EmployeeCount is shared by the Employee type.

This example illustrates the difference between instance-specific state and type-level state.

12. Composition: Building Objects from Other Objects

Composition is a design technique in which an object contains or coordinates other objects to perform a task.

For example, an order can contain order items. Each item can represent a product and its quantity.

using System;
using System.Collections.Generic;
using System.Linq;

class OrderItem
{
    public string ProductName { get; }
    public decimal Price { get; }
    public int Quantity { get; }

    public OrderItem(string productName, decimal price, int quantity)
    {
        if (price < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(price));
        }

        if (quantity <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(quantity));
        }

        ProductName = productName;
        Price = price;
        Quantity = quantity;
    }

    public decimal GetSubtotal()
    {
        return Price * Quantity;
    }
}

class Order
{
    private readonly List<OrderItem> items = new();

    public void AddItem(OrderItem item)
    {
        items.Add(item);
    }

    public decimal CalculateTotal()
    {
        return items.Sum(item => item.GetSubtotal());
    }
}

class Program
{
    static void Main()
    {
        Order order = new Order();

        order.AddItem(new OrderItem("Keyboard", 1500m, 2));
        order.AddItem(new OrderItem("Mouse", 700m, 1));

        Console.WriteLine($"Order Total: {order.CalculateTotal():C}");
    }
}

The order contains a collection of order items and delegates subtotal calculation to each item. This separates responsibilities and makes the design easier to extend.

Composition is often preferable to inheritance when one object uses or contains another object rather than being a specialized form of it.

13. Practical Project: Employee Management System

Let's combine classes, objects, encapsulation, inheritance, and polymorphism in one small console application.

The example defines a base Employee class, a derived FullTimeEmployee class, and a derived ContractEmployee class. Each employee calculates monthly pay differently.

using System;
using System.Collections.Generic;

abstract class Employee
{
    public string Name { get; }

    protected Employee(string name)
    {
        Name = name;
    }

    public abstract decimal CalculateMonthlyPay();

    public virtual void DisplayDetails()
    {
        Console.WriteLine($"Employee: {Name}");
        Console.WriteLine($"Monthly Pay: {CalculateMonthlyPay():C}");
    }
}

class FullTimeEmployee : Employee
{
    public decimal MonthlySalary { get; }

    public FullTimeEmployee(string name, decimal monthlySalary)
        : base(name)
    {
        if (monthlySalary < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(monthlySalary));
        }

        MonthlySalary = monthlySalary;
    }

    public override decimal CalculateMonthlyPay()
    {
        return MonthlySalary;
    }
}

class ContractEmployee : Employee
{
    public decimal HourlyRate { get; }
    public int HoursWorked { get; }

    public ContractEmployee(
        string name,
        decimal hourlyRate,
        int hoursWorked)
        : base(name)
    {
        if (hourlyRate < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(hourlyRate));
        }

        if (hoursWorked < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(hoursWorked));
        }

        HourlyRate = hourlyRate;
        HoursWorked = hoursWorked;
    }

    public override decimal CalculateMonthlyPay()
    {
        return HourlyRate * HoursWorked;
    }
}

class Program
{
    static void Main()
    {
        List<Employee> employees = new()
        {
            new FullTimeEmployee("Aman", 50000m),
            new ContractEmployee("Priya", 500m, 80)
        };

        foreach (Employee employee in employees)
        {
            employee.DisplayDetails();
            Console.WriteLine();
        }
    }
}

Illustrative output:

Employee: Aman
Monthly Pay: ₹50,000.00

Employee: Priya
Monthly Pay: ₹40,000.00

Currency formatting depends on the operating system's current culture; the values above illustrate the intended results.

How the project demonstrates OOP

  • Class and object: Employee, FullTimeEmployee, and ContractEmployee define types used to create objects.

  • Encapsulation: Employee data is exposed through controlled properties, and invalid salary inputs are rejected.

  • Inheritance: Both employee types derive from the Employee base class.

  • Abstraction: Employee is abstract and defines the required CalculateMonthlyPay() operation.

  • Polymorphism: The loop uses Employee references while each derived object supplies its own pay calculation.

  • Collections: A List<Employee> holds different employee implementations together.

For a production payroll system, you would also need to define tax rules, overtime policies, currency handling, persistence, and other business requirements. This example focuses on the OOP structure rather than complete payroll functionality.

14. Common OOP Mistakes to Avoid

Mistake 1: Making every member public

Public fields and unrestricted setters allow external code to bypass important rules. Use suitable access modifiers and expose only the operations that callers need.

Mistake 2: Using inheritance for unrelated types

Inheritance should express a meaningful “is-a” relationship. If a class only needs to use another class's functionality, composition may be a better fit.

Mistake 3: Confusing overloading with overriding

Overloading means the same method name has different parameter lists. Overriding replaces an eligible inherited virtual implementation in a derived class.

Mistake 4: Creating classes with too many responsibilities

A single class that manages user accounts, sends email, calculates payments, and writes reports can become difficult to maintain. Separate responsibilities where it improves clarity.

Mistake 5: Ignoring object initialization

Objects should start in a valid state. Use constructors, property validation, and appropriate factory methods where necessary.

Mistake 6: Assuming abstraction always requires an abstract class

Interfaces are also a core abstraction mechanism. Choose between an interface, an abstract class, or a concrete class according to the design requirements.

15. Best Practices for OOP in C#

  • Give classes and methods clear, descriptive names.

  • Keep each class focused on a coherent responsibility.

  • Use encapsulation to protect important invariants.

  • Prefer composition over inheritance when the relationship does not represent a true specialization.

  • Program against interfaces when different implementations need to be interchangeable.

  • Keep constructors focused on establishing valid object state.

  • Avoid unnecessary inheritance hierarchies.

  • Use polymorphism when different types need to respond to the same operation in different ways.

  • Keep methods and properties understandable and predictable.

  • Test important business rules independently.

  • Use access modifiers intentionally rather than making every member public.

  • Introduce design patterns only when they solve a real design problem.

16. Practice Exercises

Complete these exercises to strengthen your understanding.

Exercise 1: Student class

Create a Student class with a name, roll number, and a method that displays student details.

Exercise 2: Bank account

Create a BankAccount class that supports deposits and withdrawals while preventing invalid transactions.

Exercise 3: Inheritance

Create a Person base class and derive Student and Teacher classes from it.

Exercise 4: Polymorphism

Create a Shape base class with an area-calculation method. Implement the method in Circle and Rectangle.

Exercise 5: Abstraction

Create an INotification interface with a Send() method. Implement it in EmailNotification and SmsNotification classes.

Exercise 6: Composition

Create an Order class containing multiple OrderItem objects. Calculate the total price of all items.

Exercise 7: Employee management

Extend the employee project to include a PartTimeEmployee type and display all employees using a single collection of the base type.

17. Knowledge Check Quiz

Q1. What is a class in C#?

A. A blueprint for creating objects
B. A loop
C. A database table in every case
D. A return statement

Q2. Which OOP principle controls access to internal state?

A. Inheritance
B. Encapsulation
C. Polymorphism
D. Compilation

Q3. Which keyword is commonly used to override a virtual method?

A. new
B. static
C. override
D. sealed

Q4. Can a C# class directly inherit from two classes?

A. Yes
B. No

Q5. Which C# feature defines a contract that a class can implement?

A. Interface
B. Constructor
C. Namespace
D. Local variable

Q6. What is runtime polymorphism commonly achieved with?

A. Virtual methods and overriding
B. Comments
C. Variable declarations only
D. Console output

Answers: 1-A, 2-B, 3-C, 4-B, 5-A, 6-A.

18. Frequently Asked Questions

1. Is C# an object-oriented programming language?

Yes. C# supports classes, objects, encapsulation, inheritance, polymorphism, and abstraction. It also supports other programming styles, including functional programming techniques.

2. What is the difference between a class and an object?

A class defines a type and its members. An object is an instance of that type with its own instance state.

3. What are the four pillars of OOP?

They are encapsulation, inheritance, polymorphism, and abstraction.

4. What is the difference between inheritance and composition?

Inheritance represents a specialization relationship. Composition builds functionality by combining objects that collaborate with one another.

5. What is the difference between an abstract class and an interface?

An abstract class can provide shared implementation and instance state for a related hierarchy. An interface defines a contract that multiple, potentially unrelated types can implement.

6. Is OOP required in every C# program?

No. Small programs can often be written without elaborate class hierarchies. OOP is especially useful when organizing larger applications with related data, behavior, and business rules.

7. Can a C# class implement multiple interfaces?

Yes. A class can implement multiple interfaces, even though it can directly inherit from only one class.

8. Why is encapsulation important?

Encapsulation lets a class enforce rules around its state and prevents external code from changing that state arbitrarily.

Conclusion

Object-Oriented Programming helps developers organize software into reusable, maintainable components. Classes and objects form the foundation, while encapsulation, inheritance, polymorphism, and abstraction provide ways to protect data, share behavior, and support different implementations.

The best way to learn OOP is to practise with small projects such as student management, bank accounts, payment processing, and employee management. Focus on choosing clear responsibilities and relationships between classes instead of adding complexity unnecessarily.

In the next tutorial, we will explore Classes and Objects in C# with Real-Life Examples in greater detail.

←
Post 12 of 30
→