Lesson 0002 · Fondasi C# · ±13 menit

Sistem tipe C#, dilihat dari TypeScript

class vs record vs struct, value vs reference type, nullable reference types, interface vs abstract class

Target lesson ini: Anda bisa menjawab pertanyaan interview "Apa beda class, record, dan struct di C#, dan kenapa DTO Anda tulis sebagai record?" dalam 60 detik. Sebagai bonus, Anda bisa membaca 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 typeReference type
Contohstruct, enum, int, decimalclass, array, string
Saat var b = aDatanya disalin; mengubah b tidak mengubah aKeduanya menunjuk objek yang sama
Padanan TSnumber, booleanobject, 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.

DeklarasiSemantikEquality (==)Kapan dipakai [1]
classreferenceobjek yang sama?Perilaku kompleks, state yang berubah. "Most custom types are classes."
structvaluenilai sama (lewat reflection, tanpa == bawaan)Data kecil (±64 byte atau kurang) dengan value semantics
record (= record class)referencesemua properti sama?Tipe yang isinya terutama data
record structvaluesemua 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]

interfaceabstract class
IsiKontrak: method, property, event. Tanpa instance field atau constructor [5]Member abstract dan method, field, constructor yang sudah diimplementasikan [6]
Bisa di-new?TidakTidak, hanya lewat turunannya
Per classBanyakHanya satu base class
Pakai saatKontrak lintas hierarki, misalnya IProductRepository untuk DI dan mockKelas 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.

Bacaan utama (±10 menit) Microsoft Learn: The C# type system. Baca bagian "Value types and reference types" dan "Choose which kind of type", lalu lanjut ke halaman C# record types bagian "When to use records".

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