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? Falsederived sees protected=3, private protected=6, private via method=2
public: 1, internal: 4, protected internal: 5Common 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 inaccessibleBest 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;
}