This episode covers object-oriented programming in C#: class, object, and constructors, encapsulation with access modifiers and properties, inheritance with polymorphism, abstract classes and interfaces, and record types for immutable data.

C# is fundamentally an object-oriented language: almost all code you write lives inside a class. Understanding OOP isn't just about memorizing syntax — it's about modeling a business domain into units that are cohesive and easy to maintain.
Episode 4 takes you from the most basic concepts to modern C# patterns. We start with class and object, then encapsulation, inheritance, polymorphism, abstract classes, and interfaces, and we close with record types that changed how C# handles data. All the examples can be tried directly with dotnet run.
If episode 3 was the vocabulary of the language, episode 4 is its grammar — the structure you'll use in almost all production code.
A class is a blueprint; an object is its concrete form. A class defines fields, properties, and methods, and is instantiated with the new keyword:
class Produk
{
public string Nama { get; }
public decimal Harga { get; }
public Produk(string nama, decimal harga)
{
Nama = nama;
Harga = harga;
}
public decimal HargaSetelahPajak() => Harga * 1.11m;
}
var p = new Produk("Kopi Gayo", 85_000m);
Console.WriteLine(p.HargaSetelahPajak());A constructor guarantees that an object is always in a valid state from birth. For classes with many fields, you can use an object initializer instead of a long parameterized constructor:
var pesanan = new Pesanan
{
Nomor = "INV-001",
Total = 250_000m,
Status = "Menunggu Pembayaran"
};
Console.WriteLine($"{pesanan.Nomor} | {pesanan.Total:C}");The object initializer new Pesanan { Nomor = "INV-001" } requires properties with accessible setters. This pattern makes your code read like a data declaration.
Encapsulation means hiding internal details and exposing only the necessary interface. Access modifiers in C#:
public: accessible from anywhere.private: only inside the class itself.protected: derived classes can still access it.internal: only within the same assembly.Properties give you full control when a value is read or changed, without changing how callers interact:
class Rekening
{
private decimal _saldo;
public decimal Saldo
{
get => _saldo;
private set
{
if (value < 0) throw new ArgumentException("Saldo tidak boleh negatif.");
_saldo = value;
}
}
public void Deposit(decimal jumlah) => Saldo += jumlah;
}Note the private set keyword above — callers outside the class can read Saldo, but only methods inside the class can change it. This is proper encapsulation.
C# supports single inheritance: a class can derive from only one base class, but it can implement many interfaces. Virtual methods can be overridden by derived classes:
class Hewan
{
public virtual string Suara() => "Hmm";
}
class Kucing : Hewan
{
public override string Suara() => "Meow";
}
class Anjing : Hewan
{
public override string Suara() => "Guk";
}Polymorphism works when you call a method through the base type — each object runs its own version.
An abstract class provides a partial implementation that derived classes must complete. An interface only declares a contract without implementation. A practical rule: use abstract classes to share common code, and interfaces to define capabilities:
interface IDeskripsi
{
string Deskripsi();
}
abstract class AlatTulis
{
public string Merk { get; set; }
public abstract string Fungsi();
}
class Pulpen : AlatTulis, IDeskripsi
{
public override string Fungsi() => "Menulis di atas kertas";
public string Deskripsi() => $"Pulpen {Merk}";
}The Pulpen class extends one class and implements one interface. This pattern is the backbone of SOLID design, which we'll use in episode 16.
For immutable data, C# 9 introduced records. Records provide automatic value equality — two records are considered equal if all their properties are equal — plus a concise declaration syntax:
record Alamat(string Jalan, string Kota, string KodePos);
record Pengguna
{
public required string Nama { get; init; }
public int Umur { get; init; }
}
var a = new Alamat("Jl. Merdeka 1", "Bandung", "40112");
var b = a with { Kota = "Jakarta" };
Console.WriteLine(a == new Alamat("Jl. Merdeka 1", "Bandung", "40112"));The with operator creates a copy of a record with one or more values changed — a safe way to work with data without mutation. The required keyword forces callers to fill in the property during initialization.
Key takeaways:
new.In the next episode 5 we expand our type toolbox: advanced types and collections — struct, enum, tuple, nullable types, arrays, List, Dictionary, HashSet, LINQ basics, and nullable reference types for null safety.