مقدمه

به فصل دوازدهم خوش آمدید. در این فصل با یکی از قدرتمندترین و بنیادی‌ترین مفاهیم .NET یعنی نمایندگان (Delegates) آشنا می‌شویم. اگر با زبان‌هایی مانند C/C++ آشنا باشید، احتمالاً با مفهوم "اشاره‌گر به تابع" (function pointer) برخورد داشته‌اید. یک نماینده در C#، معادل مدرن، شیءگرا و امن همان مفهوم است. یک نماینده، یک نوع داده‌ی ارجاعی ویژه است که به جای نگهداری داده، یک ارجاع به یک یا چند متد را در خود نگه می‌دارد. این قابلیت به ما اجازه می‌دهد تا متدها را مانند متغیرها به اطراف پاس دهیم، آن‌ها را به عنوان پارامتر به متدهای دیگر ارسال کنیم و کدهایی بسیار انعطاف‌پذیر و پویا بنویسیم. نمایندگان، اساس و پایه‌ی برنامه‌نویسی رویدادمحور (event-driven) و عبارات لامبدا در C# هستند.

نماینده (Delegate) چیست؟

یک نماینده را می‌توان به عنوان یک "قرارداد" یا "امضا" برای یک متد در نظر گرفت. شما یک نوع نماینده تعریف می‌کنید که مشخص می‌کند چه نوع متدهایی (با چه پارامترهای ورودی و چه نوع خروجی) می‌توانند به آن اختصاص داده شوند. پس از آن، می‌توانید هر متدی را که با آن امضا مطابقت داشته باشد، درون یک متغیر از آن نوع نماینده قرار دهید و سپس آن متد را از طریق آن متغیر فراخوانی کنید.

تعریف و استفاده از یک نماینده

کار با نمایندگان شامل سه مرحله‌ی اصلی است: تعریف نوع نماینده، ساخت نمونه و تخصیص متد، و در نهایت فراخوانی نماینده.

قدم اول: تعریف نوع نماینده

ابتدا باید با استفاده از کلمه‌ی کلیدی delegate، امضای متدهایی که می‌خواهیم به آن‌ها ارجاع دهیم را تعریف کنیم.

Copy Icon Program.cs
// 1. Declare a delegate type.
// This delegate can hold any method that takes two integers and returns an integer.
public delegate int BinaryOperation(int x, int y);

در اینجا، ما یک نوع جدید به نام BinaryOperation تعریف کرده‌ایم.

قدم دوم و سوم: ساخت نمونه و فراخوانی

اکنون می‌توانیم متدهایی بسازیم که با این امضا مطابقت دارند و یک نمونه از نماینده‌ی خود را برای ارجاع به آن‌ها ایجاد کنیم.

Copy Icon Program.cs
public class Calculator
{
    public static int Add(int a, int b) => a + b;
    public static int Subtract(int a, int b) => a - b;
}

// --- In Main method ---

// 2. Create an instance of the delegate and point it to a method.
BinaryOperation op = Calculator.Add; // Simple syntax

// 3. Invoke the delegate as if it were the method itself.
int result1 = op(10, 5);
Console.WriteLine(result1); // Output: 15

// Now, point the same delegate instance to another method.
op = Calculator.Subtract;
int result2 = op(10, 5);
Console.WriteLine(result2); // Output: 5

نماینده‌های چندپخشی (Multicast Delegates)

یکی از ویژگی‌های بسیار قدرتمند نمایندگان این است که می‌توانند به بیش از یک متد به طور همزمان ارجاع دهند. به این نوع نماینده، نماینده‌ی چندپخشی گفته می‌شود. شما می‌توانید با استفاده از عملگرهای + یا += متدهای جدیدی را به لیست فراخوانی یک نماینده اضافه کنید و با - یا -= آن‌ها را حذف کنید.

Copy Icon Program.cs
public delegate void ProgressReporter(int percentComplete);

public class Util
{
    public static void ReportToConsole(int percent) { Console.WriteLine($"Progress: {percent}%"); }
    public static void ReportToFile(int percent) { System.IO.File.AppendAllText("progress.log", $"Progress: {percent}%\n"); }
}

// --- Usage ---
ProgressReporter reporter = Util.ReportToConsole;
reporter += Util.ReportToFile; // Add another method to the invocation list.

// Now, when we invoke the delegate, BOTH methods are called.
reporter(50);

reporter -= Util.ReportToConsole; // Remove a method.
reporter(100);

وقتی reporter(50) فراخوانی می‌شود، ابتدا متد ReportToConsole و سپس ReportToFile اجرا می‌گردد. این قابلیت در برنامه‌نویسی رویدادمحور که در آن چندین "شنونده" ممکن است بخواهند به یک رویداد واکنش نشان دهند، بسیار کاربردی است.

نماینده به عنوان پارامتر متد

یکی از رایج‌ترین کاربردهای نمایندگان، ارسال آن‌ها به عنوان پارامتر به متدهای دیگر است. این کار به شما اجازه می‌دهد تا بخشی از منطق یک متد را به صورت یک "پلاگین" یا "callback" از بیرون به آن تزریق کنید.

// This method accepts a delegate as a parameter to define its operation.
public static void ProcessNumbers(List<int> numbers, BinaryOperation operation)
{
    foreach (var num in numbers)
    {
        // Invoke the passed-in delegate.
        Console.WriteLine(operation(num, 10));
    }
}

List<int> myNumbers = new() { 5, 8, 12 };

Console.WriteLine("--- Adding ---");
ProcessNumbers(myNumbers, Calculator.Add);

Console.WriteLine("\n--- Subtracting ---");
ProcessNumbers(myNumbers, Calculator.Subtract);

در این الگو، متد ProcessNumbers یک منطق کلی (پیمایش لیست) را پیاده‌سازی می‌کند، اما عملیات مشخصی که روی هر عنصر انجام می‌شود (Add یا Subtract) توسط کد فراخواننده تعیین می‌گردد. این روش کد را بسیار انعطاف‌پذیر و قابل استفاده مجدد می‌کند.