Encapsulation, Inheritance, and Polymorphism in C#
Learn encapsulation, inheritance, and polymorphism in C# with clear examples to understand core OOP principles and real use cases.
Object-Oriented Programming (OOP) is an approach developed to write reusable, maintainable, and understandable code in software development. The three main pillars of OOP are:
- Encapsulation → Hiding data and providing controlled access
- Inheritance → Passing common behaviors down to subclasses
- Polymorphism → The same method behaving differently in different objects
When these three features are used together, a solid architecture can be built in software projects. Let’s now examine each of them individually.
Encapsulation
Encapsulation hides the internal structure of a class from the outside world, exposing only what is necessary.
This prevents direct manipulation of data, reduces errors, and ensures that internal logic changes do not affect external code.
In C#, it is implemented using access modifiers such as private, protected, internal, and public.
Typically, fields are hidden, and controlled access is provided through properties or methods.
public class BankAccount
{
private decimal _balance; // direct access restricted
public decimal Balance
{
get => _balance;
private set => _balance = value;
}
public void Deposit(decimal amount)
{
if (amount <= 0)
throw new ArgumentException("Amount must be greater than zero.");
Balance += amount;
}
public void Withdraw(decimal amount)
{
if (amount > Balance)
throw new InvalidOperationException("Insufficient funds.");
Balance -= amount;
}
}
In this example, the balance (_balance) cannot be modified directly.
The user can only interact with it via Deposit and Withdraw methods.
This prevents misuse and ensures data safety.
Inheritance
Inheritance allows a class to derive properties and behaviors from another class. This way, common code is centralized and becomes reusable. The base class defines general behaviors, while derived classes add specialized ones.
public class Animal
{
public string Name { get; set; } = string.Empty;
public void Eat()
{
Console.WriteLine($"{Name} is eating.");
}
}
public class Dog : Animal
{
public void Bark()
{
Console.WriteLine($"{Name} is barking.");
}
}
public class Cat : Animal
{
public void Meow()
{
Console.WriteLine($"{Name} is meowing.");
}
}
class Program
{
static void Main()
{
Dog d = new Dog { Name = "Buddy" };
d.Eat(); // inherited from Animal
d.Bark(); // Dog-specific
Cat c = new Cat { Name = "Misty" };
c.Eat(); // inherited from Animal
c.Meow(); // Cat-specific
}
}
Here, both Dog and Cat inherit from Animal.
The shared behavior Eat() is written only once but is available in all subclasses.
Polymorphism
Polymorphism means the same method can behave differently depending on the object. There are two main types of polymorphism:
- Compile-time Polymorphism: Achieved with method overloading.
- Runtime Polymorphism: Achieved with
virtualmethods andoverride.
public class Animal
{
public string Name { get; set; } = string.Empty;
public virtual void Speak()
{
Console.WriteLine($"{Name} makes a sound.");
}
}
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine($"{Name} barks.");
}
}
public class Cat : Animal
{
public override void Speak()
{
Console.WriteLine($"{Name} meows.");
}
}
class Program
{
static void Main()
{
Animal a1 = new Dog { Name = "Buddy" };
Animal a2 = new Cat { Name = "Misty" };
a1.Speak(); // Buddy barks.
a2.Speak(); // Misty meows.
}
}
The same Speak() call produces different output depending on the object type.
This flexibility is one of the strongest features of OOP.
Example: Book Types
Let’s model books in a library application.
Shared properties are defined in the Book class, while each subclass adds its own information or overrides
the PrintInfo() method.
Additionally, Author and Publisher classes are used to hold related information in separate objects.
This demonstrates how inheritance and polymorphism can be combined.
public class Author
{
public string Name { get; set; } = string.Empty;
public string Country { get; set; } = string.Empty;
}
public class Publisher
{
public string Name { get; set; } = string.Empty;
public string Address { get; set; } = string.Empty;
}
public abstract class Book
{
public string Title { get; set; } = string.Empty;
public Author BookAuthor { get; set; } = new Author();
public Publisher BookPublisher { get; set; } = new Publisher();
public int Pages { get; set; }
public string ISBN { get; set; } = string.Empty;
public int Year { get; set; }
// To be customized in subclasses
public abstract void PrintInfo();
}
public class ScienceBook : Book
{
public string Field { get; set; } = string.Empty; // Physics, Chemistry, Biology
public override void PrintInfo()
{
Console.WriteLine($"[Science Book] {Title} - {BookAuthor.Name}, {Pages} pages");
Console.WriteLine($"Field: {Field}, ISBN: {ISBN}, Publisher: {BookPublisher.Name}");
}
}
public class HistoryBook : Book
{
public string Period { get; set; } = string.Empty; // Ancient, Medieval, etc.
public override void PrintInfo()
{
Console.WriteLine($"[History Book] {Title} - {BookAuthor.Name}, {Pages} pages");
Console.WriteLine($"Period: {Period}, ISBN: {ISBN}, Publisher: {BookPublisher.Name}");
}
}
class Program
{
static void Main()
{
var publisher = new Publisher { Name = "Knowledge Press", Address = "New York" };
var author1 = new Author { Name = "Albert Einstein", Country = "Germany" };
var author2 = new Author { Name = "Halil Inalcik", Country = "Turkey" };
Book b1 = new ScienceBook
{
Title = "Theory of Relativity",
BookAuthor = author1,
BookPublisher = publisher,
Pages = 250,
ISBN = "123-456-789",
Year = 1920,
Field = "Physics"
};
Book b2 = new HistoryBook
{
Title = "The Ottoman Empire",
BookAuthor = author2,
BookPublisher = publisher,
Pages = 500,
ISBN = "987-654-321",
Year = 1973,
Period = "Ottoman Era"
};
b1.PrintInfo();
Console.WriteLine();
b2.PrintInfo();
}
}
Advantages
- Encapsulation: Provides data security, prevents misuse.
- Inheritance: Reduces code duplication, establishes a hierarchical structure.
- Polymorphism: Creates a flexible and extensible architecture.
TL;DR
- Encapsulation: Hides internal data and provides controlled access.
- Inheritance: Properties and behaviors of a base class are passed to subclasses.
- Polymorphism: The same method can behave differently in different subclasses.
Related Articles
Class, Object, Property and Methods in C#
Learn how classes, objects, properties, and methods work in C# and form the core building blocks of object-oriented programming.
Constructors, Destructors and this in C#
Learn how constructors, destructors, and the this keyword work in C# to manage object lifecycle and class-level access.
Dependency Injection Basics in C#
Learn the basics of Dependency Injection in C#, including managing dependencies, loose coupling, and improving testability.
Interfaces and Abstract Classes in C#
Learn interfaces and abstract classes in C#, their differences, and when to use each approach to design clean and extensible code.
Record Types and Immutable Objects in C#
Learn C# record types and immutable objects, including value equality, with-expressions, and patterns for building safer data models.
SOLID Principles with C#
Applying SOLID principles in C# with examples: building flexible, maintainable, and testable code.
Structs in C# – Differences from Classes
Learn the key differences between structs and classes in C#, including memory model, inheritance, boxing, and performance.