C#: تعبيرات لامدا
1. نظرة عامة على تعبيرات لامدا
التعبير «لامبدا» هو صيغة موجزة للدوال المجهولة تسمح لك بتعريف دوال مضمنة باستخدام كمية أقل من التعليمات البرمجية. وهو ميزة أساسية في البرمجة الوظيفية بلغة C#، كما أنه الأساس الذي تستند إليه استعلامات LINQ.
2. بناء جملة لامدا
الصيغة الأساسية لتعبير لامدا هي: parameters => expression or statement block. يُقرأ => على أنه «يذهب إلى»، حيث يتم ربط المعلمات الموجودة على الجانب الأيسر بالعملية الموجودة على الجانب الأيمن.
▶ مثال
using System;
class Program
{
static void Main()
{
Func`<int, int>` square = x => x * x;
Console.WriteLine(square(5));
}
}
25
3. دال لامدا أحادية السطر ومتعددة الأسطر
تُرجع دالة لامدا المكونة من سطر واحد نتيجة التعبير مباشرةً دون الحاجة إلى الكلمة الرئيسية return أو الأقواس المتعرجة. أما دالة لامدا المكونة من عدة أسطر، فتتطلب أقواسًا متعرجة لإحاطة كتلة العبارات، بالإضافة إلى عبارة return صريحة.
▶ مثال
using System;
class Program
{
static void Main()
{
Func`<int, int>` singleLine = x => x * x;
Func`<int, int>` multiLine = x =>
{
int result = x * x;
return result;
};
Console.WriteLine(singleLine(4));
Console.WriteLine(multiLine(4));
}
}
16
16
4. استدلال الأنواع
يمكن للمترجم عادةً استنتاج أنواع معلمات لامدا من السياق، مما يلغي الحاجة إلى الإعلانات الصريحة. وعندما يكون السياق غامضًا، يمكنك تحديد الأنواع يدويًّا.
▶ مثال
using System;
class Program
{
static void Main()
{
Func`<int, int>` inferred = x => x + 1;
Func`<int, int>` explicitType = (int x) => x + 1;
Console.WriteLine(inferred(10));
Console.WriteLine(explicitType(10));
}
}
11
11
5. لامبدا والمندوبون
يمكن تعيين تعبيرات لامدا لأنواع الـ«ديليغيت»، مما يجعلها الطريقة الأكثر إيجازًا لإنشاء مثيلات الـ«ديليغيت». وبالمقارنة مع الطرق المجهولة التقليدية أو إنشاء مثيلات الـ«ديليغيت» بشكل صريح، فإن تعبيرات لامدا تكون أقصر.
▶ مثال
using System;
delegate int Transform(int x);
class Program
{
static void Main()
{
Transform doubleIt = x => x * 2;
Transform addTen = x => x + 10;
Console.WriteLine(doubleIt(3));
Console.WriteLine(addTen(3));
}
}
6
13
6. دال لامدا مع Func و Action
يمثل Func<T, TResult> مندوبًا عامًا له قيمة إرجاع، بينما يمثل Action<T> مندوبًا عامًا بدون قيمة إرجاع. ومن الشائع جدًّا استخدام تعبيرات لامدا معهما.
▶ مثال
using System;
class Program
{
static void Main()
{
Func`<int, int, int>` add = (a, b) => a + b;
Action```<string>``` greet = msg => Console.WriteLine(msg);
Console.WriteLine(add(3, 7));
greet("Hello");
}
}
10
Hello
7. دال لامدا والمسند
Predicate<T> هو مندوب يُرجع bool، ويُستخدم عادةً في عمليات التصفية والفحوصات الشرطية. وتجعل تعبيرات لامدا تعريف الشروط موجزًا للغاية.
▶ مثال
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
var numbers = new List```<int>``` { 1, 2, 3, 4, 5, 6 };
Predicate```<int>``` isEven = x => x % 2 == 0;
var evens = numbers.FindAll(isEven);
Console.WriteLine(string.Join(", ", evens));
}
}
2, 4, 6
8. عمليات الإغلاق
يمكن لتعبيرات لامدا التقاط المتغيرات الخارجية، وهو ما يُعرف باسم «الإغلاق». وتمتد مدة بقاء المتغيرات الملتقطة مع تعبير لامدا — فحتى بعد انتهاء النطاق الأصلي، تظل المتغيرات قائمة.
▶ مثال
using System;
class Program
{
static void Main()
{
int factor = 3;
Func`<int, int>` multiply = x => x * factor;
Console.WriteLine(multiply(5));
factor = 10;
Console.WriteLine(multiply(5));
}
}
15
50
▶ مثال
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
var actions = new List`<Action>`();
for (int i = 0; i < 3; i++)
{
int copy = i;
actions.Add(() => Console.WriteLine(copy));
}
foreach (var action in actions)
{
action();
}
}
}
0
1
2
i مباشرةً دون إنشاء نسخة منه، فستتشارك جميع دالات لامدا نفس المتغير i، وستكون النتيجة النهائية في جميع الحالات هي 3. ويمكن تجنب هذه المشكلة عن طريق إنشاء نسخة محلية.
9. لامدا في LINQ — مقدمة تمهيدية
تعد تعبيرات لامدا عنصراً أساسياً في استعلامات LINQ. تقبل العديد من أساليب LINQ معلمات Func أو Expression، وتوفر تعبيرات لامدا طريقة موجزة لكتابة الشروط المضمنة وعمليات الإسقاط ومنطق الفرز.
▶ مثال
using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static void Main()
{
var numbers = new List```<int>``` { 3, 7, 1, 9, 4, 6 };
var big = numbers.Where(x => x > 5);
var doubled = numbers.Select(x => x * 2);
var sorted = numbers.OrderBy(x => x);
Console.WriteLine(string.Join(", ", big));
Console.WriteLine(string.Join(", ", doubled));
Console.WriteLine(string.Join(", ", sorted));
}
}
7, 9, 6
6, 14, 2, 18, 8, 12
1, 3, 4, 6, 7, 9
10. مقدمة في أشجار التعبيرات
عندما يتم تعيين دالة لامدا إلى Expression<TDelegate>، يقوم المُجمِّع بإنشاء بنية بيانات (شجرة تعبير) بدلاً من كود قابل للتنفيذ. ويمكن تحليل أشجار التعبير وتعديلها وتحويلها إلى أشكال أخرى، مثل استعلامات SQL. وهذه هي الآلية الأساسية لتقنيات مثل LINQ to SQL.
▶ مثال
using System;
using System.Linq.Expressions;
class Program
{
static void Main()
{
Expression<Func`<int, int>`> expr = x => x * x + 1;
Console.WriteLine(expr.Body);
Console.WriteLine(expr.Parameters[0]);
var compiled = expr.Compile();
Console.WriteLine(compiled(4));
}
}
(x * x) + 1
x
17
ℹ️
Expression<Funcو<int,int>و>وFunc<int,int>هي أنواع مختلفة — فالأول عبارة عن بنية بيانات، أما الأخير فهو مندوب قابل للتنفيذ.
❓ أسئلة شائعة
📖 ملخص
- صيغة لامبدا هي
parameters => expression، وهي تُعرِّف الدوال المجهولة بشكل موجز - في تعبيرات لامدا المكونة من سطر واحد، تُستبعد الأقواس المتعرجة وعلامة «return»؛ أما تعبيرات لامدا المكونة من عدة أسطر فتتطلب كليهما
- يمكن للمترجم استنتاج أنواع معلمات لامدا؛ ويتوفر الإعلان الصريح عند الحاجة
- يمكن تعيين دال لامدا مباشرةً إلى مندوبي مخصصين، مثل Func وAction وPredicate
- تتيح عمليات الإغلاق لـ«لامبدا» التقاط المتغيرات الخارجية، مما يطيل مدة بقائها
- عند التقاط متغيرات الحلقة، قم بإنشاء نسخ منها لتجنب المشاركة غير المقصودة
- تعبر «لامبدا» عن الشكل الأساسي للمعلمات في أساليب LINQ
- تمثل أشجار التعبيرات تعبيرات لامدا كهياكل بيانات، مما يتيح إجراء التحليل والتحويل أثناء وقت التشغيل
📝 تمارين
- اكتب دالة لامدا
Func<double, double, double>تحسب القيمة القصوى لعددين - استخدم دالة لامدا
Action<string>لإخراج رسالة سجل مع طابع زمني - اكتب مثالاً على الإغلاق: أنشئ دالة عداد تُرجع عددًا صحيحًا متزايدًا في كل مرة يتم استدعاؤها
- استخدم
Whereمع دالة لامدا لتصفية السلاسل التي يزيد طولها عن 5 أحرف من قائمة السلاسل - قم بتعيين دالة لامدا
Func<int, bool>لكل منFunc<int, bool>وExpression<Func``<int, bool>``>، ثم اعرض نتائجهاGetType()على التوالي