Learn .NET - OOP & Core Programming Concepts
Series/Learn .NET/Episode 5
Episode 5 of 23

Learn .NET - OOP & Core Programming Concepts

This episode builds your C# OOP foundation: classes, objects, properties, encapsulation, inheritance, polymorphism, abstract classes, and interfaces. You will also learn record types, tuples, pattern matching, and deconstruction for more modern code.

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

Introduction

In episode 4 you mastered the basic syntax of C#. Now it's time to model the real world in code with OOP (Object-Oriented Programming). Episode 5 breaks down classes, encapsulation, inheritance, polymorphism, interfaces, and abstraction — then closes with modern features such as record types and tuples.

It is important to understand: OOP in C# is not just about syntax, but about design discipline. Encapsulation protects state, interfaces separate contracts from implementations, and record types make immutable data concise. These patterns will be used continuously in episode 8 (dependency injection) and episode 16 (advanced architecture).

Class, Object, and Encapsulation

Building a Class with Properties

A class is a blueprint, and an object is its instance. Properties expose state in a controlled way. Access modifiers determine the level of access:

Class with encapsulation
public class Rekening
{
    private decimal _saldo;
 
    public string Pemilik { get; set; }
    public decimal Saldo => _saldo;
 
    public void Setor(decimal jumlah)
    {
        if (jumlah <= 0) throw new ArgumentException("Jumlah tidak valid");
        _saldo += jumlah;
    }
}

The _saldo field is private — only methods inside the class can change it. The Saldo property is read-only from the outside. This is encapsulation: internal state is protected and every change goes through the Setor method, which validates input.

Constructors and Object Initializers

Objects are created with new, and property values can be set via an object initializer:

Creating an object
var rekening = new Rekening { Pemilik = "Arman" };
rekening.Setor(100_000m);
Console.WriteLine(rekening.Saldo);

new Rekening { Pemilik = "Arman" } uses an object initializer to fill in properties at creation time. The underscore value 100_000 is a digit separator that makes large numbers easy to read.

Inheritance and Polymorphism

Abstract Classes for Basic Abstraction

Inheritance allows a class to inherit behavior from another class. Abstract classes define a partial contract — they contain shared implementations and abstract methods that must be overridden:

Abstract class and inheritance
public abstract class Hewan
{
    public abstract string Bersuara();
}
 
public class Kucing : Hewan
{
    public override string Bersuara() => "Meow";
}
 
public class Anjing : Hewan
{
    public override string Bersuara() => "Guk";
}

Kucing : Hewan inherits from Hewan, and override replaces the abstract Bersuara behavior. Polymorphism means that code operating on Hewan automatically works on all of its subtypes.

Interfaces for Behavior Contracts

An interface defines a contract without implementation. A single class can implement many interfaces, overcoming the limitation of single inheritance:

Interface for a contract
public interface IPembayaran
{
    bool Proses(decimal jumlah);
}
 
public class PembayaranKartu : IPembayaran
{
    public bool Proses(decimal jumlah) => true;
}

Use interfaces when you want to separate contracts from implementations — this is the core material of dependency injection in episode 8. Use abstract classes to share code between closely related classes.

Record Types and Immutable Objects

Records for Unchanging Data

Records provide value equality and immutability concisely. With init-only properties, values can only be set at construction time:

Record type
public record Produk(int Id, string Nama, decimal Harga);
 
var produk = new Produk(1, "Laptop", 15_000_000m);
var produkBaru = produk with { Harga = 14_500_000m };

The with expression creates a copy of the record with some values changed — the original record is unchanged. Two records with the same values are considered equal. This makes records ideal for value objects and DTOs sent between services.

Pattern Matching, Tuples, and Deconstruction

Tuples and Deconstruction

A tuple groups several values without creating a class. Deconstruction breaks values into separate variables:

Tuple and deconstruction
var hasil = (Lat: -6.2, Lng: 106.8);
var (lat, lng) = hasil;
 
Console.WriteLine(lat);
Console.WriteLine(lng);

(Lat: -6.2, Lng: 106.8) creates a named tuple, and var (lat, lng) = hasil deconstructs it. Records can also be deconstructed if all properties are in positional parameters.

Pattern Matching on Objects

Pattern matching is not limited to switch — it can check types and properties at the same time:

Pattern matching on objects
string Deskripsi(IPembayaran p) => p switch
{
    PembayaranKartu => "Kartu",
    _ => "Lainnya"
};

The pattern PembayaranKartu => "Kartu" matches the object type. With a combination of records, pattern matching, and switch expressions, C# can write declarative, readable domain logic.

Tip

Use records for pure data, classes for behavior with state, and interfaces for contracts between modules. This combination keeps your model simple.

Design Choice Guide

A summary of design decisions:

  • Class: behavior with mutable state and identity.
  • Record: immutable data with value equality.
  • Interface: a contract that many classes can implement.
  • Abstract class: shared behavior inherited by related classes.
  • Tuple: temporary grouping of values without a named type.

Closing

Key takeaways:

  • Encapsulation protects state through access modifiers and properties.
  • Inheritance uses abstract classes; polymorphism enables one contract with many behaviors.
  • Interfaces define contracts without implementation, ideal for DI.
  • Records provide immutability and value equality concisely.
  • The with expression creates a copy of a record with partial changes.
  • Tuples and deconstruction group and unpack values.

In the next episode 6 we will discuss exception handling and debugging — try, catch, finally, custom exceptions, using declarations, dispose patterns, debugging in VS Code, and basic logging with Microsoft.Extensions.Logging.

Learn .NET - OOP & Core Programming Concepts | Learn .NET