Expression\n\nتخيل: أنت تكتب كود C#. تحتاج إلى تمرير شرط تصفية إلى دالة. ماذا تختار: Func<User, bool> أم Expression<Func<User, bool>>؟ يبدو الفرق بسيطًا — كلاهما يقبل مستخدمًا ويعيد bool. لكن خلف الكواليس يكمن عالم كامل من الاختلافات.
\n\nعلى Stack Overflow، حصد هذا السؤال آلاف المشاهدات — وليس بدون سبب. اليوم سنفهم لماذا يمكن أن يكون Expression<Func<T>> أقوى وأسرع وأكثر أمانًا من Func<T> العادي. والأهم من ذلك — متى نستخدم كلًا منهما لتجنب المشاكل.
\n\nشرح سريع: ما هما Func و Expression؟
\n\nFunc<T> هو مجرد مفوض. دالة جاهزة للتنفيذ، كود آلي مُجمّع. تمرر إليه البيانات — فيقوم بمعالجتها. سريع، فعال، لكن... غير شفاف.
\n\nFunc<User, bool> isAdult = user => user.Age >= 18;\nbool result = isAdult(someUser); // يتم التنفيذ فورًا
\n\nExpression<Func<T>> هو شجرة تعبيرات. ليس كودًا، بل وصف له. لا يمكنك استدعاؤه مباشرة — بل تحتاج إما إلى تجميعه إلى مفوض (عبر .Compile()) أو تحليل هيكله.
\n\nExpression<Func<User, bool>> expr = user => user.Age >= 18;\n// لا يمكن استدعاؤه مباشرة: expr(someUser) — خطأ في التجميع\nvar compiled = expr.Compile();\nbool result = compiled(someUser); // الآن يمكن
\n\nقد يبدو هذا مجهودًا إضافيًا غير ضروري؟ لكن هذه "الوصفية" هي التي تفتح الباب للسحر.
\n\nالسيناريو 1: LINQ to Objects (العمل في الذاكرة)
\n\nعند العمل مع المجموعات في الذاكرة (مثل List<T>)، يستخدم LINQ to Objects Func<T>. ليست هناك حاجة لأشجار التعبيرات — كل شيء يُنفذ في نفس العملية.
\n\nvar users = new List<User>();\nvar adults = users.Where(u => u.Age >= 18).ToList(); // Func<User, bool>
\n\nالأداء ممتاز: لا تكاليف إضافية لتحليل الشجرة. إذا كنت تعمل فقط مع بيانات محلية — استخدم Func بثقة. إنه أسرع وأبسط.
\n\nالسيناريو 2: LINQ to SQL / Entity Framework (العمل مع قاعدة البيانات)
\n\nوهنا يبدأ السحر. عندما تكتب:
\n\nvar adults = dbContext.Users.Where(u => u.Age >= 18).ToList();
\n\nEntity Framework لا ينفذ كود C# الخاص بك. بدلاً من ذلك، يقوم بتحليل شجرة التعبيرات المُمررة وتحويلها إلى استعلام SQL:
\n\nSELECT * FROM Users WHERE Age >= 18
\n\nإذا كنت قد مررت Func، لما استطاع EF "النظر إلى الداخل" — لكان قد حمّل جميع المستخدمين من الجدول ونفذ التصفية في الذاكرة. تخيل: جدول يحتوي على 10 ملايين سجل — وكلها تنتقل عبر الشبكة إلى تطبيقك لتُصفى على العميل.
\n\nExpression<Func<T>> يسمح لـ EF (أو أي مزود IQueryable آخر) بقراءة شرطك، وترجمته إلى لغة قاعدة البيانات (SQL، استعلام MongoDB، OData، إلخ) وتنفيذ التصفية على الخادم.
\n\nالأداء: اختبارات الأداء والأرقام الحقيقية
\n\nلنلقِ نظرة على الأرقام. لنأخذ مثالًا بسيطًا: تصفية 100,000 مستخدم حسب العمر.
\n\n// الخيار 1: Func — تحميل الكل، التصفية في الذاكرة\nvar allUsers = dbContext.Users.ToList(); // SELECT * FROM Users\nvar adults1 = allUsers.Where(u => u.Age >= 18).ToList();\n\n// الخيار 2: Expression — التصفية على الخادم\nvar adults2 = dbContext.Users.Where(u => u.Age >= 18).ToList(); // SELECT * FROM Users WHERE Age >= 18
\n\nالنتائج (تقريبية ولكنها توضيحية):
\n\n
تخيل: أنت تكتب كود C#. تحتاج إلى تمرير شرط تصفية إلى دالة. ماذا تختار: Func<User, bool> أم Expression<Func<User, bool>>؟ يبدو الفرق بسيطًا — كلاهما يقبل مستخدمًا ويعيد bool. لكن خلف الكواليس يكمن عالم كامل من الاختلافات.
على Stack Overflow، حصد هذا السؤال آلاف المشاهدات — وليس بدون سبب. اليوم سنفهم لماذا يمكن أن يكون Expression<Func<T>> أقوى وأسرع وأكثر أمانًا من Func<T> العادي. والأهم من ذلك — متى نستخدم كلًا منهما لتجنب المشاكل.
\n\nشرح سريع: ما هما Func و Expression؟
\n\nFunc<T> هو مجرد مفوض. دالة جاهزة للتنفيذ، كود آلي مُجمّع. تمرر إليه البيانات — فيقوم بمعالجتها. سريع، فعال، لكن... غير شفاف.
\n\nFunc<User, bool> isAdult = user => user.Age >= 18;\nbool result = isAdult(someUser); // يتم التنفيذ فورًا\n\nExpression<Func<T>> هو شجرة تعبيرات. ليس كودًا، بل وصف له. لا يمكنك استدعاؤه مباشرة — بل تحتاج إما إلى تجميعه إلى مفوض (عبر .Compile()) أو تحليل هيكله.
Expression<Func<User, bool>> expr = user => user.Age >= 18;\n// لا يمكن استدعاؤه مباشرة: expr(someUser) — خطأ في التجميع\nvar compiled = expr.Compile();\nbool result = compiled(someUser); // الآن يمكن\n\nقد يبدو هذا مجهودًا إضافيًا غير ضروري؟ لكن هذه "الوصفية" هي التي تفتح الباب للسحر.
\n\nالسيناريو 1: LINQ to Objects (العمل في الذاكرة)
\n\nعند العمل مع المجموعات في الذاكرة (مثل List<T>)، يستخدم LINQ to Objects Func<T>. ليست هناك حاجة لأشجار التعبيرات — كل شيء يُنفذ في نفس العملية.
var users = new List<User>();\nvar adults = users.Where(u => u.Age >= 18).ToList(); // Func<User, bool>\n\nالأداء ممتاز: لا تكاليف إضافية لتحليل الشجرة. إذا كنت تعمل فقط مع بيانات محلية — استخدم Func بثقة. إنه أسرع وأبسط.
\n\nالسيناريو 2: LINQ to SQL / Entity Framework (العمل مع قاعدة البيانات)
\n\nوهنا يبدأ السحر. عندما تكتب:
\n\nvar adults = dbContext.Users.Where(u => u.Age >= 18).ToList();\n\nEntity Framework لا ينفذ كود C# الخاص بك. بدلاً من ذلك، يقوم بتحليل شجرة التعبيرات المُمررة وتحويلها إلى استعلام SQL:
\n\nSELECT * FROM Users WHERE Age >= 18\n\nإذا كنت قد مررت Func، لما استطاع EF "النظر إلى الداخل" — لكان قد حمّل جميع المستخدمين من الجدول ونفذ التصفية في الذاكرة. تخيل: جدول يحتوي على 10 ملايين سجل — وكلها تنتقل عبر الشبكة إلى تطبيقك لتُصفى على العميل.
\n\nExpression<Func<T>> يسمح لـ EF (أو أي مزود IQueryable آخر) بقراءة شرطك، وترجمته إلى لغة قاعدة البيانات (SQL، استعلام MongoDB، OData، إلخ) وتنفيذ التصفية على الخادم.
\n\nالأداء: اختبارات الأداء والأرقام الحقيقية
\n\nلنلقِ نظرة على الأرقام. لنأخذ مثالًا بسيطًا: تصفية 100,000 مستخدم حسب العمر.
\n\n// الخيار 1: Func — تحميل الكل، التصفية في الذاكرة\nvar allUsers = dbContext.Users.ToList(); // SELECT * FROM Users\nvar adults1 = allUsers.Where(u => u.Age >= 18).ToList();\n\n// الخيار 2: Expression — التصفية على الخادم\nvar adults2 = dbContext.Users.Where(u => u.Age >= 18).ToList(); // SELECT * FROM Users WHERE Age >= 18\n\nالنتائج (تقريبية ولكنها توضيحية):
\n- \n