C#: تعبيرات لامدا

1. نظرة عامة على تعبيرات لامدا

التعبير «لامبدا» هو صيغة موجزة للدوال المجهولة تسمح لك بتعريف دوال مضمنة باستخدام كمية أقل من التعليمات البرمجية. وهو ميزة أساسية في البرمجة الوظيفية بلغة C#، كما أنه الأساس الذي تستند إليه استعلامات LINQ.


2. بناء جملة لامدا

الصيغة الأساسية لتعبير لامدا هي: parameters => expression or statement block. يُقرأ => على أنه «يذهب إلى»، حيث يتم ربط المعلمات الموجودة على الجانب الأيسر بالعملية الموجودة على الجانب الأيمن.

▶ مثال

CSHARP
using System;

class Program
{
    static void Main()
    {
        Func`<int, int>` square = x => x * x;
        Console.WriteLine(square(5));
    }
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
25

3. دال لامدا أحادية السطر ومتعددة الأسطر

تُرجع دالة لامدا المكونة من سطر واحد نتيجة التعبير مباشرةً دون الحاجة إلى الكلمة الرئيسية return أو الأقواس المتعرجة. أما دالة لامدا المكونة من عدة أسطر، فتتطلب أقواسًا متعرجة لإحاطة كتلة العبارات، بالإضافة إلى عبارة return صريحة.

▶ مثال

CSHARP
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));
    }
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
16
16

4. استدلال الأنواع

يمكن للمترجم عادةً استنتاج أنواع معلمات لامدا من السياق، مما يلغي الحاجة إلى الإعلانات الصريحة. وعندما يكون السياق غامضًا، يمكنك تحديد الأنواع يدويًّا.

▶ مثال

CSHARP
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));
    }
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
11
11

5. لامبدا والمندوبون

يمكن تعيين تعبيرات لامدا لأنواع الـ«ديليغيت»، مما يجعلها الطريقة الأكثر إيجازًا لإنشاء مثيلات الـ«ديليغيت». وبالمقارنة مع الطرق المجهولة التقليدية أو إنشاء مثيلات الـ«ديليغيت» بشكل صريح، فإن تعبيرات لامدا تكون أقصر.

▶ مثال

CSHARP
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));
    }
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
6
13

6. دال لامدا مع Func و Action

يمثل Func<T, TResult> مندوبًا عامًا له قيمة إرجاع، بينما يمثل Action<T> مندوبًا عامًا بدون قيمة إرجاع. ومن الشائع جدًّا استخدام تعبيرات لامدا معهما.

▶ مثال

CSHARP
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");
    }
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
10
Hello

7. دال لامدا والمسند

Predicate<T> هو مندوب يُرجع bool، ويُستخدم عادةً في عمليات التصفية والفحوصات الشرطية. وتجعل تعبيرات لامدا تعريف الشروط موجزًا للغاية.

▶ مثال

CSHARP
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));
    }
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
2, 4, 6

8. عمليات الإغلاق

يمكن لتعبيرات لامدا التقاط المتغيرات الخارجية، وهو ما يُعرف باسم «الإغلاق». وتمتد مدة بقاء المتغيرات الملتقطة مع تعبير لامدا — فحتى بعد انتهاء النطاق الأصلي، تظل المتغيرات قائمة.

▶ مثال

CSHARP
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));
    }
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
15
50
💡 تقوم الإغلاق (closure) بالتقاط المتغير نفسه، وليس قيمته، لذا فإن تعديل المتغير الخارجي يؤثر على نتيجة تنفيذ دالة لامدا.

▶ مثال

CSHARP
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();
        }
    }
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
0
1
2
⚠️ إذا قمت بالتقاط متغير الحلقة i مباشرةً دون إنشاء نسخة منه، فستتشارك جميع دالات لامدا نفس المتغير i، وستكون النتيجة النهائية في جميع الحالات هي 3. ويمكن تجنب هذه المشكلة عن طريق إنشاء نسخة محلية.


9. لامدا في LINQ — مقدمة تمهيدية

تعد تعبيرات لامدا عنصراً أساسياً في استعلامات LINQ. تقبل العديد من أساليب LINQ معلمات Func أو Expression، وتوفر تعبيرات لامدا طريقة موجزة لكتابة الشروط المضمنة وعمليات الإسقاط ومنطق الفرز.

▶ مثال

CSHARP
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));
    }
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
7, 9, 6
6, 14, 2, 18, 8, 12
1, 3, 4, 6, 7, 9

10. مقدمة في أشجار التعبيرات

عندما يتم تعيين دالة لامدا إلى Expression<TDelegate>، يقوم المُجمِّع بإنشاء بنية بيانات (شجرة تعبير) بدلاً من كود قابل للتنفيذ. ويمكن تحليل أشجار التعبير وتعديلها وتحويلها إلى أشكال أخرى، مثل استعلامات SQL. وهذه هي الآلية الأساسية لتقنيات مثل LINQ to SQL.

▶ مثال

CSHARP
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));
    }
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
(x * x) + 1
x
17

ℹ️ Expression<Func و<int,int> و> وFunc<int,int> هي أنواع مختلفة — فالأول عبارة عن بنية بيانات، أما الأخير فهو مندوب قابل للتنفيذ.


❓ أسئلة شائعة

س ما الفرق بين لغة لامدا والأساليب المجهولة؟
ج لغة لامدا أكثر إيجازًا، وتدعم استنتاج الأنواع وأشجار التعبيرات؛ أما لغة الأساليب المجهولة فهي مطولة وتتميز بميزات أقل.
س متى يجب تحديد أنواع معلمات Lambda بشكل صريح؟
ج عندما يتعذر على المُجمِّع استنتاج النوع من السياق، مثل حالات أنواع المعلمات الغامضة أو تعارضات حل التحميل الزائد.
س متى يتم تحرير المتغيرات الملتقطة في تعبيرات لامدا؟
ج لا يتم تحرير المتغيرات الملتقطة إلا بعد إزالة جميع تعبيرات لامدا التي تشير إليها من الذاكرة عن طريق عملية جمع القمامة.
س هل يمكن تحويل Expression و Func إلى بعضهما البعض؟
ج يمكن تحويل Expression إلى Func باستخدام Compile()، ولكن لا يمكن تحويل Func مباشرةً إلى Expression.

📖 ملخص

📝 تمارين

  1. اكتب دالة لامدا Func<double, double, double> تحسب القيمة القصوى لعددين
  2. استخدم دالة لامدا Action<string> لإخراج رسالة سجل مع طابع زمني
  3. اكتب مثالاً على الإغلاق: أنشئ دالة عداد تُرجع عددًا صحيحًا متزايدًا في كل مرة يتم استدعاؤها
  4. استخدم Where مع دالة لامدا لتصفية السلاسل التي يزيد طولها عن 5 أحرف من قائمة السلاسل
  5. قم بتعيين دالة لامدا Func<int, bool> لكل من Func<int, bool> وExpression<Func``<int, bool>``>، ثم اعرض نتائجها GetType() على التوالي
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%