What are Delegates in C#?

ببساطة، نقدر نقول إن الـ delegates في لغة C# هي Pointer لـ Function بس أمان من ناحية النوع (Type-Safe Function Pointer). ده معناه إنها بتحمل reference لـ method أو function، وبعدين بتستدعي الـ method دي عشان تتنفذ.

عشان نقرب الفكرة، تخيل إن الـ Delegate ده عامل زي “المندوب” أو إنك بتعمل “تفويض”. لو إنت مدير في شركة وعايز تدي مهمة لموظف، إنت ممكن تختار أي موظف يعمل المهمة دي من غير ما تعرف اسمه، المهم يكون عنده نفس الشروط (مثلاً: خبرة في مجال معين). في البرمجة، الـ Delegate هو نوع بيشاور لـ method معينة، وبيسمحلك تستدعي أي method من خلاله طالما “شكلهم” واحد (نفس الـ Signature، يعني نفس عدد وأنواع الـ parameters ونفس نوع الـ return type). كل اللي بتعرفه عنها هو الـ Signature، لكن هي فين؟ في أي class؟ أو بتعمل إيه؟ كل ده ميخصكش. إنت بتناديها وبتبعتلها الـ parameters وهي بترجعلك النتيجة.

Why use Delegates?

  • تجنب الـ if-else الكتير: زي ما هنشوف في مثال الآلة الحاسبة، بدل ما تعمل شروط كتير، تقدر تاخد العملية الحسابية نفسها كـ parameter.
  • المرونة (Flexibility): تقدر تضيف عمليات جديدة أو تغير السلوك من غير ما تغير أو تعدل في الكود الأساسي.
  • الاستخدام في الـ Event: أغلب أنظمة الـ UI (زي إنك تضغط على زرار) بتستخدم Delegates.

The Calculator Example: Before and After

عشان نفهم قوة الـ Delegates بجد، خلينا نعمل 4 methods بسيطة للعمليات الحسابية:

public static int Add(int num1, int num2)
{
    Console.WriteLine("Add Method Called");
    return num1 + num2;
}
 
public static int Subtract(int num1, int num2)
{
    Console.WriteLine("Subtract Method Called");
    return num1 - num2;
}
 
public static int Multiply(int num1, int num2)
{
    Console.WriteLine("Multiply Method Called");
    return num1 * num2;
}
 
public static int Divide(int num1, int num2)
{
    Console.WriteLine("Divide Method Called");
    return num1 / num2;
}

The Old Way (Without Delegates)

الطريقة القديمة إننا نبعت parameter يحدد العملية المطلوبة ونستخدم if-else أو switch:

public static void Calculator(int a, int b, char op)
{
    int result = 0;
    if (op == '+')
        result = Add(a, b);
    else if (op == '-')
        result = Subtract(a, b);
    else if (op == '*')
        result = Multiply(a, b);
    else if (op == '/')
        result = Divide(a, b);
    else 
        throw new ArgumentException("Invalid Operator");
 
    Console.WriteLine($"Result: {result}");
}
 
// Main
Calculator(10, 2, '+'); // 12
Calculator(10, 2, '-'); // 8

الـ method دي فيها مشاكل كتير:

  1. محدودة: بتدعم 4 عمليات بس. لو حبيت أزود عملية (زي باقي القسمة %)، لازم أرجع أعدل في الكود.
  2. غير مرنة: مش هتقدر تعمل بيها عمليات معقدة.
  3. معقدة: الـ if-else الكتير بيخليها معقدة وضد مبادئ الـ Clean Code.

The New Way (With Delegates)

هنرمي الـ if-else دي خالص. هنعرف delegate كالتالي:

// This delegate can point to any method that takes two integers and returns an integer.
public delegate int CalculatorDelegate(int a, int b);

ونعدل الـ Calculator عشان تستقبل الـ delegate:

public static void CalculatorWithDelegate(int a, int b, CalculatorDelegate operation)
{
    // Invoke the delegate directly
    int result = operation(a, b);
    Console.WriteLine($"Result: {result}");
}

شفت البساطة؟ دلوقتي بنناديها كالتالي:

static void Main()
{
    // Pass the method name directly as a parameter
    CalculatorWithDelegate(10, 2, Add);      // 12
    CalculatorWithDelegate(10, 2, Subtract); // 8
    CalculatorWithDelegate(10, 2, Multiply); // 20
    CalculatorWithDelegate(10, 2, Divide);   // 5
}

مهم جداً: إحنا بنبعت اسم الـ method بس (زي Add)، مش بنناديها. الـ delegate هو اللي بيناديها من جوه.


How to Create a Custom Delegate?

انك تعمل custom delegate عملية بسيطة جداً. بنستخدم الكلمة delegate. الكلمة دي تعتبر سحرية شوية، لإن وراء الكواليس، لما الـ compiler بيشوف الـ delegate keyword، هو في الحقيقة بينشئ class بيورث من delegate classes تانية في .NET Framework.

الـ syntax عشان نعمل delegate شبه الـ abstract method جداً، الفرق بس إننا بنستخدم delegate بدل abstract:

<Access Modifier> delegate <Return Type> <Delegate Name> (Parameter List);

ده مثال عملي على delegate:

public delegate void WorkPerformedHandler(int hours, WorkType workType);

الـ delegate ده ممكن تفكر فيه على إنه خط أنابيب (pipeline) في اتجاه واحد. هو بيرجع void، يعني مفيش حاجة هترجع، البيانات بس بتمشي لقدام. اسم الـ delegate هو WorkPerformedHandler وبياخد اتنين parameters. البيانات هتتنقل باستخدام الـ pipeline ده. في الحالة دي، الـ pipeline بيقبل بس اتنين parameters ولازم يكونوا من نوع int و WorkType، وإلا مش هيتعمله compile.

الـ delegate يعتبر blueprint للـ method اللي هتنقل البيانات للـ event handler. اللي بنكون عايزينه هو طريقة ننقل بيها شوية بيانات من (Point A) لـ (Point B) اللي هي هتبقى الـ Handler method بتاعتنا.

قاعدة مهمة جداً: الـ signature بتاع الـ delegate والـ handler method signature لازم يكونوا متطابقين. بما إننا عرفنا الـ delegate باتنين parameters، يبقى الـ handler لازم يكون عنده نفس العدد، والنوع، والترتيب. أسماء الـ parameters مش مهمة، المهم النوع والترتيب والعدد.

// The delegate signature
public delegate void WorkPerformedHandler(int hours, WorkType workType);
 
// The handler method signature must match the delegate's
public static void Manager_WorkPerformed(int workHours, WorkType wType)
{
    // Implementation
}

How to use Delegates in C#?

عشان تستدعي method باستخدام delegates، لازم تتبع الـ 3 خطوات دي:

  1. الـ Declare a Delegate: تعلن عن الـ delegate.
  2. الـ Instantiate a Delegate: تعمل instance منه وتمررله اسم الـ method (الـ Handler Method). لو هي static بتناديها باسمها أو باسم الـ class، ولو non-static بتناديها عن طريق object instance.
  3. الـ Invoke a Delegate: تستدعي الـ delegate.

Basic Invocation Example

بنفس الطريقة اللي بنستدعي بيها method عادية، نقدر نستدعي الـ delegate، أو نستخدم الـ Invoke method.

using System;
namespace DelegatesDemo
{
    // 1. Declare
    public delegate void WorkPerformedHandler(int hours, WorkType workType);
    
    class Program
    {
        static void Main(string[] args)
        {
            // 2. Instantiate (Manager_WorkPerformed is the handler)
            WorkPerformedHandler del1 = new WorkPerformedHandler(Manager_WorkPerformed);
            
            // 3. Invoke (Dynamic call at runtime)
            del1(10, WorkType.Golf);
            
            // del1.Invoke(50, WorkType.GotoMeetings); // This also works
            Console.ReadKey();
        }
        
        // Match the delegate signature
        public static void Manager_WorkPerformed(int workHours, WorkType wType)
        {
            Console.WriteLine("Work Performed by Event Handler");
            Console.WriteLine($"Work Hours: {workHours}, Work Type: {wType}");
        }
    }
    
    public enum WorkType
    {
        Golf,
        GotoMeetings,
        GenerateReports
    }
}

Output:

Work Performed by Event Handler
Work Hours: 10, Work Type: Golf

Different Ways to Use Delegates

فيه كذا طريقة نقدر نبعت بيهم الـ logic للـ delegate:

1. الـDirectly: زي المثال اللي فات (بنبعت اسم الـ method). 2. الـ Variable: نعمل متغير من نوع الـ delegate.

CalculatorDelegate myDelegate = Add;
CalculatorWithDelegate(10, 2, myDelegate);
 
myDelegate = Subtract;
CalculatorWithDelegate(10, 2, myDelegate);

3. Anonymous Methods (Old way): لو العملية بسيطة، نكتبها من غير ما نعمل method منفصلة.

CalculatorWithDelegate(10, 2, delegate(int n1, int n2) {
    return n1 * n2;
});

4. Lambda Expressions (Modern and Preferred way): طريقة مختصرة وأقوى بكثير (تسمى Arrow Function).

// Lambda expression for multiplication
CalculatorWithDelegate(10, 5, (a, b) => a * b); // 50
 
// Lambda expression for division
CalculatorWithDelegate(10, 2, (x, y) => x / y); // 5
 
// Adding Modulus without changing the main method!
CalculatorWithDelegate(10, 3, (x, y) => x % y); // 1

Callbacks using Delegates

الـ Delegates بتُستخدم عشان نستدعي call-back functions. ده معناه إننا هنستدعي function ونمرر delegate instance كـ parameter ليها، ونتوقع إنها تستدعي الـ delegate في وقت معين.

using System;
namespace DelegatesDemo
{
    public delegate void CallbackMethodHandler(string message);
    
    class Program
    {
        static void Main(string[] args)
        {
            Program obj = new Program();
            CallbackMethodHandler del1 = new CallbackMethodHandler(obj.CallbackMethod);
            
            // Pass the delegate to another method
            DoSomework(del1);
            Console.ReadKey();
        }
        
        public static void DoSomework(CallbackMethodHandler del)
        {
            Console.WriteLine("Processing some Task");
            // Invoke the callback method via the delegate
            del("Pranaya");
        }
        
        public void CallbackMethod(string message)
        {
            Console.WriteLine("CallbackMethod Executed");
            Console.WriteLine($"Hello: {message}, Good Morning");
        }
    }
}

Output:

Processing some Task
CallbackMethod Executed
Hello: Pranaya, Good Morning

Another Practical Example: Filtering Employees

تخيل عندنا List من الموظفين وعايزين نعمل method تحسب مرتباتهم بناءً على شروط متغيرة (مثلاً اللي راتبه أقل من 2000، أو اللي اسمه بيبدأ بحرف معين).

public class Employee
{
    public string Name { get; set; }
    public int Salary { get; set; } // BasicSalary
}

بدل ما نعمل if conditions كتير جوه الـ method، هنخليها تستقبل delegate يرجع boolean:

public delegate bool FilterDelegate(Employee emp);
 
public static void ProcessSalaries(List<Employee> employees, FilterDelegate filter)
{
    foreach (var emp in employees)
    {
        // Use the delegate to check if we should process this employee
        if (filter(emp))
        {
            Console.WriteLine($"Processing salary for {emp.Name}");
        }
    }
}

دلوقتي نقدر نمرر أي شرط إحنا عايزينه باستخدام Lambda:

// Process salaries for employees with Salary < 2000
ProcessSalaries(employees, emp => emp.Salary < 2000);
 
// Process salaries for employees with Salary > 5000
ProcessSalaries(employees, emp => emp.Salary > 5000);
 
// Process salaries for names starting with "Employee 5"
ProcessSalaries(employees, emp => emp.Name.StartsWith("Employee 5"));

الـ method دلوقتي متعرفش حاجة عن الـ business rules، هي بتلف وتسأل الـ delegate بس.


Types of Delegates

  1. الـ Single Cast Delegate: الـ delegate بيشير لـ method واحدة بس.
  2. الـ Multicast Delegate: الـ delegate بيشير لأكتر من method في نفس الوقت.

Deep Dive into Multicast Delegates

تقدر تخلي المندوب يشير لأكتر من method باستخدام علامة +=.

CalculatorDelegate multiDelegate = Add;
multiDelegate += Subtract; 
multiDelegate += Multiply;
multiDelegate += Divide;
 
CalculatorWithDelegate(10, 2, multiDelegate);

إيه اللي هيحصل؟ لما يتنادى، هينفذ الـ methods دي بالترتيب (Add ثم Subtract وهكذا). النتيجة النهائية اللي هترجع هتكون نتيجة آخر method اتنفذت (Divide).

وممكن نشيل method باستخدام -=:

multiDelegate -= Multiply; // Remove the Multiply method

تحذير: لو فضلت تشيل methods لحد ما الـ delegate بقى فاضي وبيشاور على null وحاولت تناديه، هيضرب NullReferenceException.


Rules and Usage of Delegates

القواعد الأساسية:

  • الـ Delegate هو user-defined type، لازم تعرّفه الأول.
  • الـ Signature لازم يكون متطابق عشان متخدش compiler error.

بنستخدمهم فين؟

  • Event Handlers
  • Callbacks
  • تمرير Methods كـ Parameters
  • LINQ
  • Multithreading

What happens behind the scene?

1. Delegate Base Class

واحد من الـ classes الأساسية جداً في .NET Framework هو Delegate، واللي بيوفر شوية وظائف أساسية. لو روحت لتعريف Delegate class هتلاقيه abstract class.

الـ class ده بيوفر خاصيتين مهمين:

  • الـ public MethodInfo Method {get;}: بتُستخدم عشان تجيب الـ method اللي بيمثلها الـ delegate (شكلها إيه).
  • الـ public object Target {get;}: بتُستخدم عشان تجيب الـ class instance اللي الـ delegate بيستدعي عليه الـ instance method. (بترجع null لو الـ delegate بيمثل static method).

كمان عنده virtual method مهمة جداً:

  • الـ public virtual Delegate[] GetInvocationList(): بترجع قايمة الـ methods اللي الـ delegate ده بيشاور عليها.

2. MulticastDelegate Base Class

في core class تاني مهم اسمه MulticastDelegate. لو روحت لتعريفه هتلاقيه برضه abstract class وبيورث من الـ Delegate abstract class.

كل delegate إحنا بنعمله في الكود، لما بيتعمله compile، بيورث أوتوماتيك من MulticastDelegate. وده اللي بيدينا القدرة إننا نخلي الـ delegate يشيل أكتر من method في نفس الوقت.

graph TD
    A[System.Object] --> B[System.Delegate]
    B --> C[System.MulticastDelegate]
    C --> D[Your Custom Delegate]
    
    style A fill:#f9f,stroke:#333,stroke-width:2px
    style B fill:#bbf,stroke:#333,stroke-width:2px
    style C fill:#bbf,stroke:#333,stroke-width:2px
    style D fill:#dfd,stroke:#333,stroke-width:2px

ملاحظة: في الكود بتاعك، متقدرش تورث بشكل مباشر من Delegate أو MulticastDelegate. الطريقة الوحيدة هي إنك تستخدم delegate keyword والـ compiler هيعمل الباقي. لو بصيت على الـ IL Code بتاع الـ delegate، هتلاقيه عبارة عن sealed class بيعمل extends لـ MulticastDelegate.


Delegate Properties and Methods in Action

زي ما قلنا فوق عن خصائص الـ Delegate class، خلينا نشوفهم في الكود:

using System;
using System.Reflection;
namespace DelegatesDemo
{
    public delegate void DoSomeMethodHandler(string message);
    
    class Program
    {
        static void Main(string[] args)
        {
            SomeClass obj = new SomeClass();
            DoSomeMethodHandler del1 = new DoSomeMethodHandler(obj.DoSomework);
            
            // Get information about the delegate
            MethodInfo Method = del1.Method;
            object Target = del1.Target;
            Delegate[] InvocationList = del1.GetInvocationList();
            
            Console.WriteLine($"Method Property: {Method}");
            Console.WriteLine($"Target Property: {Target}");
           
            foreach (var item in InvocationList)
            {
                Console.WriteLine($"InvocationList: {item}");
            }
        }
    }
    
    public class SomeClass
    {
        public void DoSomework(string message)
        {
            Console.WriteLine("DoSomework Executed");
        }
    }
}

Output:

Method Property: Void DoSomework(System.String)
Target Property: DelegatesDemo.SomeClass
InvocationList: DelegatesDemo.DoSomeMethodHandler
  • الـ Method property هترجع الـ prototype بتاع الـ method اللي الـ delegate بيشاور عليها، وفي مثالنا هتبقى Void DoSomework(System.String).
  • الـ Target property هترجع اسم الـ class الكامل (fully qualified class name) اللي الـ event handler method بتنتمي ليه، وفي مثالنا هو DelegatesDemo.SomeClass.
  • الـ GetInvocationList method هترجع قايمة الـ delegates اللي الـ delegate بيشير ليها، وفي الحالة دي delegate واحد بس. في مقالنا الجاي، هنفهم الـ multicast delegate وفي الحالة دي هترجع أكتر من delegate.

Generic Delegates

الـ Generic Delegates هي تحسين قوي بيخليك تعرف delegates بنوع عام (Generic Type)، وده بيخليها:

  • أكثر مرونة: بتشتغل مع أنواع بيانات مختلفة.
  • أكثر أماناً: بتضمن تطابق الأنواع.
  • أقل تكراراً: بتقلل تعريف delegates كتير.

لغة C# بتوفر 3 أنواع جاهزة (Built-in) ومهمين جداً:

1. Action<T>

بتُستخدم للـ methods اللي مش بترجع حاجة (void).

Action<string> printAction = s => Console.WriteLine(s);
printAction("Hello Generics!"); // Output: Hello Generics!

2. Func<T, TResult>

بتُستخدم للـ methods اللي بترجع قيمة. (آخر Parameter في القوس هو الـ Return Type).

Func<int, int, int> add = (a, b) => a + b;
int result = add(5, 3); // Output: 8

3. Predicate<T>

بتُستخدم للـ methods اللي بترجع boolean (true أو false).

Predicate<int> isEven = num => num % 2 == 0;
bool test = isEven(4); // Output: true

Integration with Lists (List<T>)

الأنواع دي بنستخدمها بشكل مكثف مع الـ LINQ وعمليات الـ Lists:

List<int> numbers = new List<int> {1, 2, 3, 4, 5};
 
// Action with ForEach
numbers.ForEach(n => Console.Write(n + " ")); // 1 2 3 4 5 
 
// Predicate with FindAll
var evens = numbers.FindAll(n => n % 2 == 0); // [2, 4]
 
// Func with Select
var squares = numbers.Select(n => n * n); // [1, 4, 9, 16, 25]

وممكن طبعاً تعمل الـ Generic Delegate بتاعك:

public delegate TOutput MyConverter<TInput, TOutput>(TInput input);
 
// Using:
MyConverter<int, string> intToString = num => num.ToString();
string result = intToString(42); // "42"

Common Pitfalls

  • الـ Null Reference Exception: تأكد دايماً إن الـ Delegate مش null قبل ما تعمل ليه Invoke (ممكن تستخدم ?.Invoke()).
  • الـ Signature: لازم شكل الـ method يطابق الـ Delegate بالضبط.
  • الـ Memory Leaks: لو استخدمت Delegates مع Events، متنساش تعمل unsubscribe (باستخدام -=) لما تخلص عشان متخليش الذاكرة تتهدر.

Delegate vs Interface

  • الـ Delegate: مناسب للمهام البسيطة اللي بتحتاج تمرر method واحدة كـ Callback.
  • الـ Interface: أنسب لو عندك مجموعة من الـ Methods المترابطة وعايز تفرض “عقد” (Contract) كامل على الـ class.

Notes

  • االاستدعاء المباشر للـ method (Direct Invocation) هو دايماً الأسرع لأن الـ Delegate بيضيف overhead بسيط جداً وقت التنفيذ لأنه بيعمل indirect call. ورغم إن الـ overhead ده شبه معدوم في التطبيقات الحديثة، إلا إن الـ Delegate أبطأ بنسبة طفيفة جداً من الاستدعاء المباشر، وليس أسرع.
    • في التحديثات الجديدة الخاصة بـ C# 13 وما بعدها وصولاً لـ .NET 10، الـ Delegates بقت بتدعم استخدام الـ params keyword مع كل أنواع الـ Collections (زي Span<T> و List<T>) مش بس الـ Arrays زي زمان، وده بيحسن جداً من الأداء وبيقلل استهلاك الميموري وقت استدعاء الـ Delegates اللي بتاخد عدد متغير من الـ parameters.
    • الـ Function Pointers (delegate*): للناس المهتمين بالـ High Performance، الـ C# وفرت Function Pointers بتُكتب كـ delegate* جوه الـ unsafe code، ودي بتديك سرعة استدعاء مماثلة لـ C++ بدون أي Memory Allocations أو Overhead خاص بالـ Classes بتاعة الـ Delegate.