مقدمة: الخوف المألوف لدى كل مطور C#
\nتخيل الموقف: أنت تكتب استعلامًا لقاعدة بيانات أو مجموعة في الذاكرة. تحتاج إلى الحصول على العنصر الأول الذي يحقق الشرط. أنت متأكد من أن هذا العنصر موجود بالتأكيد. أم لست متأكدًا؟ هنا يبدأ الرقص مع الطبلة: .First() أم .FirstOrDefault()؟
هذا السؤال هو ضربة حقيقية على Stack Overflow بآلاف المشاهدات. وليس من دون سبب. الاختيار بين هاتين الطريقتين يمكن أن يعني الفرق بين كود نظيف ومتوقع وبين استثناء InvalidOperationException مفاجئ في بيئة الإنتاج. دعنا نفهم مرة واحدة وإلى الأبد متى نستخدم أي طريقة، وننسى الشكوك إلى الأبد.
باختصار عن الأساسيات: ما الفرق؟
\nكلا الطريقتين تفعلان نفس الشيء تقريبًا: تعيدان العنصر الأول من التسلسل الذي يحقق الشرط (أو ببساطة العنصر الأول إذا لم يتم تحديد شرط). لكن هناك فرق جوهري:
\n- \n
- .First() — إذا لم يتم العثور على العنصر، فإنها ترمي استثناء
System.InvalidOperationException. \n - .FirstOrDefault() — إذا لم يتم العثور على العنصر، فإنها تعيد القيمة الافتراضية للنوع (
nullللأنواع المرجعية،0لـint،falseلـbool، إلخ). \n
يبدو الأمر بسيطًا. لكن الشيطان، كما هو الحال دائمًا، يكمن في التفاصيل.
\n\nمتى نستخدم .First(): عندما "يجب أن يكون موجودًا، ولا نقاش"
\nاستخدم .First() عندما يضمن منطق عملك بشكل صارم أن العنصر موجود. إذا لم يكن موجودًا، فهذه حالة استثنائية، خطأ في البيانات أو منطق التطبيق.
مثال 1: البحث بواسطة معرف فريد
\nإذا كنت تبحث عن مستخدم بواسطة المفتاح الأساسي في قاعدة البيانات (مثل المعرف)، وتأكدت من وجود هذا المستخدم، استخدم .First():
public class UserService\n{\n private readonly AppDbContext _context;\n\n public UserService(AppDbContext context)\n {\n _context = context;\n }\n\n public User GetUserById(int userId)\n {\n // نتوقع وجود مستخدم بهذا المعرف.\n // إذا لم يكن موجودًا، فهذه مشكلة خطيرة، فليُرمَ الاستثناء.\n return _context.Users.First(u => u.Id == userId);\n }\n}\nالاستثناء في هذه الحالة هو إشارة إنذار. إنه يقول: "مرحبًا، البيانات تالفة، أو شخص ما أرسل معرفًا غير صحيح". ستلتقط هذا الاستثناء في مستوى أعلى من المكدس وتتعامل معه كخطأ حرج.
\n\nمثال 2: العنصر الأول بعد الفرز (مضمون)
\nإذا كنت تقوم بفرز قائمة وتأخذ العنصر الأول، وكانت القائمة تحتوي بالتأكيد على عناصر:
\nvar highestScore = players\n .OrderByDescending(p => p.Score)\n .First(); // لا يمكن أن يكون عدد اللاعبين 0، لقد تحققنا من ذلك مسبقًا\nإذا تحققت مسبقًا من أن المجموعة ليست فارغة (مثل if (players.Any()))، فإن .First() هو اختيارك.
متى نستخدم .FirstOrDefault(): عندما قد لا يكون العنصر موجودًا
\nالقاعدة الذهبية: إذا كان غياب النتيجة هو وضع طبيعي وليس خطأ، استخدم .FirstOrDefault().
مثال 1: بحث اختياري
\nتخيل أننا نبحث عن طلب برقم، ولكن الطلب قد لا يكون قد تم إنشاؤه بعد:
\npublic Order FindOrderByNumber(string orderNumber)\n{\n // قد لا يكون الطلب موجودًا. هذا طبيعي.\n return _context.Orders\n .FirstOrDefault(o => o.OrderNumber == orderNumber);\n // سيعيد null إذا لم يتم العثور على الطلب.\n}\nبعد استدعاء هذه الطريقة، يجب علينا التحقق من النتيجة للتأكد من أنها ليست null:
var order = FindOrderByNumber("ORD-12345");\nif (order != null)\n{\n // نعمل مع الطلب\n Console.WriteLine($"تم العثور على الطلب: {order.Id}");\n}\nelse\n{\n Console.WriteLine("لم يتم العثور على الطلب. يتم إنشاء طلب جديد.");