Learn C# - Classes, Objects & OOP
Series/Learn C#/Episode 4
Episode 4 of 23

Learn C# - Classes, Objects & OOP

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.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

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.

Class, Object, and Constructors

Creating a Class and Instantiating It

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 with a constructor
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());

Constructors and Object Initializers

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:

Object initializer
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 and Access Modifiers

Access Modifiers

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 with Validation

Properties give you full control when a value is read or changed, without changing how callers interact:

Property with validation
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.

Inheritance and Polymorphism

Inheritance and Overriding

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:

Inheritance and overriding
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.

Abstract Classes and Interfaces

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 and abstract class
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.

Record Types and Immutable Data

Data That Never Changes

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 with init-only
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.

Closing

Key takeaways:

  • A class is a blueprint; objects are created with new.
  • Encapsulation is maintained with access modifiers and validated properties.
  • C# has single inheritance plus multiple interfaces.
  • Abstract classes for sharing implementations; interfaces for contracts.
  • Records suit immutable data with automatic value equality.

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.