Classes, Objects, Encapsulation and Access Modifiers

A class is a blueprint: it declares the data (fields and properties) and behaviour (methods) of a kind of thing.

Concept

Classes and Objects A class is a blueprint: it declares the data (fields and properties) and behaviour (methods) of a kind of thing. An object (instance) is created from a class with new and lives on the managed heap; variables hold references to it.

Stores data (private double _capacityKwh;) Controlled access to data (public double StoredKwh { get; private set; }) Behaviour (Charge, Discharge) Initializes a new object (Module 14)

Belongs to the class itself, shared by all instances Encapsulation means hiding internal state and exposing only operations that keep the object valid. The BatteryPack below guarantees 0 ≤ StoredKwh ≤ capacity because no outside code can set StoredKwh directly:

Fields are private.

Properties expose reads publicly and restrict writes (private set).

Methods validate arguments (ArgumentOutOfRangeException.ThrowIfNegative) before changing state.

Only the containing type (default for class members) The containing type and derived types

Any code in the same assembly (default for top-level types) protected internal Same assembly or derived types private protected Derived types in the same assembly

Why It Matters

Encapsulation is what makes large programs maintainable: when only a few methods can change state, bugs are easier to find and invariants cannot be broken accidentally. Access modifiers define your public API — everything public is a promise to callers.

Syntax

[access] class Name
{
    private Type _field;
    public Type Property { get; private set; }
    public ReturnType Method(params) { ... }
}
var obj = new Name();

Example

13.1 A class with encapsulated state 13.2 Access modifiers

// Objects are created from classes with 'new'
var battery = new BatteryPack("PACK-01", capacityKwh: 75);
battery.Charge(20);
battery.Discharge(5);
Console.WriteLine(battery);

try
{
    battery.Charge(-3);          // validation protects the object's invariants
}
catch (ArgumentOutOfRangeException ex)
{
    Console.WriteLine($"Rejected: {ex.Message}");
}

// Encapsulation: state can only change through methods that enforce the rules
// battery.StoredKwh = 1000;    // does not compile: the setter is private
Console.WriteLine($"State of charge: {battery.StateOfCharge:P0}");
Console.WriteLine($"Packs created so far: {BatteryPack.Count}");

var second = new BatteryPack("PACK-02", 60);
Console.WriteLine($"Packs created so far: {BatteryPack.Count}, same object? {ReferenceEquals(battery, second)}");

public class BatteryPack
{
    // Fields: private data
    private readonly double _capacityKwh;
    private static int _count;                       // shared by all instances

    // Properties: controlled access
    public string Id { get; }                        // read-only after construction
    public double StoredKwh { get; private set; }    // public get, private set
    public dou
…
// Access modifiers inside one assembly (this program)
var d = new Derived();
d.Show();
Console.WriteLine($"public: {d.PublicValue}, internal: {d.InternalValue}, protected internal: {d.ProtectedInternalValue}");

public class Base
{
    public int PublicValue = 1;                     // everyone
    private int _privateValue = 2;                  // only this class
    protected int ProtectedValue = 3;               // this class and derived classes
    internal int InternalValue = 4;                 // any code in the same assembly
    protected internal int ProtectedInternalValue = 5;  // same assembly OR derived classes
    private protected int PrivateProtectedValue = 6;    // derived classes in the same assembly

    public int ReadPrivate() => _privateValue;
}

public class Derived : Base
{
    public void Show() =>
        Console.WriteLine($"derived sees protected={ProtectedValue}, private protected={PrivateProtectedValue}, private via method={ReadPrivate()}");
}

Expected Result

PACK-01: 15.0/75 kWh
Rejected: kwh ('-3') must be a non-negative value. (Parameter 'kwh')
Actual value was -3.
State of charge: 20%
Packs created so far: 1
Packs created so far: 2, same object? False
derived sees protected=3, private protected=6, private via method=2
public: 1, internal: 4, protected internal: 5

Common Mistakes

Accessing private members from outside — real compiler errors:

Making every field public — expose properties and methods instead.

"Anemic" classes: data-only classes whose rules live elsewhere, so invalid states are possible.

God classes that do everything — split by responsibility.

Forgetting that the default for class members is private and for top-level classes is internal.

var b = new Box();
b._secret = 5;
Console.WriteLine(b.Size);
b.Size = 3;

class Box
{
    private int _secret;
    public int Size { get; private set; }
}
m13_err.cs(2,3): error CS0122: 'Box._secret' is inaccessible due to its protection level
m13_err.cs(4,1): error CS0272: The property or indexer 'Box.Size' cannot be used in this context because the set accessor is inaccessible

Best Practices

Start with the most restrictive access and widen only when needed.

Validate in constructors and methods so objects can never be invalid.

Use readonly fields and get-only properties for data that never changes.

Keep classes small and cohesive; name them with nouns.

Interview Relevance

Very high: "What is encapsulation?", "class vs object", "difference between protected internal and private protected", "field vs property", "what does static mean?".

Practice Questions

Write a BankAccount class with Deposit, Withdraw (no overdraft) and a read-only Balance.

Add a static counter of created accounts.

Which access modifier lets a derived class in another assembly see a member but not unrelated classes in that assembly?

Make the BatteryPack reject a capacity above 200 kWh.

Quick Quiz

Q1. (Multiple choice) Default accessibility of a class member without a modifier? A) public B) internal C) private D) protected Answer: C.

Q2. (True/False) A private protected member is visible to derived classes in other assemblies.

Answer: False. Only derived classes in the same assembly.

Q3. (Predict the output) How many packs does the example report after creating the second one?

Answer: 2 — the static counter is shared by all instances (verified).

Q4. (Find the bug) battery.StoredKwh = 1000; does not compile. Why?

Answer: The setter is private (error CS0272, verified); state changes must go through Charge/Discharge.

Coding Challenge

Write a Thermostat class whose TargetC can only be set between 5 and 30 (throw otherwise), with a method bool NeedsHeating(double currentC).

var t = new Thermostat();
t.TargetC = 21;
Console.WriteLine(t.NeedsHeating(19.5));
try { t.TargetC = 40; } catch (ArgumentOutOfRangeException) { Console.WriteLine("rejected 40"); }

class Thermostat
{
    private double _targetC = 20;
    public double TargetC
    {
        get => _targetC;
        set
        {
            if (value is < 5 or > 30) throw new ArgumentOutOfRangeException(nameof(value));
            _targetC = value;
        }
    }
    public bool NeedsHeating(double currentC) => currentC < TargetC;
}