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

الطابور Queue في C#: Enqueue وDequeue وPeek وTryDequeue

Queue<T> مجموعة الداخل أولًا يخرج أولًا: تغادر العناصر بترتيب وصولها. تعلّم Enqueue وDequeue وPeek، واستثناء الطابور الفارغ وTryDequeue، والبحث بالعرض أولًا بطابور، ومتى تلجأ إلى ConcurrentQueue أو PriorityQueue.

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

يعمل Queue<T> مثل طابور أمام شبّاك: أول عنصر يُضاف هو أول عنصر يُؤخذ (FIFO، الداخل أولًا يخرج أولًا). تضيف في المؤخرة بـ Enqueue وتأخذ من المقدمة بـ Dequeue، كلاهما في زمن ثابت.

Enqueue وDequeue وPeek

المخرجات:

Waiting: 3
Next up: invoice.pdf
Printing invoice.pdf
Next up: photo.png
Printing photo.png
Printing report.docx
Waiting: 0

Peek لتقرير ما تفعله بالعنصر التالي قبل الالتزام به؛ وDequeue تلتزم. حلقة while (queue.Count > 0) التي تسحب حتى يفرغ الطابور هي الطريقة القياسية لمعالجة طابور، وتظل تعمل إن أضاف جسم الحلقة عناصر أخرى، وهو ما لا تسمح به foreach.

استثناء الطابور الفارغ وTryDequeue

ترمي Dequeue وPeek على طابور فارغ InvalidOperationException. لا يوجد سلوك "null عند الفراغ"، حتى للأنواع المرجعية.

المخرجات:

Caught InvalidOperationException
Serving ticket 41
Serving ticket 42
False

تعيد TryDequeue وTryPeek القيمة true وتضبطان متغيّر out حين يوجد عنصر، وتعيدان false حين يكون الطابور فارغًا. وصلتا في .NET Core 2.0؛ وفي شيفرة .NET Framework الأقدم افحص Count > 0 قبل استدعاء Dequeue.

النظر داخل الطابور

يمكن تعداد الطابور دون حذف شيء. ترى foreach وToArray وContains كلها العناصر من المقدمة إلى المؤخرة.

المخرجات:

Ana Ben Chloe 
True
Ana is first, 3 in line
3

لا فهرس: لا تُترجم line[1]، ولا يمكنك حذف عنصر من المنتصف. إن احتجت إلى أي منهما فالبيانات ليست طابورًا حقًا؛ استخدم List<T> أو LinkedList<T>. وكما في المجموعات الأخرى، الإضافة أو السحب داخل foreach على الطابور نفسه يرمي InvalidOperationException.

البحث بالعرض أولًا بطابور

ترتيب الطابور هو بالضبط ما يحتاجه البحث بالعرض أولًا (BFS): زر كل ما يبعد خطوة واحدة، ثم كل ما يبعد خطوتين، وهكذا. هنا يجد عدد الوصلات التي تفصل Ana عن كل شخص آخر في شبكة صغيرة:

المخرجات:

Ana: 0 step(s) from Ana
Ben: 1 step(s) from Ana
Chloe: 1 step(s) from Ana
Dev: 2 step(s) from Ana
Eli: 2 step(s) from Ana
Fay: 3 step(s) from Ana

يُضاف كل شخص إلى الطابور مرة واحدة، أول مرة يُبلغ فيها، ولأن الطابور يعيدهم بترتيب العثور عليهم تكون المرة الأولى دائمًا على مسار أقصر. استبدل الطابور بـ Stack فتصبح الحلقة نفسها بحثًا بالعمق أولًا، الذي لا يجد أقصر المسارات بعد ذلك.

والشكل نفسه يعالج المرور على شجرة مستوى بمستوى، والتعبئة بالفيضان على شبكة، وزحف الروابط: ابدأ بعنصر واحد في الطابور، وما دام غير فارغ اسحب واحدًا وأضف جيرانه غير المزورين.

Queue مقابل List لعمل FIFO

يمكنك استخدام List<T> كطابور بـ Add وRemoveAt(0)، لكن RemoveAt(0) تزيح كل عنصر متبقٍّ خانة واحدة للخلف. تفريغ قائمة من 100,000 عنصر بهذه الطريقة يجري نحو خمسة مليارات نقل عنصر؛ بينما يجري Queue<T> مئة ألف خطوة بزمن ثابت. داخليًا الطابور مخزن دائري: يتتبّع فهرسي رأس وذيل في مصفوفة ولا يعيد الحجز إلا حين يمتلئ.

العمليةQueue<T>List<T> مستخدمة كطابور
الإضافة في المؤخرةEnqueue، O(1)Add، O(1)
الحذف من المقدمةDequeue، O(1)RemoveAt(0)، O(n)
النظر إلى المقدمةPeeklist[0]
الوصول بالفهرسغير متاحlist[i]

ConcurrentQueue لعدة خيوط

Queue<T> غير آمن مع الخيوط. حين تضيف عدة خيوط عناصر أو تأخذها استخدم ConcurrentQueue<T> من System.Collections.Concurrent. فيه Enqueue وTryDequeue وTryPeek لكن لا Dequeue، لأنه مع وجود خيوط أخرى قد يفشل "افحص Count ثم Dequeue" بين الاستدعاءين.

المخرجات:

4000
4000 processed

مع Queue<T> عادي مكان ConcurrentQueue<T> كان العدد سيخرج خاطئًا أو كان البرنامج سيرمي استثناءً، بحسب التوقيت. ولخيوط المنتج والمستهلك التي يجب أن تنتظر العمل بدل الدوران، تضيف BlockingCollection<T> (التي تغلّف ConcurrentQueue<T> افتراضيًا) أو System.Threading.Channels الحجب والإكمال. راجع lock لحماية مجموعة عادية يدويًا.

PriorityQueue

حين يجب أن تغادر العناصر بحسب الأولوية لا بترتيب الوصول (التذكرة الأكثر إلحاحًا أولًا، أو أقصر مسار حتى الآن في خوارزمية Dijkstra)، توفّر .NET 6 وما بعدها PriorityQueue<TElement, TPriority>. تخرج أدنى قيمة أولوية أولًا:

var triage = new PriorityQueue<string, int>();
triage.Enqueue("sprained ankle", 3);
triage.Enqueue("chest pain", 1);
triage.Enqueue("headache", 5);

while (triage.TryDequeue(out string patient, out int priority))
{
    Console.WriteLine($"{priority}: {patient}");
}
// 1: chest pain
// 3: sprained ankle
// 5: headache

العناصر ذات الأولوية المتساوية قد تخرج بأي ترتيب؛ أضف رقمًا تسلسليًا إلى الأولوية إن كان يجب أن يحسم ترتيب الوصول التعادل.

أخطاء شائعة

  • السحب دون فحص. الطابور الفارغ يرمي InvalidOperationException؛ دُر على Count > 0 أو استخدم TryDequeue.
  • استدعاء Peek وتوقّع أن يختفي العنصر. وحدها Dequeue تحذف.
  • الإضافة داخل foreach على الطابور. يرمي استثناءً؛ عالج بحلقة while بدلًا من ذلك.
  • مشاركة Queue<T> بين الخيوط. استخدم ConcurrentQueue<T> أو قفلًا.
  • استخدام List.RemoveAt(0) كطابور على بيانات كبيرة. كل استدعاء O(n).

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

ما هو Queue في C#؟

Queue<T> في System.Collections.Generic مجموعة الداخل أولًا يخرج أولًا (FIFO). تضيف Enqueue عنصرًا في المؤخرة، وتحذف Dequeue العنصر في المقدمة وتعيده، وتعيد Peek عنصر المقدمة دون حذفه. الثلاث تستغرق زمنًا ثابتًا.

ماذا يحدث حين تستدعي Dequeue على طابور فارغ في C#؟

ترمي Dequeue وPeek على طابور فارغ InvalidOperationException. افحص queue.Count > 0 أولًا، أو استخدم TryDequeue(out var item) وTryPeek(out var item)، اللتين تعيدان false بدل رمي استثناء (متاحتان منذ .NET Core 2.0).

ما الفرق بين Peek وDequeue؟

تعيد Peek العنصر في المقدمة وتتركه في الطابور، فيعيد استدعاؤها مرتين العنصر نفسه. أما Dequeue فتعيد عنصر المقدمة وتحذفه، فيعيد الاستدعاء التالي العنصر الذي خلفه.

هل Queue<T> آمن مع الخيوط في C#؟

لا. استدعاء خيطين Enqueue أو Dequeue على Queue<T> نفسه في الوقت نفسه قد يفسده. استخدم ConcurrentQueue<T> من System.Collections.Concurrent، التي يمكن استدعاء Enqueue وTryDequeue فيها بأمان من خيوط كثيرة، أو احمِ كل وصول إلى طابور عادي بـ lock.

لماذا أستخدم Queue بدل List؟

حذف العنصر الأول من List<T> بـ RemoveAt(0) يزيح كل عنصر آخر، فيبطؤ كلما كبرت القائمة. أما Queue<T> فتحذف من المقدمة في زمن ثابت، وواجهتها تعلن القصد: تُعالج العناصر بترتيب وصولها.

Coddy programming languages illustration

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

ابدأ الآن