C# Programming Tutorial 0/45 lessons ~6 min read Lesson 16

    Encapsulation

    Encapsulation hides internal state behind controlled interfaces — private fields, public properties with validation, and methods that enforce invariants.

    Course progress0%
    Focus
    10 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    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

    1. Consumers interact through public API surface only.
    2. Setters validate and normalize input before assignment.
    3. Getters can compute derived values without stored field.
    4. Backing field convention: _camelCase private field.
    5. Required members force explicit initialization.
    6. Immutability: remove public setters; use constructor/init.

    Practical code example

    Bank account with encapsulated balance — no direct mutation:

    csharp
    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 _balance hides the raw balance from external code.
    • public Guid Id { get; } = Guid.NewGuid() exposes a read-only account identifier.
    • public decimal Balance => _balance is 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 += amount mutates internal state only after validation passes.
    • TryWithdraw(decimal amount) returns false instead of throwing for business-rule failures.
    • if (amount <= 0 || amount > _balance) return false guards both invalid and overdraft cases.
    • _balance -= amount applies the withdrawal only when funds are sufficient.
    • return true confirms 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?
    Bundling data and behavior; hiding internal state behind controlled access.
    Q2Beginnerprivate vs internal?
    private: class only; internal: entire assembly.
    Q3IntermediateWhy properties over public fields?
    Validation, versioning, computed values, and breaking change control.
    Q4IntermediateExpose read-only collection how?
    Private List; public IReadOnlyList property; or return copy.
    Q5AdvancedRefactor anemic domain model with public setters to rich model.
    Private setters; factory methods; behavior methods enforcing invariants; domain events on state change.

    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.

    Ready to mark this lesson complete?Track your journey across the entire course.