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

LINQ في C#: Where وSelect وOrderBy وGroupBy والمزيد

يضيف LINQ معاملات الاستعلام إلى كل مجموعة في C#. تعلّم صياغة الدوال وصياغة الاستعلام، والتصفية بـ Where، والإسقاط بـ Select، والفرز بـ OrderBy وThenBy، والتجميع بـ GroupBy، وفخ First مقابل FirstOrDefault، والتجميعات، وكيف يغيّر التنفيذ المؤجّل موعد تشغيل استعلامك.

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

LINQ (Language Integrated Query) مكتبة دوال لتصفية التسلسلات وتحويلها وفرزها وتجميعها وتلخيصها. أضف using System.Linq; فيكتسب كل مصفوفة وList<T> وDictionary وكل IEnumerable<T> آخر دوالًا مثل Where وSelect وOrderBy، يأخذ كل منها تعبير lambda يقول ماذا تفعل بعنصر واحد.

أول استعلام: صياغة الدوال وصياغة الاستعلام

لـ LINQ ترميزان. تسلسل صياغة الدوال الاستدعاءات؛ وتُقرأ صياغة الاستعلام مثل SQL. يحوّل المترجم صياغة الاستعلام إلى استدعاءات الدوال نفسها، فينتجان نتائج متطابقة.

المخرجات:

95, 91, 88
95, 91, 88

صياغة الدوال أشيع في الشيفرة الحديثة، وبعض المعاملات (Take وDistinct وCount وFirst وToList) لا توجد إلا كدوال. أما صياغة الاستعلام فتتألق في عمليات الربط (join) وفي الاستعلامات التي تقدّم متغيّرات وسيطة بـ let. تستخدم بقية هذه الصفحة صياغة الدوال وتُظهر صياغة الاستعلام حيث تضيف شيئًا.

Where وSelect: التصفية والإسقاط

تبقي Where العناصر التي تطابق شرطًا. وتحوّل Select كل عنصر إلى شيء آخر. معًا تغطيان معظم الاستعلامات اليومية.

المخرجات:

Mug, Kettle
MUG $8.50 | LAMP $32.00 | KETTLE $27.00 | RUG $120.00
Lamp: 38.40
Rug: 144.00
1. Mug 2. Lamp 3. Kettle 4. Rug

لا تغيّر Where العناصر أبدًا، ولا تغيّر Select عددها أبدًا. تنشئ new { p.Name, WithTax = ... } نوعًا مجهولًا، صنفًا يكتبه المترجم عنك، تقرأ خصائصه بصياغة النقطة العادية. ليس له اسم يمكنك كتابته، فيجب التصريح بالمتغيّر الذي يحمله بـ var، ويناسب النتائج التي تبقى داخل دالة واحدة.

OrderBy وThenBy والفرز بعدة مفاتيح

المخرجات:

Ana 60
Ana 15
Ben 90
Ben 40
Chloe 25
Ben, Ana, Ben, Chloe, Ana

OrderBy مستقرة: تبقى العناصر ذات المفاتيح المتساوية بترتيبها الأصلي. استخدم ThenBy أو ThenByDescending لمفتاح ثانٍ. أما OrderBy ثانية فستعيد فرز كل شيء بمفتاحها، تاركة المفتاح الأول مجرد حاسم للتعادل: الأولوية المعاكسة. وفي صياغة الاستعلام تُفصل المفاتيح بفواصل: orderby o.Customer, o.Total descending.

بخلاف List<T>.Sort تترك OrderBy المصدر دون مساس وتعيد تسلسلًا جديدًا.

First وSingle وLast ونسخها OrDefault

تعيد هذه عنصرًا واحدًا بدل تسلسل. ترمي النسخ العادية استثناءً حين لا يوجد ما تعيده؛ وتعيد نسخ OrDefault القيمة الافتراضية للنوع.

المخرجات:

Ana
Ben
Bea
True
First: InvalidOperationException
Single: InvalidOperationException
0

الاستثناء من First على تسلسل فارغ هو InvalidOperationException برسالة "Sequence contains no elements" (أو "Sequence contains no matching element" حين لا يطابق الشرط شيئًا)، وهو من أكثر أخطاء C# بحثًا. استخدم FirstOrDefault حين يكون "لا شيء" نتيجة طبيعية وافحص null.

يُظهر السطر الأخير فخ أنواع القيم: في تسلسل int تعيد FirstOrDefault القيمة 0 حين لا يطابق شيء، ولا يمكن تمييزها عن 0 حقيقية. صفِّ بـ Where وافحص Any() أولًا، أو في .NET 6 وما بعده مرّر قيمتك الافتراضية: scores.FirstOrDefault(s => s > 100, -1).

Single لـ "واحد بالضبط": ترمي إن لم يوجد أي عنصر أو وُجد أكثر من واحد، فتكون تأكيدًا بقدر ما هي استعلام.

Any وAll وCount وContains

المخرجات:

True
True
True
3
True
True

تتوقف Any() عند أول تطابق، فتكون items.Any() الطريقة الصحيحة لسؤال "هل يوجد شيء؟". تقرأ items.Count() > 0 الخاصية Count حين يكون المصدر مجموعة مثل قائمة أو مصفوفة، لكنها على استعلام أو تسلسل كسول آخر تمرّ على كل عنصر أولًا. وAll على تسلسل فارغ تعيد true، لأنه لا يوجد عنصر يفشل في الشرط.

Sum وAverage وMin وMax

المخرجات:

Total: 28.25
Items: 7
Priciest: 7.25
Average price: 4.92
0
Average: InvalidOperationException

Sum لتسلسل فارغ تساوي 0، لكن Average وMin وMax لتسلسل أرقام فارغ ترمي InvalidOperationException: لا توجد إجابة معقولة. احمِ بـ Any()، أو استخدم DefaultIfEmpty() أولًا: empty.DefaultIfEmpty(0).Average().

Distinct وTake وSkip

المخرجات:

home, shop, cart
Page 1: 1 2 3 4 5 6 7 8 9 10
Page 2: 11 12 13 14 15 16 17 18 19 20
Page 3: 21 22 23
12, 15

تبقي Distinct أول ظهور لكل قيمة بالترتيب. وSkip(n).Take(m) هو نمط التقسيم إلى صفحات القياسي، وتتوقف TakeWhile/SkipWhile أو تبدأ عند أول عنصر يفشل في شرط. وتولّد Enumerable.Range(start, count) تسلسل أعداد صحيحة دون مصفوفة.

GroupBy

تفرز GroupBy العناصر في دلاء بحسب مفتاح. لكل مجموعة Key وهي نفسها تسلسل، فيمكنك إجراء تجميعات عليها.

المخرجات:

North: 3 sales, total 600
South: 2 sales, total 400
East: 1 sales, total 350
Top rep: Ana 400
North -> Ana/Chloe/Ana; South -> Ben/Eli; East -> Dev

تخرج المجموعات بترتيب أول ظهور لمفاتيحها في المصدر (North ثم South ثم East)، لا مرتّبة؛ أضف OrderBy(g => g.Key) إن احتجت إلى ذلك. وفي صياغة الاستعلام تختار group X by K ما يدخل كل مجموعة (هنا اسم المندوب فقط) وتسمّي into g المجموعة كي يستمر الاستعلام.

ToList وToArray وToDictionary

تشغّل دوال To... الاستعلام وتخزّن النتائج.

المخرجات:

ben@x.com
2
ToDictionary: ArgumentException

ترمي ToDictionary استثناء ArgumentException حين ينتج عنصران المفتاح نفسه، تمامًا مثل Dictionary.Add. إن كانت التكرارات متوقعة فجمّع أولًا (كما تفعل wordCounts) أو استخدم ToLookup، التي تربط كل مفتاح بتسلسل من القيم.

التنفيذ المؤجّل

تعيد Where وSelect وOrderBy وGroupBy ومعظم المعاملات الأخرى كائن استعلام ولا تؤدي أي عمل حتى يعدّده شيء ما. لذلك نتيجتان تستحقان المشاهدة:

المخرجات:

25, 40, 99
  converting 10
  converting 25
  converting 40
  converting 99
Count: 4
  converting 10
  converting 25
  converting 40
  converting 99
Max: 9900
  converting 10
  converting 25
  converting 40
  converting 99
Cached: 4, 9900

رأى الاستعلام الأول 99 مع أنها أُضيفت بعد كتابة الاستعلام: عملت التصفية حين عدّدتها string.Join. وطبع الاستعلام الثاني تحويلاته مرتين لأن Count() وMax() عدّدته كل منهما من الصفر. مع تعبير lambda رخيص يكون ذلك مجرد عمل ضائع؛ أما مع استعلام قاعدة بيانات أو قراءة ملف أو رقم عشوائي داخل تعبير lambda فقد يعيد المروران بيانات مختلفة. حين ستستخدم نتيجة أكثر من مرة جسّدها بـ ToList() أو ToArray().

المعاملات التي تعيد قيمة واحدة (Count وSum وFirst وAny وMax) ودوال To... تعمل فورًا. راجع IEnumerable وyield لكيفية بناء هذا الكسل.

مرجع سريع

المعاملما يفعلهما يعيده
Where(x => cond)التصفيةتسلسل (مؤجّل)
Select(x => expr)تحويل كل عنصرتسلسل (مؤجّل)
SelectMany(x => seq)تسطيح التسلسلات المتداخلةتسلسل (مؤجّل)
OrderBy، ThenBy (+Descending)الفرز، مستقرتسلسل (مؤجّل)
GroupBy(x => key)التوزيع في دلاء بحسب المفتاحتسلسل من المجموعات (مؤجّل)
Distinct، Take، Skipإزالة التكرار، التقسيم إلى صفحاتتسلسل (مؤجّل)
First، Single، Lastعنصر واحد، يرمي إن لم يوجدعنصر
FirstOrDefault وأمثالهاعنصر واحد أو الافتراضيعنصر
Any، All، Containsاختبارbool
Count، Sum، Min، Max، Averageتجميعرقم
ToList، ToArray، ToDictionaryالتشغيل والتخزينمجموعة

أخطاء شائعة

  • First() على تسلسل قد يكون فارغًا. يرمي "Sequence contains no elements"؛ استخدم FirstOrDefault وافحص.
  • استدعاءا OrderBy. يصبح المفتاح الثاني الرئيسي؛ استخدم ThenBy.
  • تعداد استعلام عدة مرات. يتكرر العمل؛ استدعِ ToList() مرة واحدة.
  • توقّع أن يلتقط الاستعلام لقطة من مصدره. يقرأ المصدر حين يُعدَّد، لا حين يُكتب.
  • Average وMin وMax على تسلسل فارغ. ترمي؛ احمِ بـ Any() أو DefaultIfEmpty.
  • Count() > 0 لاختبار وجود عناصر. تتوقف Any() عند أول عنصر.

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

ما هو LINQ في C#؟

LINQ (Language Integrated Query) مجموعة من دوال التوسيع في System.Linq، مثل Where وSelect وOrderBy وGroupBy، تعمل على أي IEnumerable<T>: المصفوفات والقوائم والقواميس ونتائج الاستعلامات الأخرى. وفي C# أيضًا صياغة استعلام (from x in items where ... select ...) يترجمها المترجم إلى استدعاءات الدوال نفسها.

ما الفرق بين Select وWhere في LINQ؟

تصفّي Where: تبقي العناصر التي يعيد تعبير lambda لها true وتتركها دون تغيير. وتُسقط Select: تحوّل كل عنصر إلى شيء آخر، مثل خاصية أو كائن جديد، وتبقي العدد نفسه. وكثيرًا ما تُسلسلان: people.Where(p => p.Age >= 18).Select(p => p.Name).

ما الفرق بين First وFirstOrDefault؟

تعيد First() العنصر الأول (أو أول تطابق لشرط) وترمي InvalidOperationException ("Sequence contains no elements" أو "no matching element") حين لا يوجد. أما FirstOrDefault() فتعيد القيمة الافتراضية للنوع بدلًا من ذلك: null للأصناف و0 لـ int، فافحص النتيجة قبل استخدامها.

كيف أفرز بحقلين في LINQ؟

سلسل ThenBy بعد OrderBy: orders.OrderBy(o => o.Customer).ThenByDescending(o => o.Total). استدعاء OrderBy مرتين لا يعمل: يعيد الاستدعاء الثاني فرز التسلسل كله، فيصبح مفتاحه الأساسي ولا يحسم المفتاح الأول إلا التعادلات، وهذا عكس المقصود.

كيف تعمل GroupBy في LINQ؟

تعيد GroupBy(x => key) مجموعة واحدة لكل مفتاح مميّز. كل مجموعة IGrouping<TKey, TElement>: لها خاصية Key وهي نفسها تسلسل من العناصر التي تحمل ذلك المفتاح، فيمكنك استدعاء Count() أو Sum() أو Select عليها. وتخرج المجموعات بترتيب أول ظهور لمفاتيحها.

ما هو التنفيذ المؤجّل في LINQ؟

معظم معاملات LINQ لا تعمل حين تستدعيها؛ بل تعيد استعلامًا يعمل في كل مرة يُعدَّد فيها (بـ foreach وToList() وCount() وغيرها). لذلك يرى الاستعلام التغييرات التي تحدث على المصدر بعد تعريفه، وتعداده مرتين يؤدي العمل مرتين. استدعِ ToList() أو ToArray() لتشغيله مرة واحدة والاحتفاظ بالنتائج.

Coddy programming languages illustration

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

ابدأ الآن