Sistem tipe C#, dilihat dari TypeScript
class vs record vs struct, value vs reference type, nullable reference types, interface vs abstract class
string? di template .NET 8 dan memilih antara interface atau abstract class.
1. Value type vs reference type
Di TypeScript, number disalin saat di-assign, sedangkan object dan array dibagi lewat referensi.
C# punya pembagian yang sama, tapi Anda sendiri yang memilihnya saat mendeklarasikan tipe.
Microsoft: "Every type in C# is either a value type or a reference type."
[1]
| Value type | Reference type | |
|---|---|---|
| Contoh | struct, enum, int, decimal | class, array, string |
Saat var b = a | Datanya disalin; mengubah b tidak mengubah a | Keduanya menunjuk objek yang sama |
| Padanan TS | number, boolean | object, array (semua tipe buatan Anda) |
"Value types hold their data directly. When you assign a value type to a new variable, the runtime copies the data."
Reference type "hold a reference to an object on the managed heap."
[1]
Hal yang mengejutkan dari TS: string di C# adalah reference type, bukan primitive.
2. class, struct, dan record
record bukan jenis tipe ketiga. Ia adalah modifier yang dipasang di atas class atau struct:
"The underlying type, a class or a struct, still determines whether instances use reference or value semantics."
[2]
Compiler lalu membuatkan value equality, ToString yang mudah dibaca, dan with untuk menyalin dengan perubahan.
| Deklarasi | Semantik | Equality (==) | Kapan dipakai [1] |
|---|---|---|---|
class | reference | objek yang sama? | Perilaku kompleks, state yang berubah. "Most custom types are classes." |
struct | value | nilai sama (lewat reflection, tanpa == bawaan) | Data kecil (±64 byte atau kurang) dengan value semantics |
record (= record class) | reference | semua properti sama? | Tipe yang isinya terutama data |
record struct | value | semua properti sama? | Data kecil yang butuh value equality dan with |
// DTOs/ProductResponse.cs — satu baris: constructor + properti init-only
public record ProductResponse(int Id, string Name, decimal Price);
var a = new ProductResponse(1, "Pen", 5m);
var b = new ProductResponse(1, "Pen", 5m);
Console.WriteLine(a == b); // True (value equality)
var murah = a with { Price = 3m }; // salinan baru, a tidak berubah
Console.WriteLine(murah); // ProductResponse { Id = 1, Name = Pen, Price = 3 }
Padanan TS terdekat: type ProductResponse = Readonly<{ id: number; name: string; price: number }> plus spread
{ ...a, price: 3 } untuk with. Bedanya, di JS a === b tetap false; record C# membandingkan isinya.
Kenapa DTO ditulis sebagai record, tetapi Entity tidak
Microsoft menyarankan record bila "The type's primary role is storing data", "Two instances with the same values should be equal",
dan Anda "want immutability".
[2]
Itu persis definisi DTO di GLOSSARY: bentuk data yang masuk/keluar API. Positional record class memberi properti init-only,
jadi DTO tidak berubah setelah dibuat.
[3]
Entity justru sebaliknya: "Entity Framework Core depends on reference equality to ensure that it uses only one instance of an entity type
for what is conceptually one entity. For this reason, records and record structs aren't appropriate for use as entity types."
[3]
Jadi: Models/Product.cs = class, DTOs/ProductResponse.cs = record.
3. Nullable reference types: string? = string | null
Template .NET modern menyalakan <Nullable>enable</Nullable> di file project.
[4]
Setelah itu "Every reference type variable is non-nullable by default. Append ? to declare a nullable reference type."
[4]
Microsoft sendiri menyebut modelnya mirip strictNullChecks di TypeScript.
public class Product
{
public string Name { get; set; } = ""; // wajib ada ≈ name: string
public string? Description { get; set; } // boleh null ≈ description: string | null
}
Console.WriteLine(p.Description.Length); // warning: dereference of a possibly null reference
Console.WriteLine(p.Description?.Length); // aman, sama seperti optional chaining di TS
Jebakan interview: ini hanya pemeriksaan compiler. "The runtime behavior of your program is unchanged. Nullable reference types are entirely a compile-time feature."
[4]
Sama seperti tipe TS yang hilang setelah dikompilasi ke JS, jadi request dari luar tetap perlu divalidasi.
Operator ! (null-forgiving) membungkam warning; Microsoft menyarankan "Use ! sparingly."
4. Interface vs abstract class
Konsepnya sama dengan TS, tapi ada satu batasan yang sering ditanyakan: "A class can inherit from only one base class but can implement multiple interfaces." [5]
interface | abstract class | |
|---|---|---|
| Isi | Kontrak: method, property, event. Tanpa instance field atau constructor [5] | Member abstract dan method, field, constructor yang sudah diimplementasikan [6] |
Bisa di-new? | Tidak | Tidak, hanya lewat turunannya |
| Per class | Banyak | Hanya satu base class |
| Pakai saat | Kontrak lintas hierarki, misalnya IProductRepository untuk DI dan mock | Kelas berkerabat berbagi state, constructor, atau member non-public [5] |
Beda dengan TS: interface TS bersifat struktural (cukup bentuknya cocok) dan hilang saat runtime.
Di C#, class harus menuliskan interface-nya secara eksplisit (class ProductRepository : IProductRepository)
[5],
dan tipe itu tetap ada saat runtime. Karena itulah AddScoped<IProductRepository, ProductRepository>() dari
lesson 0001 bisa bekerja.
Latihan 1 — Tebak hasilnya, pilih tipenya
Jawab tanpa scroll ke atas. Soal yang sama akan muncul lagi, diacak, di review campuran 0007.
Kuis di lesson ini adalah feedback cepat, bukan bukti penguasaan; yang dicatat hanya hasil di review campuran 0007.
Latihan 2 — Ucapkan dengan suara keras
Pasang timer 60 detik dan jawab dengan suara keras, seolah sedang interview. Jangan buka jawaban contoh sebelum selesai.
"Apa beda class, record, dan struct di C#, dan kenapa DTO Anda tulis sebagai record?"
Bandingkan dengan jawaban contoh dan checklist-nya
"class adalah reference type: assignment menyalin referensi, dan equality defaultnya membandingkan referensi, sama seperti object di TypeScript.
struct adalah value type: assignment menyalin datanya, cocok untuk data kecil seperti koordinat.
record adalah modifier di atas class atau struct; compiler membuatkan value equality, ToString, dan with untuk salinan dengan perubahan.
Positional record class propertinya init-only, jadi immutable.
DTO itu data murni yang melewati batas API, jadi record pas: satu baris, immutable, dan dua DTO dengan isi sama dianggap sama, mirip type + Zod schema di TS.
Tapi Entity EF Core tetap class, karena EF Core butuh reference equality untuk melacak entitas."
Checklist: apakah Anda menyebut…
- class = reference type, assignment menyalin referensi?
- struct = value type, assignment menyalin data, untuk data kecil?
- record = modifier di atas class/struct: value equality,
ToString,with? - positional record class punya properti
init-only (immutable)? - DTO = data murni, jadi cocok sebagai record (dengan padanan TS)?
- Entity EF Core tetap class karena EF Core butuh reference equality?
Lulus kalau minimal 4 dari 6. Kalau kurang, ulangi besok pagi sebelum lesson berikutnya (spacing). Review 0007 juga bisa menanyakan "interface vs abstract class" dan "nullable reference types" secara lisan, dengan checklist yang sama gayanya.
Ringkasan untuk dicetak: Reference: C# untuk developer TypeScript · Istilah: GLOSSARY.md
Ada yang belum jelas? Tanyakan ke saya (agent) di terminal, misalnya: "kenapa string di C# reference type tapi terasa seperti value?" atau "kapan saya perlu record struct?"
Sumber: [1] The C# type system · [2] C# record types · [3] Records (C# reference) · [4] Nullable reference types · [5] Interfaces · [6] abstract keyword