Encapsulation
Encapsulation hides internal state behind controlled interfaces — private fields, public properties with validation, and methods that enforce invariants.
Introduction
Encapsulation hides internal state behind controlled interfaces — private fields, public properties with validation, and methods that enforce invariants. It prevents callers from putting objects into invalid states, a requirement in banking balances and medical dosage models.
C# offers access modifiers (private, protected, internal, public), property accessors, and init-only setters. Interviewers link encapsulation to SOLID and ask you to refactor public fields into properties with validation.
Strong encapsulation enables refactoring internals without breaking consumers — essential for library and API design.
The story
A retail bank never exposes account balances as public fields that any caller could set to arbitrary values. Instead, customers deposit and withdraw through methods that enforce rules: deposits must be positive, withdrawals fail silently when funds are insufficient, and the balance property is read-only from outside the class.
That is encapsulation in action — the internal _balance field stays private while the public API stays safe and predictable.
Understanding the topic
Key concepts
- private hides members from outside class.
- protected visible to derived classes; internal to assembly.
- Properties wrap fields with get/set logic.
- init allows set only during initialization.
- file modifier restricts to same source file (C# 11).
- Invariant: rule always true — enforce in property setters.
Step-by-step explanation
- Consumers interact through public API surface only.
- Setters validate and normalize input before assignment.
- Getters can compute derived values without stored field.
- Backing field convention: _camelCase private field.
- Required members force explicit initialization.
- Immutability: remove public setters; use constructor/init.
Practical code example
Bank account with encapsulated balance — no direct mutation:
namespace TechLearningPro.Encapsulation;public sealed class BankAccount{private decimal _balance;public Guid Id { get; } = Guid.NewGuid();public decimal Balance => _balance;public void Deposit(decimal amount){if (amount <= 0) throw new ArgumentOutOfRangeException(nameof(amount));_balance += amount;}public bool TryWithdraw(decimal amount){if (amount <= 0 || amount > _balance) return false;_balance -= amount;return true;}}
Line-by-line code explanation
private decimal _balancehides the raw balance from external code.public Guid Id { get; } = Guid.NewGuid()exposes a read-only account identifier.public decimal Balance => _balanceis an expression-bodied getter — read-only from outside.Deposit(decimal amount)is the controlled entry point for adding money.if (amount <= 0) throw new ArgumentOutOfRangeException(...)rejects invalid deposits at the boundary._balance += amountmutates internal state only after validation passes.TryWithdraw(decimal amount)returnsfalseinstead of throwing for business-rule failures.if (amount <= 0 || amount > _balance) return falseguards both invalid and overdraft cases._balance -= amountapplies the withdrawal only when funds are sufficient.return trueconfirms the withdrawal succeeded.
Key takeaway: Balance exposed read-only via expression-bodied getter. All mutations through methods enforcing rules — classic encapsulation interview answer.
Real-world use
Where you'll use this in production
- Account balances never negative via TryWithdraw.
- Medical dosage objects rejecting out-of-range values.
- API keys stored private, exposed as masked string.
- Configuration objects with validated setters.
Best practices
- Default to private; widen access only when needed.
- Never expose mutable collections publicly — return IReadOnlyList.
- Validate in one place — property or method, not both inconsistently.
- Use init for DTOs passed across layers.
- Document invariants in XML comments for public APIs.
Common mistakes
- public List
Items allowing external Add bypassing rules. - Empty setter with no validation.
- Leaking internal types through public method returns.
- Over-exposing protected when private suffices.
Advanced interview questions
Q1BeginnerWhat is encapsulation?
Q2Beginnerprivate vs internal?
Q3IntermediateWhy properties over public fields?
Q4IntermediateExpose read-only collection how?
Q5AdvancedRefactor anemic domain model with public setters to rich model.
Summary
Encapsulation protects invariants via access control and validation. Properties are the primary public surface for state. init and read-only properties support immutability. Never expose mutable internals directly. Next: inheritance and code reuse.