Foto oleh Brett Jordan via Pexels

Tipe data pada C# menentukan jenis nilai apa yang bisa disimpan sebuah variabel beserta operasi apa saja yang valid dilakukan terhadapnya. C# membagi tipe data menjadi dua kategori besar: value type yang menyimpan nilai secara langsung, dan reference type yang menyimpan alamat memori menuju data sebenarnya.

Tipe data primitif seperti int, double, bool, dan char adalah value type, sementara string, array, dan class adalah reference type. Perbedaan ini bukan sekadar teknis, tetapi memengaruhi bagaimana data tersebut berperilaku saat disalin ke variabel lain atau dikirim sebagai parameter method.

Artikel ini membahas tipe data dasar yang paling sering dipakai, perbedaan fundamental antara value type dan reference type, serta konsep type inference dengan kata kunci var yang sering membingungkan pemula.

Tipe Data Numerik dan Jangkauannya

Pemilihan tipe numerik sesuai kebutuhan presisi dan jangkauan

int jumlahBarang = 100; // bilangan bulat, -2 miliar s/d 2 miliar long populasiJakarta = 10_500_000L; // bilangan bulat lebih besar double hargaRataRata = 15000.75; // bilangan desimal presisi ganda decimal totalPembayaran = 150000.50m; // presisi tinggi, untuk uang
Pemilihan tipe numerik yang tepat penting terutama untuk nilai uang: decimal sebaiknya selalu dipakai untuk perhitungan finansial karena presisinya yang tinggi dan bebas dari pembulatan floating-point yang bisa terjadi pada double, yang lebih cocok untuk perhitungan ilmiah yang mentoleransi sedikit ketidakakuratan.

Value Type Disalin Secara Independen

Mengubah salinan value type tidak memengaruhi aslinya

int a = 10; int b = a; // b adalah SALINAN independen dari a b = 20; Console.WriteLine(a); // Expected: 10, tidak ikut berubah menjadi 20
Ketika sebuah value type disalin ke variabel lain, yang terjadi adalah penyalinan nilai itu sendiri ke lokasi memori baru yang terpisah, sehingga mengubah salinan tersebut tidak akan memengaruhi variabel aslinya sama sekali, perilaku yang intuitif namun penting dipahami sebagai pembanding terhadap reference type.

Reference Type Berbagi Lokasi Memori yang Sama

Mengubah reference type lewat satu variabel memengaruhi variabel lain yang menunjuk ke objek sama

int[] arrayA = { 1, 2, 3 }; int[] arrayB = arrayA; // arrayB menunjuk ke MEMORI YANG SAMA arrayB[0] = 99; Console.WriteLine(arrayA[0]); // Expected: 99, IKUT berubah
Berbeda dengan value type, menyalin reference type seperti array atau object hanya menyalin alamat memorinya, bukan datanya, sehingga kedua variabel menunjuk ke objek yang sama persis di memori. Mengubah data lewat salah satu variabel akan terlihat juga lewat variabel lainnya, karena keduanya sebenarnya merujuk ke data yang identik.

Type Inference dengan var

var tetap strongly-typed, hanya tipe disimpulkan compiler

var nama = "Budi"; // compiler menyimpulkan tipe: string var umur = 25; // compiler menyimpulkan tipe: int var daftarHarga = new List<double>(); // tipe: List<double>
Kata kunci var bukan berarti C# menjadi dynamically-typed seperti JavaScript; compiler tetap menentukan tipe data yang pasti berdasarkan nilai yang diberikan saat deklarasi, hanya saja penulisannya disingkat. Setelah dideklarasikan, variabel var tetap tidak bisa diubah tipenya, sama ketatnya dengan deklarasi tipe eksplisit.

Perbedaan Value Type dan Reference Type dalam Alokasi Memori

struct (value type) vs class (reference type) saat disalin

struct Point { public int X; public int Y; } class Box { public int Value; } Point p1 = new Point { X = 1, Y = 2 }; Point p2 = p1; p2.X = 99; Console.WriteLine(p1.X); // Output: 1 (tidak berubah) Box b1 = new Box { Value = 1 }; Box b2 = b1; b2.Value = 99; Console.WriteLine(b1.Value); // Output: 99 (ikut berubah)

Tipe data pada C# terbagi menjadi dua kategori besar berdasarkan cara penyimpanannya di memori: value type dan reference type. Tipe seperti int, double, bool, char, dan struct termasuk value type yang disimpan langsung pada stack dan menyimpan nilainya sendiri, sehingga ketika sebuah variabel value type disalin ke variabel lain, yang terjadi adalah penyalinan nilai secara penuh dan independen satu sama lain, seperti terlihat pada variabel p1 dan p2 di atas.

Sebaliknya, class termasuk reference type yang objeknya disimpan pada heap, sementara variabel yang mereferensikannya hanya menyimpan alamat memori (reference) menuju objek tersebut. Ketika b1 disalin ke b2, keduanya menunjuk ke objek yang sama di heap, sehingga perubahan melalui b2 juga terlihat melalui b1. Perbedaan perilaku ini sering menjadi sumber bug tersembunyi bagi pemula yang mengasumsikan semua penyalinan variabel bersifat independen.

Memahami perbedaan ini juga penting untuk pertimbangan performa: value type yang berukuran besar dan sering disalin dapat membebani stack, sedangkan reference type yang terlalu banyak dibuat akan membebani garbage collector karena harus melacak objek-objek di heap yang sudah tidak digunakan lagi untuk dibersihkan secara berkala.

Konversi Tipe Data: Implicit, Explicit Casting, dan Parsing String

Implicit casting otomatis, explicit casting manual, serta Parse vs TryParse

int angka = 100; double hasilImplicit = angka; // implicit, aman karena int muat di double double harga = 15000.75; int hasilExplicit = (int)harga; // explicit casting, nilai desimal terpotong Console.WriteLine(hasilExplicit); // Output: 15000 string input = "250"; int hasilParse = int.Parse(input); bool berhasil = int.TryParse("abc", out int hasilTryParse); Console.WriteLine(berhasil); // Output: False Console.WriteLine(hasilTryParse); // Output: 0

Konversi tipe data pada C# terbagi menjadi implicit dan explicit casting. Implicit casting terjadi otomatis tanpa sintaks khusus ketika tipe tujuan mampu menampung seluruh kemungkinan nilai dari tipe asal tanpa risiko kehilangan data, seperti konversi int ke double pada contoh di atas. Sebaliknya, explicit casting menggunakan tanda kurung berisi nama tipe tujuan dan wajib dituliskan manual karena ada risiko kehilangan presisi atau data, seperti konversi double ke int yang memotong bagian desimalnya.

Untuk mengonversi data berbentuk string menjadi tipe numerik, method Parse seperti int.Parse akan melempar FormatException jika string yang diberikan tidak sesuai format angka yang valid, sehingga pemanggilannya sebaiknya dibungkus try-catch atau, lebih disarankan, memakai method TryParse yang mengembalikan nilai boolean penanda keberhasilan tanpa melempar exception sama sekali, dengan hasil konversi diletakkan pada parameter out seperti hasilTryParse pada contoh di atas.

Method Convert.ToInt32 merupakan alternatif lain yang mirip dengan Parse namun memiliki perilaku berbeda terhadap nilai null, karena Convert.ToInt32(null) akan mengembalikan 0 alih-alih melempar exception seperti yang dilakukan int.Parse(null). Pemilihan antara Parse, TryParse, dan Convert perlu disesuaikan dengan konteks, terutama ketika data berasal dari input pengguna atau sumber eksternal yang formatnya tidak selalu dapat dijamin valid, sehingga TryParse umumnya menjadi pilihan paling aman untuk kasus semacam itu. Casting yang tidak tepat antar tipe numerik, misalnya dari long ke int tanpa pengecekan rentang nilai, juga dapat menimbulkan overflow diam-diam yang menghasilkan nilai tidak terduga tanpa melempar exception sama sekali kecuali blok checked secara eksplisit digunakan.

Perbedaan const dan readonly dalam Deklarasi Konstanta

public class Konfigurasi { public const double PajakPersen = 0.11; public readonly DateTime WaktuDibuat; public Konfigurasi() { WaktuDibuat = DateTime.Now; } } var k = new Konfigurasi(); Console.WriteLine(Konfigurasi.PajakPersen); Console.WriteLine(k.WaktuDibuat.Year); // Output: // 0.11 // 2026

Kata kunci const digunakan untuk nilai yang sudah diketahui dan tetap sama sejak waktu kompilasi, seperti angka pajak atau nama hari dalam seminggu, dan nilainya tertanam langsung ke dalam kode hasil kompilasi sehingga tidak bisa diubah sama sekali, bahkan oleh instance class itu sendiri karena sifatnya implicit static dan hanya bisa diakses melalui nama class, bukan melalui instance object seperti k.PajakPersen. Karena nilainya disalin langsung ke tempat pemakaian saat kompilasi, mengubah nilai const di kemudian hari mengharuskan semua kode yang memakainya dikompilasi ulang agar perubahan benar-benar diterapkan.

Sebaliknya, readonly mengizinkan nilai ditentukan saat runtime, biasanya melalui constructor seperti WaktuDibuat pada contoh di atas, dan nilainya boleh berbeda untuk tiap instance object meskipun setelah constructor selesai dijalankan nilainya tidak bisa diubah lagi dari luar. Readonly cocok dipakai ketika nilai konstan bergantung pada kondisi tertentu saat object dibuat, seperti waktu pembuatan object, hasil perhitungan awal, atau nilai yang diterima dari parameter constructor yang berbeda-beda untuk setiap instance, sehingga setiap object bisa memiliki nilai readonly yang unik sesuai konteks pembuatannya masing-masing.

Kesalahan Umum yang Sering Terjadi

Memakai double untuk Perhitungan Uang

Tipe double bisa menghasilkan kesalahan pembulatan kecil pada operasi aritmatika berulang, yang jika diakumulasikan pada perhitungan finansial bisa menghasilkan selisih yang signifikan dibanding memakai decimal.

Mengira var Membuat C# Menjadi Dynamically-Typed

Beberapa pemula mengira var memungkinkan variabel diisi ulang dengan tipe data berbeda di kemudian hari, padahal tipe tetap ditentukan secara permanen saat deklarasi pertama berdasarkan nilai awal yang diberikan.

Tidak Memahami Konsekuensi Berbagi Reference pada Array atau Object

Menyalin array atau object ke variabel lain dengan asumsi keduanya independen seperti value type bisa menyebabkan bug yang membingungkan ketika perubahan pada satu variabel ternyata ikut mengubah variabel lain yang dikira terpisah.

Kapan Pendekatan Ini Tidak Cocok Dipakai

Value type lebih efisien untuk data kecil dan sederhana karena tidak memerlukan alokasi memori tambahan di heap serta tidak perlu ditangani garbage collector, cocok untuk tipe seperti int, bool, atau struct kecil.

Reference type lebih fleksibel untuk struktur data kompleks yang perlu dibagikan antar bagian program tanpa menyalin seluruh isinya, namun membutuhkan pemahaman yang jelas tentang kapan data benar-benar disalin dan kapan hanya referensinya yang disalin untuk menghindari bug yang tidak disengaja.

Ringkasan

Memahami perbedaan value type dan reference type adalah salah satu fondasi paling penting dalam C#, karena memengaruhi cara data berperilaku saat disalin atau dikirim sebagai parameter. Pilih tipe numerik yang sesuai kebutuhan presisi, dan selalu sadari apakah tipe yang dipakai adalah value atau reference type sebelum menulis logika yang bergantung pada penyalinan data.