Class System.Random menghasilkan angka pseudo-random: urutan deterministik yang terlihat acak, dimulai dari sebuah seed. Buat satu instance lalu panggil method-nya:
Contoh output:
1294270287
83
You rolled a 5
0.6060
Jalankan lagi dan angkanya berubah. Batas atasnya eksklusif: Next(1, 7) bisa mengembalikan 1, 2, 3, 4, 5, atau 6, tetapi tidak pernah 7. Untuk angka antara 1 dan 10 (10 termasuk), tulis Next(1, 11). Memberikan max yang lebih kecil dari min akan melempar ArgumentOutOfRangeException.
Angka acak dalam rentang tertentu
Next(min, max) dipakai untuk bilangan bulat. Untuk double dalam suatu rentang, skalakan NextDouble(); untuk decimal seperti harga, bangun dari bilangan bulat agar hasilnya punya presisi yang kamu inginkan:
Contoh output:
23.6 C
72.42
heads
N
-3
rng.Next(100, 10000) / 100m memilih jumlah sen dalam bilangan bulat lalu membaginya dengan decimal, jadi harganya selalu punya tepat dua angka desimal. Menskalakan double akan menghasilkan nilai seperti 72.4183.
Seed: urutan yang bisa diulang
Memberikan seed berupa bilangan bulat membuat urutannya bisa diulang. Dua object Random dengan seed yang sama menghasilkan angka yang persis sama, dengan urutan yang sama:
Output:
16 29 91 99 91
16 29 91 99 91
38
Seed adalah yang kamu butuhkan untuk tes, simulasi yang perlu dijalankan ulang, replay di game, dan level yang dibuat secara prosedural ("world seed 2026"). Catat seed yang kamu pakai, maka hasil yang mengejutkan bisa direproduksi nanti.
Microsoft tidak menjamin sebuah seed menghasilkan urutan yang sama di setiap versi .NET. Jangan menyimpan hasil dari seed sebagai data (misalnya membuat ID pelanggan dari seed tetap lalu berharap bisa membuatnya ulang setelah upgrade).
Memilih elemen acak
Indeks acak adalah Next(0, count), yang selalu menjadi indeks valid karena batas atasnya eksklusif:
Output:
Winner 1 gets a sticker pack
Winner 2 gets a mug
Winner 3 gets a sticker pack
common 695
rare 260
legendary 45
Undian berbobot memetakan angka dari 0 sampai 99 ke rentang yang ukurannya sama dengan bobotnya: 0 sampai 69 adalah common, 70 sampai 94 rare, dan 95 sampai 99 legendary. Dari 1.000 kali undian, hasilnya mendekati 700, 250, dan 50 (di sini 695, 260, dan 45).
Mengacak list: Fisher-Yates
Untuk menyusun list dalam urutan acak, telusuri dari ujung lalu tukar setiap elemen dengan elemen acak di posisinya atau sebelumnya. Inilah shuffle Fisher-Yates; caranya cepat dan setiap urutan punya peluang yang sama. Jika ditulis sebagai method generic, cara ini bisa dipakai untuk array dan list dengan tipe elemen apa pun:
Output:
5 A 8 6 4 7 2 3
4 2 1 3 5
Dua jalan pintas yang sering kamu lihat di internet lebih buruk. list.OrderBy(x => rng.Next()) memang berjalan, tetapi melakukan sorting, jadi lebih lambat, dan key acaknya bisa bertabrakan. Menukar dengan rng.Next(items.Count) alih-alih rng.Next(i + 1) terlihat mirip, tetapi membuat beberapa urutan lebih mungkin muncul daripada yang lain. Di .NET 8 dan sesudahnya, Random.Shared.Shuffle(array) melakukan shuffle yang benar langsung di array itu, dan rng.GetItems(choices, 5) memilih lima item acak (boleh berulang).
Jebakan new Random() di dalam perulangan
Bug klasik adalah membuat Random baru setiap kali butuh angka:
// Don't do this
int RollDie()
{
var rng = new Random(); // new instance on every call
return rng.Next(1, 7);
}
Di .NET Framework, new Random() tanpa seed memakai jam sistem (Environment.TickCount), yang hanya berubah setiap 10 sampai 16 milidetik. Panggilan dalam perulangan cepat membuat instance dengan seed yang sama dan mengembalikan angka yang sama, jadi perulangan lemparan dadu mencetak 4 4 4 4 4. .NET Core dan .NET 5+ memberi seed ke setiap instance dari sumber acak bersama, yang menyembunyikan gejalanya, tetapi membuat satu object untuk setiap angka tetap boros, dan kodenya rusak saat dipakai ulang di proyek lama atau di Unity.
Solusinya adalah satu instance untuk class itu, disimpan di sebuah field:
Output:
3 6 6 5 6 1 2 1
Seed di sini hanya agar contohnya mencetak hal yang sama setiap kali dijalankan; di game sungguhan kamu akan menulis new Random().
Thread dan Random.Shared
Random tidak thread-safe. Jika dua thread memanggil Next pada instance yang sama secara bersamaan, state internalnya bisa rusak; gejala yang terkenal adalah instance yang sejak itu selalu mengembalikan 0. Pilihannya, dari yang paling sederhana:
static class Dice
{
// .NET 6 and later: a thread-safe shared instance
public static int RollShared() => Random.Shared.Next(1, 7);
// Any version: one instance per thread
[ThreadStatic] private static Random _local;
private static Random Local => _local ?? (_local = new Random(Guid.NewGuid().GetHashCode()));
public static int RollPerThread() => Local.Next(1, 7);
// Any version: a lock around one shared instance
private static readonly Random _rng = new Random();
private static readonly object _sync = new object();
public static int RollLocked()
{
lock (_sync) { return _rng.Next(1, 7); }
}
}
Random.Shared adalah pilihan default yang tepat di kode baru: thread-safe, tidak butuh field, dan diberi seed secara acak. Ia tidak bisa diberi seed, jadi pakai new Random(seed) milikmu sendiri saat kamu butuh hasil yang bisa diulang.
Angka acak yang aman secara kriptografis
System.Random memang dirancang bisa ditebak. Siapa pun yang tahu seed-nya, atau melihat cukup banyak output-nya, bisa menghitung sisanya. Itu tidak masalah untuk game dan sampling, tetapi salah untuk password, token reset, ID sesi, key, dan undian lotre. Untuk semua itu, pakai RandomNumberGenerator dari System.Security.Cryptography, yang membaca dari generator aman milik sistem operasi:
Contoh output:
Verification code: 276062
Reset token: 391e40ab5fa205e6457e48661586d10a
Password: gyv8LWyajJcF
RandomNumberGenerator.GetInt32 (tersedia di .NET Core 3.0 dan sesudahnya) mengembalikan bilangan bulat tanpa bias di dalam rentangnya. Jangan membuat kode keamanan dengan bytes[0] % 10: operasi modulo membuat beberapa digit lebih mungkin muncul daripada yang lain. Di .NET 6 dan sesudahnya, RandomNumberGenerator.GetBytes(16) langsung mengembalikan array baru, dan Convert.ToHexString(bytes) memformatnya. Untuk ID unik yang tidak perlu dirahasiakan, Guid.NewGuid() lebih sederhana.
Pertanyaan yang sering diajukan
Bagaimana cara membuat angka acak di C#?
Buat satu object Random lalu panggil Next: var rng = new Random(); int roll = rng.Next(1, 7); menghasilkan 1 sampai 6. Next(max) menghasilkan 0 sampai max - 1, dan NextDouble() menghasilkan double dari 0,0 hingga mendekati 1,0 (1,0 tidak termasuk). Di .NET 6 dan sesudahnya kamu bisa melewatkan object itu dan memakai Random.Shared.Next(1, 7).
Apakah batas atas Random.Next ikut termasuk?
Tidak. Next(min, max) mengembalikan angka yang lebih besar dari atau sama dengan min dan benar-benar lebih kecil dari max. Untuk angka dari 1 sampai 10 (10 termasuk), tulis Next(1, 11). Karena itu Next(0, list.Count) selalu menjadi indeks yang valid untuk sebuah list.
Kenapa new Random() menghasilkan angka yang sama?
Di .NET Framework, new Random() diberi seed dari jam sistem, yang hanya berubah setiap beberapa milidetik, jadi beberapa instance yang dibuat dalam perulangan cepat mendapat seed yang sama dan urutan angka yang sama. .NET Core dan .NET 5+ memberi seed yang berbeda untuk setiap instance, tetapi solusinya sama di mana pun: buat satu Random lalu pakai ulang.
Bagaimana cara mendapatkan angka acak yang sama setiap kali di C#?
Berikan seed ke constructor: new Random(42). Dua instance dengan seed yang sama menghasilkan urutan yang sama, sehingga tes, simulasi, dan procedural generation bisa diulang. Urutan untuk seed tertentu bisa berbeda antarversi .NET, jadi jangan menyimpan hasil dari seed sebagai data permanen.
Apakah System.Random cukup aman untuk password atau token?
Tidak. Random bisa ditebak: siapa pun yang mengetahui state atau seed-nya bisa menghasilkan ulang angkanya. Untuk password, token, key, dan apa pun yang berhubungan dengan keamanan, pakai System.Security.Cryptography.RandomNumberGenerator, misalnya RandomNumberGenerator.GetInt32(0, 10) atau RandomNumberGenerator.GetBytes(32).