Introduction
Object-Oriented Programming (OOP) is a programming paradigm widely used in C# and .NET development. Instead of organizing an application only around functions and procedures, OOP organizes code around objects that combine data and behavior.
For beginners, the four fundamental OOP concepts are:
Encapsulation
Inheritance
Polymorphism
Abstraction
These concepts are easier to understand when they are connected to a practical application rather than treated as separate definitions.
In this article, we will use a simple banking application to understand each concept with C# examples and see how the concepts work together.
What Is Object-Oriented Programming?
Object-Oriented Programming focuses on objects that contain both state and behavior.
For example, a bank account can have:
State
Account number
Account holder
Balance
Behavior
Deposit
Withdraw
Check balance
In C#, a class can represent the structure and behavior of such an object.
public class BankAccount
{
public string AccountNumber { get; set; }
public string AccountHolder { get; set; }
public decimal Balance { get; set; }
public void Deposit(decimal amount)
{
Balance += amount;
}
}
An object can then be created from the class:
BankAccount account = new BankAccount
{
AccountNumber = "ACC1001",
AccountHolder = "Rakesh",
Balance = 5000
};
account.Deposit(1000);
Console.WriteLine(account.Balance);
Output:
6000
The class defines the structure and behavior, while the object represents an actual instance.
Why Use OOP?
OOP can help developers organize larger applications by separating responsibilities into meaningful classes and relationships.
Common benefits include:
Encapsulation: Controls access to an object's internal state.
Reusability: Allows common behavior to be reused through appropriate class relationships and composition.
Maintainability: Keeps related data and behavior together.
Extensibility: Allows applications to support new implementations without unnecessarily changing existing code.
Abstraction: Hides implementation details that callers do not need to know.
However, OOP is not simply about creating as many classes as possible. Good object-oriented design also requires choosing appropriate responsibilities and relationships between objects.
The Four Pillars of OOP
The four commonly taught pillars are:
Encapsulation
Inheritance
Polymorphism
Abstraction
Let's examine each one using C#.
Encapsulation
Encapsulation means controlling how an object's internal state is accessed and modified.
Instead of allowing every part of an application to change a bank account's balance directly, the account can expose operations such as Deposit() and Withdraw().
Real-World Analogy: ATM
When using an ATM, you do not directly manipulate the bank's internal account records.
Instead, you interact with operations such as:
Withdraw money
Deposit money
Check balance
The banking system controls what happens internally.
The same idea can be applied to a C# class.
C# Example
Instead of exposing the balance as a freely writable property:
public decimal Balance { get; set; }
we can protect it:
public class BankAccount
{
private decimal _balance;
public decimal GetBalance()
{
return _balance;
}
public void Deposit(decimal amount)
{
if (amount <= 0)
throw new ArgumentException(
"Deposit amount must be greater than zero.");
_balance += amount;
}
public void Withdraw(decimal amount)
{
if (amount <= 0)
throw new ArgumentException(
"Withdrawal amount must be greater than zero.");
if (amount > _balance)
throw new InvalidOperationException(
"Insufficient balance.");
_balance -= amount;
}
}
Now external code cannot directly modify _balance.
Instead:
BankAccount account = new BankAccount();
account.Deposit(5000);
account.Withdraw(1500);
Console.WriteLine(account.GetBalance());
Output:
3500
The class controls how the balance changes.
Why Encapsulation Matters
Without encapsulation, another part of the application could potentially do something like:
account.Balance = -50000;
That could violate the business rules of the banking system.
By keeping the field private and exposing controlled operations, the class becomes responsible for protecting its own state.
Inheritance
Inheritance allows a class to derive from another class and reuse accessible members from the base class.
For example, different types of bank accounts may share common behavior.
We can create a base class:
public class BankAccount
{
public string AccountNumber { get; set; } = string.Empty;
public decimal Balance { get; protected set; }
public void Deposit(decimal amount)
{
if (amount <= 0)
throw new ArgumentException(
"Amount must be greater than zero.");
Balance += amount;
}
}
A savings account can inherit from it:
public class SavingsAccount : BankAccount
{
public decimal InterestRate { get; set; }
}
A current account can also inherit from it:
public class CurrentAccount : BankAccount
{
public decimal OverdraftLimit { get; set; }
}
Now both derived classes can use the common Deposit() behavior.
SavingsAccount savingsAccount = new SavingsAccount();
savingsAccount.Deposit(5000);
Console.WriteLine(savingsAccount.Balance);
Output:
5000
Real-World Analogy
Think about different types of vehicles.
A car and a bike are both vehicles. They may share common behavior such as starting and stopping while having their own specialized behavior.
The same relationship can be represented in C#:
Vehicle
/ \
/ \
Car Bike
Important Design Consideration
Inheritance should represent a genuine is-a relationship.
For example:
SavingsAccount is a BankAccount
makes sense.
But:
BankAccount is a Database
does not represent an appropriate inheritance relationship. Composition would be more appropriate in such a case.
Polymorphism
Polymorphism means that the same interface or operation can have different implementations.
In C#, polymorphism commonly appears through:
Method overloading
Method overriding
Interface-based programming
Compile-Time Polymorphism: Method Overloading
Method overloading allows multiple methods to have the same name but different parameter lists.
public class PaymentService
{
public void ProcessPayment(decimal amount)
{
Console.WriteLine(
$"Processing payment of {amount}");
}
public void ProcessPayment(
decimal amount,
string currency)
{
Console.WriteLine(
$"Processing {amount} {currency}");
}
}
The compiler determines which method should be called based on the arguments.
PaymentService service = new PaymentService();
service.ProcessPayment(1000);
service.ProcessPayment(1000, "USD");
Output:
Processing payment of 1000
Processing 1000 USD
Runtime Polymorphism: Method Overriding
Runtime polymorphism allows a derived class to provide its own implementation of a base-class method.
Consider different account types calculating interest differently.
public class BankAccount
{
public decimal Balance { get; set; }
public virtual decimal CalculateInterest()
{
return 0;
}
}
A savings account can override the method:
public class SavingsAccount : BankAccount
{
public override decimal CalculateInterest()
{
return Balance * 0.04m;
}
}
A premium account can provide another implementation:
public class PremiumAccount : BankAccount
{
public override decimal CalculateInterest()
{
return Balance * 0.06m;
}
}
Now the same method call can produce different results:
BankAccount account1 = new SavingsAccount
{
Balance = 10000
};
BankAccount account2 = new PremiumAccount
{
Balance = 10000
};
Console.WriteLine(account1.CalculateInterest());
Console.WriteLine(account2.CalculateInterest());
Output:
400.00
600.00
The variables have the same base type, but the runtime invokes the appropriate overridden implementation.
This is runtime polymorphism.
Abstraction
Abstraction means exposing the essential behavior while hiding implementation details.
A common way to implement abstraction in C# is through abstract classes or interfaces.
Real-World Analogy: ATM
When you withdraw money from an ATM, you know what operation you want to perform:
Withdraw Money
You do not need to know the internal implementation involving banking systems, transaction processing, validation, and database operations.
The interface exposed to the user is simpler than the implementation behind it.
C# Example with an Abstract Class
public abstract class PaymentMethod
{
public abstract void Pay(decimal amount);
}
Different payment methods can implement their own behavior.
public class CreditCardPayment : PaymentMethod
{
public override void Pay(decimal amount)
{
Console.WriteLine(
$"Paid {amount} using credit card.");
}
}
Another implementation:
public class UpiPayment : PaymentMethod
{
public override void Pay(decimal amount)
{
Console.WriteLine(
$"Paid {amount} using UPI.");
}
}
The caller only needs to work with PaymentMethod:
PaymentMethod payment =
new UpiPayment();
payment.Pay(1500);
Output:
Paid 1500 using UPI.
The caller does not need to know the internal details of the UPI payment implementation.
Abstraction vs Encapsulation
These two concepts are often confused.
Encapsulation | Abstraction |
|---|---|
Controls access to internal state and implementation | Exposes essential behavior while hiding unnecessary implementation details |
Commonly uses access modifiers | Commonly uses interfaces and abstract classes |
Protects object state | Simplifies how functionality is consumed |
Focuses on how access is controlled | Focuses on what functionality is exposed |
A simple way to remember the difference is:
Encapsulation → How do I protect the internals?
Abstraction → What should the caller need to know?
In real applications, the two concepts often work together.
Combining the Four OOP Concepts
The four principles are not isolated features.
A realistic application may use all of them together.
For example, consider a banking application:
BankAccount
|
+-----------+-----------+
| |
SavingsAccount CurrentAccount
| |
Interest Rules Overdraft Rules
Encapsulation protects account state.
Inheritance allows specialized account types to reuse common functionality.
Polymorphism allows different account types to implement behavior differently.
Abstraction allows other parts of the application to work with a general account or service contract without depending on implementation details.
Complete Example
The following example combines several OOP concepts:
using System;
public abstract class BankAccount
{
private decimal _balance;
public string AccountNumber { get; }
protected BankAccount(
string accountNumber,
decimal initialBalance)
{
AccountNumber = accountNumber;
_balance = initialBalance;
}
public decimal GetBalance()
{
return _balance;
}
public void Deposit(decimal amount)
{
if (amount <= 0)
throw new ArgumentException(
"Amount must be greater than zero.");
_balance += amount;
}
public abstract decimal CalculateInterest();
}
public class SavingsAccount : BankAccount
{
public SavingsAccount(
string accountNumber,
decimal initialBalance)
: base(accountNumber, initialBalance)
{
}
public override decimal CalculateInterest()
{
return GetBalance() * 0.04m;
}
}
public class PremiumAccount : BankAccount
{
public PremiumAccount(
string accountNumber,
decimal initialBalance)
: base(accountNumber, initialBalance)
{
}
public override decimal CalculateInterest()
{
return GetBalance() * 0.06m;
}
}
public class Program
{
public static void Main()
{
BankAccount savings =
new SavingsAccount("SAV001", 10000);
BankAccount premium =
new PremiumAccount("PRE001", 10000);
savings.Deposit(2000);
Console.WriteLine(
$"Savings Balance: {savings.GetBalance()}");
Console.WriteLine(
$"Savings Interest: {savings.CalculateInterest()}");
Console.WriteLine(
$"Premium Interest: {premium.CalculateInterest()}");
}
}
Output
Savings Balance: 12000
Savings Interest: 480.00
Premium Interest: 600.00
This single example demonstrates several concepts:
The private
_balancefield demonstrates encapsulation.SavingsAccountandPremiumAccountinherit fromBankAccount, demonstrating inheritance.CalculateInterest()has different implementations, demonstrating polymorphism.BankAccountis abstract and defines behavior without providing every implementation detail, demonstrating abstraction.
OOP in Real-World Applications
OOP principles are commonly used in many types of applications.
Banking Applications
Encapsulation can protect account and transaction state, while polymorphism can represent different account or payment behaviors.
E-Commerce Applications
Products, orders, customers, carts, and payment methods can be modeled as domain objects.
Different payment providers can implement a common payment interface:
IPayment
|
+-- CreditCardPayment
+-- UpiPayment
+-- PayPalPayment
Gaming Applications
A game may contain a common character abstraction with specialized player, enemy, and non-player character implementations.
Enterprise Applications
Interfaces and abstractions can separate business logic from infrastructure such as databases, messaging systems, and external services.
Common OOP Interview Questions
What are the four pillars of OOP?
They are:
Encapsulation
Inheritance
Polymorphism
Abstraction
What is the difference between overloading and overriding?
Overloading defines multiple methods with the same name but different parameter lists.
Overriding allows a derived class to replace a virtual or abstract base-class implementation.
Can a static method be overridden in C#?
No. Static methods belong to the type rather than an object instance and cannot participate in runtime overriding.
A derived class can hide a static member, but that is different from overriding it.
What is the difference between an abstract class and an interface?
An abstract class can contain state, constructors, implemented members, and abstract members.
An interface primarily defines a contract that implementing types agree to provide. Modern C# interfaces can also contain certain default implementations and static members, so the distinction is broader than simply "interfaces contain only methods."
Does inheritance always improve code reuse?
No.
Inheritance should be used when the relationship between the types makes sense. Otherwise, composition is often a better design choice.
Common Mistakes When Learning OOP
Treating OOP as Only Four Definitions
Memorizing the four pillars is not enough. Developers should understand when each concept improves a design.
Overusing Inheritance
Not every reusable component should become a base class.
Composition and dependency injection are often better alternatives.
Exposing Internal State
Making every field publicly writable can make it difficult to enforce business rules.
Use appropriate access modifiers and controlled operations.
Creating Classes Without Clear Responsibilities
A class should have a meaningful responsibility. Simply converting every noun in a requirement into a class can result in unnecessary complexity.
Confusing Abstraction with Encapsulation
They are related but solve different problems. Encapsulation controls access to internals, while abstraction simplifies what consumers need to interact with.
Key Takeaway
OOP provides a way to structure software around objects, their responsibilities, and their relationships.
The four fundamental concepts can be summarized as:
Encapsulation
↓
Protect the object's internal state
Inheritance
↓
Create specialized types from a base type
Polymorphism
↓
Allow different implementations of common behavior
Abstraction
↓
Expose essential behavior and hide implementation details
The real value of OOP comes from applying these concepts appropriately rather than using them simply because they are available.
Conclusion
Object-Oriented Programming is an important foundation for C# and .NET development. Encapsulation, inheritance, polymorphism, and abstraction provide different mechanisms for organizing code and managing complexity.
In the banking example, encapsulation protected the account balance, inheritance allowed specialized account types to share common behavior, polymorphism enabled different interest calculations, and abstraction provided a common model for working with different account implementations.
For beginners preparing for interviews, understanding the definitions is useful, but being able to explain why a particular design uses encapsulation, inheritance, polymorphism, or abstraction is much more valuable.
For the final C# Corner submission, the author should also add screenshots from their own execution environment and, if requested by the editor, attach the actual working POC source code. Any personal observations or real-world experiences should be added by the author in their own words rather than being fabricated.
Join the conversation! Your thoughts help the community grow.