C# adalah bahasa yang dibangun di atas fondasi OOP — bahkan program paling sederhana pun melibatkan class (Console adalah class, String adalah class). Memahami cara membuat class sendiri adalah langkah besar menuju pemrograman C# yang sesungguhnya. Dibanding C++ yang kita bahas sebelumnya (Class dan Object di C++), C# memiliki fitur properties bawaan yang membuat enkapsulasi jauh lebih elegan.
Membuat Class Pertama
public class RekeningBank
{
// Fields private — data yang disembunyikan dari luar
private string pemilik;
private decimal saldo;
private List<string> riwayat = new();
// Constructor
public RekeningBank(string namaPemilik, decimal saldoAwal)
{
pemilik = namaPemilik;
saldo = saldoAwal;
riwayat.Add($"Buka rekening: +Rp {saldoAwal:N0}");
}
// Method public
public void Setor(decimal jumlah)
{
if (jumlah <= 0) throw new ArgumentException("Jumlah harus positif");
saldo += jumlah;
riwayat.Add($"Setor: +Rp {jumlah:N0} (saldo: {saldo:N0})");
Console.WriteLine($"Setor berhasil. Saldo baru: Rp {saldo:N0}");
}
public bool Tarik(decimal jumlah)
{
if (jumlah > saldo)
{
Console.WriteLine("Saldo tidak cukup!");
return false;
}
saldo -= jumlah;
riwayat.Add($"Tarik: -Rp {jumlah:N0} (saldo: {saldo:N0})");
Console.WriteLine($"Berhasil tarik Rp {jumlah:N0}. Saldo baru: Rp {saldo:N0}");
return true;
}
public void CetakRiwayat()
{
Console.WriteLine($"\n=== Riwayat {pemilik} ===");
foreach (var transaksi in riwayat)
Console.WriteLine($" {transaksi}");
}
}
// Penggunaan
var rekening = new RekeningBank("Budi Santoso", 1_000_000m);
rekening.Setor(500_000m);
rekening.Tarik(200_000m);
rekening.CetakRiwayat();
// Output:
// Setor berhasil. Saldo baru: Rp 1.500.000
// Berhasil tarik Rp 200.000. Saldo baru: Rp 1.300.000
// === Riwayat Budi Santoso ===
// Buka rekening: +Rp 1.000.000
// Setor: +Rp 500.000 (saldo: 1.500.000)
// Tarik: -Rp 200.000 (saldo: 1.300.000)
Properties: Fitur Unggulan C#
Properties adalah fitur C# yang tidak ada di C++ atau Java — mereka menggabungkan field dan getter/setter menjadi sintaks yang bersih:
public class Produk
{
private string _nama;
private decimal _harga;
// Property dengan validasi
public string Nama
{
get { return _nama; }
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("Nama tidak boleh kosong");
_nama = value.Trim();
}
}
public decimal Harga
{
get { return _harga; }
set
{
if (value < 0) throw new ArgumentException("Harga tidak boleh negatif");
_harga = value;
}
}
// Auto-property — tanpa backing field manual
public int Stok { get; set; } = 0;
// Read-only property
public bool TersediaStok => Stok > 0;
// Expression-bodied property
public string Info => $"{Nama} — Rp {Harga:N0} (stok: {Stok})";
public Produk(string nama, decimal harga, int stok = 0)
{
Nama = nama; // Memanggil setter (ada validasi)
Harga = harga;
Stok = stok;
}
}
var laptop = new Produk("ThinkPad X1", 15_000_000m, 5);
Console.WriteLine(laptop.Info);
// Output: ThinkPad X1 — Rp 15.000.000 (stok: 5)
Console.WriteLine(laptop.TersediaStok); // Output: True
laptop.Stok = 0;
Console.WriteLine(laptop.TersediaStok); // Output: False
// laptop.Harga = -1000; // ❌ Exception: Harga tidak boleh negatif
Object Initializer Syntax
// Inisialisasi properti tanpa constructor khusus
var buku = new Produk("Clean Code", 350_000m)
{
Stok = 20
};
Console.WriteLine(buku.Info);
// Output: Clean Code — Rp 350.000 (stok: 20)
Static Members
static members milik class, bukan instance:
public class Counter
{
private static int _total = 0; // Satu untuk semua instance
private int _id;
public Counter()
{
_total++;
_id = _total;
}
public static int Total => _total;
public int Id => _id;
public static void Reset() => _total = 0;
}
var c1 = new Counter();
var c2 = new Counter();
var c3 = new Counter();
Console.WriteLine(Counter.Total); // Output: 3 (akses via nama class, bukan instance)
Console.WriteLine(c1.Id); // Output: 1
Console.WriteLine(c3.Id); // Output: 3
Record Type — Immutable Data Class (C# 9+)
record adalah cara modern membuat class data yang immutable dan dengan equality berbasis nilai:
// record: semua properties read-only by default, equality berdasarkan nilai
public record Koordinat(double Latitude, double Longitude);
var jakarta = new Koordinat(-6.2088, 106.8456);
var surabaya = new Koordinat(-7.2575, 112.7521);
Console.WriteLine(jakarta);
// Output: Koordinat { Latitude = -6.2088, Longitude = 106.8456 }
// Equality berbasis nilai (bukan referensi)
var jakarta2 = new Koordinat(-6.2088, 106.8456);
Console.WriteLine(jakarta == jakarta2); // Output: True
// Membuat salinan dengan nilai yang diubah
var jakarta_baru = jakarta with { Latitude = -6.2100 };
Console.WriteLine(jakarta_baru);
// Output: Koordinat { Latitude = -6.21, Longitude = 106.8456 }
Constructor Overloading dan this()
public class Jadwal
{
public string MataKuliah { get; }
public string Dosen { get; }
public string Ruang { get; }
public int JamMulai { get; }
// Constructor utama
public Jadwal(string mataKuliah, string dosen, string ruang, int jamMulai)
{
MataKuliah = mataKuliah;
Dosen = dosen;
Ruang = ruang;
JamMulai = jamMulai;
}
// Constructor dengan default dosen — memanggil constructor utama
public Jadwal(string mataKuliah, string ruang, int jamMulai)
: this(mataKuliah, "TBD", ruang, jamMulai) { }
public override string ToString()
=> $"{MataKuliah} | {Dosen} | {Ruang} | Jam {JamMulai}:00";
}
var mk1 = new Jadwal("Algoritma", "Prof. Ali", "Lab A", 8);
var mk2 = new Jadwal("Database", "Lab B", 10); // Dosen TBD
Console.WriteLine(mk1); // Output: Algoritma | Prof. Ali | Lab A | Jam 8:00
Console.WriteLine(mk2); // Output: Database | TBD | Lab B | Jam 10:00
Pertanyaan yang Sering Diajukan
Apa perbedaan class dan record di C#?
class adalah reference type yang mutable (nilainya bisa diubah) dan equality-nya berdasarkan referensi (dua variabel yang isinya sama dianggap tidak sama jika tidak menunjuk objek yang sama). record menghasilkan class yang immutable by default dengan equality berdasarkan nilai — cocok untuk DTO (Data Transfer Object), event, atau data domain yang tidak berubah.
Kapan menggunakan auto-property vs property dengan backing field?
Gunakan auto-property (public string Nama { get; set; }) untuk properti sederhana tanpa logika. Gunakan property dengan backing field saat kamu perlu validasi (cek nilai sebelum diterima), transformasi (trim string), atau logika lainnya di getter/setter.
Mengapa naming convention C# menggunakan PascalCase untuk method dan property?
Ini adalah konvensi Microsoft yang sudah menjadi standar C# sejak awal. Method dan properti publik menggunakan PascalCase (NamaMethod), variabel lokal dan parameter menggunakan camelCase (namaVariabel), field private sering menggunakan _namaField dengan underscore. Mengikuti konvensi ini membuat kode C# konsisten dan mudah dibaca oleh semua developer C#.
Apakah record menggantikan class untuk semua kasus?
Tidak. record paling cocok untuk data yang merepresentasikan nilai (value object) yang tidak berubah setelah dibuat — seperti koordinat, DTO dari API, atau domain event. class tetap lebih cocok untuk entitas dengan identitas dan state yang berubah (seperti RekeningBank di contoh di atas).
Kesimpulan
| Konsep | C++ | C# |
|---|---|---|
| Enkapsulasi | private: section | Access modifiers per member |
| Properties | Manual getter/setter | Native get/set properties |
| Constructor | Initializer list | : this() chaining |
| Destructor | ~Class() deterministic | Finalizer non-deterministic |
| Immutable data | Buat manual | record type |
| Static | static keyword | static keyword + static class |
Artikel sebelumnya: Koleksi Data di C# — List, Dictionary, dan LINQ.
Langkah selanjutnya: Inheritance dan Polymorphism di C# — cara membuat hierarki class dan polymorphism di C#.