Menu
flag Ar iconالعربيةdown icon

الأرقام العشوائية في C#: Random.Next والنطاقات والبذور والخلط والعشوائية الآمنة

كيف تولّد أرقامًا عشوائية في C# بالصنف Random: الدالة Next وحدّها الأعلى غير المشمول، وقيم double عشوائية في نطاق، والبذور لنتائج قابلة للتكرار، واختيار عنصر عشوائي، وخلط قائمة، وفخ new Random داخل حلقة، والأرقام الآمنة تشفيريًا.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

يولّد الصنف System.Random أرقامًا شبه عشوائية: تسلسلًا حتميًا يبدو عشوائيًا، يبدأ من بذرة. أنشئ مثيلًا واحدًا واستدعِ دواله:

مخرجات على سبيل المثال:

1294270287
83
You rolled a 5
0.6060

شغّله مرة أخرى فتتغيّر الأرقام. الحد الأعلى غير مشمول: قد تعيد Next(1, 7) القيم 1 أو 2 أو 3 أو 4 أو 5 أو 6، ولا تعيد 7 أبدًا. لرقم بين 1 و10 شاملًا اكتب Next(1, 11). وتمرير max أصغر من min يرمي ArgumentOutOfRangeException.

أرقام عشوائية في نطاق

تغطي Next(min, max) الأعداد الصحيحة. لقيمة double في نطاق، حجّم NextDouble()؛ ولقيمة decimal مثل السعر، ابنها من أعداد صحيحة لتحصل النتيجة على الدقة التي تريدها:

مخرجات على سبيل المثال:

23.6 C
72.42
heads
N
-3

تختار rng.Next(100, 10000) / 100m عددًا صحيحًا من السنتات وتقسمه على decimal، فيكون للسعر دائمًا منزلتان عشريتان بالضبط. أما تحجيم double فسيعطي قيمًا مثل 72.4183.

البذور: تسلسلات قابلة للتكرار

تمرير بذرة صحيحة يجعل التسلسل قابلًا للتكرار. كائنا Random بالبذرة نفسها ينتجان الأرقام نفسها تمامًا، بالترتيب نفسه:

المخرجات:

16 29 91 99 91
16 29 91 99 91
38

البذور هي ما تريده للاختبارات، والمحاكاة التي تحتاج إلى إعادة تشغيلها، وإعادة العرض في الألعاب، والمراحل المولّدة إجرائيًا ("world seed 2026"). سجّل البذرة التي استخدمتها، فيمكن إعادة إنتاج أي نتيجة مفاجئة لاحقًا.

لا تعد Microsoft بأن البذرة تنتج التسلسل نفسه في كل إصدار من .NET. لا تحفظ المخرجات المبذورة كبيانات (مثلًا توليد معرّفات العملاء من بذرة ثابتة وتوقّع إعادة توليدها بعد ترقية).

اختيار عنصر عشوائي

الفهرس العشوائي هو Next(0, count)، وهو فهرس صالح دائمًا لأن الحد الأعلى غير مشمول:

المخرجات:

Winner 1 gets a sticker pack
Winner 2 gets a mug
Winner 3 gets a sticker pack
common     695
rare       260
legendary  45

يربط السحب الموزون رقمًا من 0 إلى 99 بنطاقات أحجامها هي الأوزان: من 0 إلى 69 شائع، ومن 70 إلى 94 نادر، ومن 95 إلى 99 أسطوري. عبر 1,000 سحبة تقترب الأعداد من 700 و250 و50 (هنا 695 و260 و45).

خلط قائمة: Fisher-Yates

لوضع قائمة بترتيب عشوائي، امشِ من النهاية وبدّل كل عنصر مع عنصر عشوائي في موضعه أو قبله. هذا خلط Fisher-Yates؛ سريع وكل ترتيب فيه متساوي الاحتمال. ومكتوبًا كدالة عامة يعمل مع المصفوفات والقوائم بأي نوع عناصر:

المخرجات:

5 A 8 6 4 7 2 3
4 2 1 3 5

اختصاران ستراهما على الإنترنت أسوأ. list.OrderBy(x => rng.Next()) يعمل لكنه يفرز، فهو أبطأ، وقد تتصادم المفاتيح العشوائية. والتبديل بـ rng.Next(items.Count) بدل rng.Next(i + 1) يبدو مشابهًا لكنه يجعل بعض الترتيبات أكثر احتمالًا من غيرها. في .NET 8 وما بعده تنفّذ Random.Shared.Shuffle(array) خلطًا صحيحًا في المكان، وتختار rng.GetItems(choices, 5) خمسة عناصر عشوائية (مع التكرار).

فخ new Random() داخل حلقة

خطأ تقليدي هو إنشاء Random جديد كلما احتجت إلى رقم:

// Don't do this
int RollDie()
{
    var rng = new Random();   // new instance on every call
    return rng.Next(1, 7);
}

في .NET Framework تستخدم new Random() دون بذرة ساعة النظام (Environment.TickCount)، التي لا تتغيّر إلا كل 10 إلى 16 ميلي ثانية. الاستدعاءات في حلقة سريعة تنشئ مثيلات بالبذرة نفسها وتعيد الرقم نفسه، فتطبع حلقة رمي النرد 4 4 4 4 4. تبذر .NET Core و.NET 5+ كل مثيل من مصدر عشوائي مشترك، وهذا يخفي العرض، لكن إنشاء كائن لكل رقم ما زال هدرًا، وتنكسر الشيفرة حين يُعاد استخدامها في مشاريع أقدم أو في Unity.

الحل مثيل واحد للصنف، مخزّن في حقل:

المخرجات:

3 6 6 5 6 1 2 1

البذرة هنا فقط كي يطبع المثال الشيء نفسه في كل تشغيل؛ في لعبة حقيقية ستكتب new Random().

الخيوط وRandom.Shared

Random غير آمن مع الخيوط. إن استدعى خيطان Next على المثيل نفسه في الوقت نفسه فقد تفسد حالته الداخلية؛ والعرض المعروف مثيل يعيد 0 من تلك اللحظة فصاعدًا. الخيارات، بدءًا بالأبسط:

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 هو الافتراضي الصحيح في الشيفرة الجديدة: آمن مع الخيوط، ولا يحتاج إلى حقل، ومبذور عشوائيًا. لا يمكن بذره، فاستخدم new Random(seed) خاصًا بك حين تحتاج إلى قابلية التكرار.

الأرقام العشوائية الآمنة تشفيريًا

System.Random قابل للتنبؤ بتصميمه. من يعرف البذرة، أو يرى مخرجات كافية، يستطيع حساب البقية. هذا مقبول للألعاب وأخذ العينات، وخطأ لكلمات المرور ورموز إعادة التعيين ومعرّفات الجلسات والمفاتيح وسحوبات اليانصيب. لهذه استخدم RandomNumberGenerator من System.Security.Cryptography، الذي يقرأ من المولّد الآمن لنظام التشغيل:

مخرجات على سبيل المثال:

Verification code: 276062
Reset token: 391e40ab5fa205e6457e48661586d10a
Password: gyv8LWyajJcF

تعيد RandomNumberGenerator.GetInt32 (المتاحة في .NET Core 3.0 وما بعده) عددًا صحيحًا غير منحاز في النطاق. لا تبنِ رموزًا آمنة بـ bytes[0] % 10: فباقي القسمة يجعل بعض الأرقام أكثر احتمالًا من غيرها. في .NET 6 وما بعده تعيد RandomNumberGenerator.GetBytes(16) مصفوفة جديدة مباشرة، وتنسّقها Convert.ToHexString(bytes). وللمعرّفات الفريدة التي لا تهمّ سريتها تكون Guid.NewGuid() أبسط.

الأسئلة الشائعة

كيف أولّد رقمًا عشوائيًا في C#؟

أنشئ كائن Random واحدًا واستدعِ Next: var rng = new Random(); int roll = rng.Next(1, 7); تعطي من 1 إلى 6. تعطي Next(max) من 0 إلى max - 1، وتعطي NextDouble() قيمة double من 0.0 حتى 1.0 دون تضمينها. في .NET 6 وما بعده يمكنك الاستغناء عن الكائن واستخدام Random.Shared.Next(1, 7).

هل الحد الأعلى في Random.Next مشمول؟

لا. تعيد Next(min, max) رقمًا أكبر من min أو يساويه وأصغر تمامًا من max. لرقم من 1 إلى 10 شاملًا اكتب Next(1, 11). وهذا يجعل Next(0, list.Count) فهرسًا صالحًا في قائمة.

لماذا تعطي new Random() الأرقام نفسها؟

في .NET Framework تُبذر new Random() من ساعة النظام، التي لا تتغيّر إلا كل بضع ميلي ثوانٍ، فتحصل عدة مثيلات مُنشأة في حلقة سريعة على البذرة نفسها والتسلسل نفسه. تبذر .NET Core و.NET 5+ كل مثيل بشكل مختلف، لكن الحل واحد في كل مكان: أنشئ Random واحدًا وأعد استخدامه.

كيف أحصل على الأرقام العشوائية نفسها في كل مرة في C#؟

مرّر بذرة إلى المُنشئ: new Random(42). مثيلان بالبذرة نفسها ينتجان التسلسل نفسه، وهذا يجعل الاختبارات والمحاكاة والتوليد الإجرائي قابلة للتكرار. قد يختلف التسلسل لبذرة معينة بين إصدارات .NET، فلا تخزّن المخرجات المبذورة كبيانات دائمة.

هل System.Random آمن بما يكفي لكلمات المرور أو الرموز؟

لا. Random قابل للتنبؤ: من يعرف حالته أو بذرته يستطيع إعادة إنتاج الأرقام. لكلمات المرور والرموز والمفاتيح وكل ما يتعلق بالأمان استخدم System.Security.Cryptography.RandomNumberGenerator، مثل RandomNumberGenerator.GetInt32(0, 10) أو RandomNumberGenerator.GetBytes(32).

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن