C# OOP Concepts: The Four Pillars of Object-Oriented Design
C# OOP concepts rest on four pillars: encapsulation, inheritance, polymorphism, and abstraction. Together they let you model real-world entities as classes and objects, keep state safe, reuse logic through hierarchies, and write code that responds flexibly to different types at runtime. This guide walks through each concept with the C# idioms you will actually reach for in everyday projects.
- C# OOP Concepts: The Four Pillars of Object-Oriented Design
- Encapsulation: Protecting Internal State
- Core Tools in C#
- Inheritance: Reusing and Extending Behavior
- How C# Implements Inheritance
- Polymorphism: One Interface, Many Behaviors
- Key Mechanisms
- Abstraction: Hiding Complexity Behind Contracts
- Putting It Together: A Practical Example
- When to Apply Each Concept
- Common Pitfalls to Avoid
More from this site
Keep reading the latest coverage
Encapsulation: Protecting Internal State
Encapsulation bundles data and the methods that operate on it inside a class, then restricts direct access to internals. In C# this is primarily achieved through access modifiers and properties. A field stays private; the public property exposes only what callers need, and validation logic lives in one place.
Core Tools in C#
- private — accessible only within the declaring class.
- protected — accessible within the class and its derived classes.
- public — accessible from any code with a reference.
- internal — accessible within the same assembly.
- Properties — get and set accessors that control reads and writes.
- Records — concise immutable data types that still respect encapsulation.
Well-encapsulated classes are easier to reason about and safer to change because no external code can silently corrupt an object's invariants.
Inheritance: Reusing and Extending Behavior
Inheritance lets a new class derive from an existing one, inheriting its fields, methods, and properties. In C#, a class can inherit from only one base class, which keeps the hierarchy simple while still enabling code reuse.
How C# Implements Inheritance
- Use a colon after the base class name: class Manager : Employee.
- The base keyword calls parent constructors or methods.
- Virtual methods enable the base to provide default behavior that derived classes can override.
- Sealed classes or methods prevent further inheritance when immutability of the hierarchy matters.
Prefer shallow inheritance trees. Deep hierarchies become brittle; prefer composition when the relationship is "has-a" rather than "is-a."
Polymorphism: One Interface, Many Behaviors
Polymorphism lets you treat objects of different types through a common interface, with the correct behavior selected at runtime. In C# this is achieved through virtual and override methods, interfaces, and, more recently, static abstract members in interfaces.
Key Mechanisms
- Virtual and override — a base class declares a virtual method; derived classes override it.
- Interfaces — define a contract that unrelated classes can implement.
- Abstract classes — provide partial implementation while forcing derived classes to fill in specific behavior.
- Pattern matching — lets you branch on type cleanly in switch expressions and is patterns.
Polymorphism is especially powerful when combined with dependency injection: consumers depend on abstractions, and concrete implementations are swapped in at composition time without touching client code.
Abstraction: Hiding Complexity Behind Contracts
Abstraction separates what an object does from how it does it. In C#, abstract classes and interfaces are the primary vehicles. An abstract class can hold shared state and partial implementation; an interface defines a pure contract with no implementation (prior to C# 8 default interface methods).
Good abstraction means the caller knows the method signature and its intent, but nothing about the internal algorithm. This decoupling makes it possible to swap implementations, write unit tests with stubs, and evolve internals without breaking consumers.
Putting It Together: A Practical Example
Consider a payment-processing system. An IPaymentGateway interface defines Charge and Refund. Concrete gateways for Stripe and PayPal implement the interface. An Order class encapsulates its total and payment status, accepts a gateway via constructor injection, and delegates the actual charge. A PremiumOrder might inherit from Order and override discount logic through polymorphism.
When to Apply Each Concept
| Concept | Use When | C# Idiom |
|---|---|---|
| Encapsulation | Protecting invariants and controlling access to state | Private fields with public properties |
| Inheritance | Modeling genuine "is-a" relationships and sharing base logic | Class derivation, base keyword |
| Polymorphism | Runtime selection of behavior across types | virtual/override, interfaces |
| Abstraction | Defining contracts and hiding implementation detail | Abstract classes, interfaces |
Common Pitfalls to Avoid
- Forcing inheritance where composition is more natural.
- Exposing mutable fields directly instead of using properties.
- Overriding methods without honoring the base contract (Liskov Substitution Principle).
- Creating "god" classes that mix unrelated responsibilities.
These pitfalls erode the benefits of C# OOP concepts and make code harder to test and maintain. Applying each pillar with intention keeps your object-oriented design clean, flexible, and idiomatic in C#.