Koleksi adalah bagian yang paling sering bocor antar bagian program. Satu List<int> yang diteruskan mentah-mentah dari class domain ke lapisan UI membuka pintu bagi bagian lain untuk mengubah data tanpa melewati aturan bisnis. Bab penutup section ini merangkum kebiasaan desain yang menjaga koleksi tetap terkendali, dengan memanfaatkan kontrak interface dari bab Collection Interfaces dan tampilan baca-saja dari AsReadOnly().

Empat Prinsip Utama #

  • Simpan mutable di dalam, tampilkan versi baca-saja ke luar: field internal boleh List<T>, tetapi property publik sebaiknya IReadOnlyList<T> atau IReadOnlyCollection<T>.
  • Terima parameter paling longgar: method yang hanya membaca cukup menerima IEnumerable<T>, sehingga array, List<T>, maupun hasil LINQ bisa masuk tanpa konversi.
  • Arahkan perubahan lewat method: penambahan item dilakukan lewat method domain seperti TambahNilai, bukan lewat koleksi mentah, agar aturan validasi tidak bisa dilewati.
  • Salin sebelum menyimpan: bila menerima koleksi dari penelepon, pertimbangkan ToList() agar perubahan penelepon di kemudian hari tidak merembet ke data internal.

Contoh: NilaiSiswa yang Terkendali #

Class berikut menyimpan nilai di List<int> internal dan hanya membuka bacaan:

csharp
public class NilaiSiswa
{
    private readonly List<int> _nilai = new List<int>();

    public string Nama { get; }

    public NilaiSiswa(string nama)
    {
        Nama = nama;
    }

    public void TambahNilai(int nilai)
    {
        if (nilai < 0 || nilai > 100)
        {
            throw new ArgumentOutOfRangeException(nameof(nilai), "Nilai harus 0-100.");
        }
        _nilai.Add(nilai);
    }

    public double RataRata => _nilai.Count == 0 ? 0 : _nilai.Average();

    public IReadOnlyList<int> Nilai => _nilai.AsReadOnly();
}

NilaiSiswa siswa = new NilaiSiswa("Sinta");
siswa.TambahNilai(85);
siswa.TambahNilai(90);
siswa.TambahNilai(78);

Console.WriteLine($"Nilai {siswa.Nama}: {string.Join(", ", siswa.Nilai)}");
Console.WriteLine($"Rata-rata: {siswa.RataRata:0.0}");
Console.WriteLine($"Nilai kedua: {siswa.Nilai[1]}");

Output yang Dihasilkan:

text
Nilai Sinta: 85, 90, 78
Rata-rata: 84.3
Nilai kedua: 90

Mari kita bedah bagian pentingnya:

  1. private readonly List<int> _nilai: satu-satunya penyimpanan asli; modifier private dan readonly menutup jalan bagi class lain untuk mengganti referensinya.
  2. TambahNilai memvalidasi 0-100: satu-satunya pintu masuk data; ArgumentOutOfRangeException dilempar sebelum item masuk, sehingga isi list dijamin sah.
  3. Nilai => _nilai.AsReadOnly(): property mengembalikan bungkus ReadOnlyCollection<T>; penelepon bisa membaca dan mengindeks, tetapi tidak punya Add maupun Remove.
  4. RataRata dihitung saat dibaca: perhitungan menghindari state kedua yang bisa berbeda dari isi list; rumus LINQ Average() bekerja di atas koleksi internal.

Dengan empat prinsip di atas, koleksi yang kita rancang aman dikonsumsi bagian program mana pun. Materi section berikutnya, LINQ, membangun langsung di atas kontrak IEnumerable<T> yang menjadi fondasi semua bab di section ini.