مقدمه
در درسهای گذشته، یاد گرفتیم که چگونه با استفاده از کلمهی کلیدی delegate، انواع نمایندهی
سفارشی خود را تعریف کنیم. این روش کاملاً کارآمد است، اما اغلب متوجه میشویم که در حال تعریف
نمایندههایی با الگوهای بسیار مشابه و تکراری هستیم؛ برای مثال، نمایندهای که هیچ پارامتری
نمیگیرد و void برمیگرداند، یا نمایندهای که یک string میگیرد و یک bool برمیگرداند.
برای جلوگیری از این تکرار و استانداردسازی کد، فریمورک .NET مجموعهای از
نمایندگان جنریک آماده را در فضای نام System فراهم کرده است. این نمایندگان
جنریک، نیاز ما به تعریف نمایندهی سفارشی را در اکثر قریب به اتفاق موارد از بین میبرند.
Action<T>: برای متدهایی که void برمیگردانند
نمایندهی جنریک Action برای کپسوله کردن متدهایی استفاده میشود که
هیچ مقداری را برنمیگردانند (یعنی void هستند). این نماینده چندین نسخهی سربارگذاری شده دارد:
- Action: به متدی اشاره دارد که هیچ پارامتری ندارد.
- Action<T>: به متدی اشاره دارد که یک پارامتر از نوع `T`
دارد.
- Action<T1, T2>: به متدی اشاره دارد که دو پارامتر از انواع
T1 و T2 دارد (و به همین ترتیب تا ۱۶ پارامتر).
بیایید مثال ProgressReporter از درس قبل را با استفاده از Action<int> بازنویسی کنیم.
FileDownloader.cs
public class FileDownloader
{
public Action<int> OnProgress;
public void DownloadFile()
{
for (int i = 0; i <= 100; i += 25)
{
OnProgress?.Invoke(i);
}
}
}
با این تغییر، ما از تعریف یک نوع نمایندهی اضافی خلاص شدیم و کد ما استانداردتر و خواناتر شد. هر
برنامهنویس C# با دیدن Action<int> فوراً متوجه میشود که با یک نماینده برای متدی که
یک int میگیرد و void برمیگرداند، سروکار دارد.
Func<T, TResult>: برای متدهایی که مقدار برمیگردانند
نمایندهی جنریک Func برای کپسوله کردن متدهایی استفاده میشود که
یک مقدار را برمیگردانند. این نماینده نیز چندین نسخهی سربارگذاری شده دارد.
قانون مهم: در تعریف Func، آخرین پارامتر نوع، همیشه نوع بازگشتی
(return type) متد است.
- Func<TResult>: به متدی اشاره دارد که هیچ پارامتری ندارد و
مقداری از نوع TResult برمیگرداند.
- Func<T, TResult>: به متدی اشاره دارد که یک پارامتر از نوع
T دارد و مقداری از نوع TResult برمیگرداند.
- Func<T1, T2, TResult>: به متدی اشاره دارد که دو پارامتر
از انواع T1 و T2 دارد و مقداری از نوع TResult برمیگرداند (و به همین ترتیب تا ۱۶
پارامتر ورودی).
بیایید مثال BinaryOperation را با استفاده از Func<int, int, int> بازنویسی کنیم.
Program.cs
public class Calculator
{
public static int Add(int a, int b) => a + b;
}
Func<int, int, int> addOperation = Calculator.Add;
int result = addOperation(20, 22);
Console.WriteLine(result);
Predicate<T>: حالت خاصی از Func
یک سناریوی بسیار رایج، نوشتن متدی است که یک مقدار را تست کرده و true یا false برمیگرداند.
برای این کار میتوانستیم از Func<T, bool> استفاده کنیم. اما فریمورک .NET یک
نمایندهی خاص و گویاتر برای این منظور فراهم کرده است: Predicate
.
Predicate دقیقاً معادل Func<T, bool> است. این نماینده متدی را
کپسوله میکند که یک پارامتر از نوع T گرفته و یک مقدار bool برمیگرداند. استفاده از آن کد
را خواناتر میکند، زیرا نام Predicate (به معنای گزاره) به وضوح هدف متد را مشخص میکند. این
نماینده اغلب در متدهای فیلتر کردن کالکشنها به کار میرود.
Program.cs
public static bool IsEven(int number)
{
return number % 2 == 0;
}
var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };
Predicate<int> evenNumberPredicate = IsEven;
List<int> evenNumbers = numbers.FindAll(evenNumberPredicate);
foreach (var num in evenNumbers)
{
Console.Write($"{num} ");
}
چه زمانی از کدام نماینده استفاده کنیم؟
- اگر متد شما مقداری برنمیگرداند (void)، از Action استفاده
کنید.
- اگر متد شما مقداری برمیگرداند، از Func استفاده کنید.
- اگر متد شما یک مقدار را برای برگرداندن true یا false بررسی میکند، از Predicate استفاده کنید تا هدف کد شما واضحتر باشد.
قانون کلی: در برنامهنویسی مدرن C#، تقریباً هرگز نیازی به تعریف یک
نوع delegate سفارشی نخواهید داشت. همیشه ابتدا بررسی کنید که آیا یکی از نمایندگان جنریک
Action، Func یا Predicate نیاز شما را برآورده میکند یا خیر.