Menu
flag Ar iconالعربيةdown icon

Func وAction في C#: المفوّضات العامة وPredicate وأمثلة

كيف تعمل Func وAction في C#: Func<T, TResult> للدوال التي تعيد قيمة، وAction<T> للتي لا تعيد، وPredicate وComparison، وتمرير الدوال وإعادتها، وقاموس الأوامر، وتعابير lambda غير المتزامنة، ومتى تصرّح بمفوّضك الخاص بدلًا منها.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

Func وAction أنواع مفوّضات عامة تصرّح بها .NET لك، فتستطيع تخزين الدوال وتمريرها دون التصريح بنوع مفوّض أولًا. تعيد Func قيمة؛ ولا تعيد Action شيئًا.

المخرجات:

4
46.00
False
Hello!
Mia scored 87

القاعدة التي تتذكرها: في Func يكون وسيط النوع الأخير نوع الإرجاع. تأخذ Func<int, int, bool> قيمتي int وتعيد bool. وتأخذ Func<int> لا شيء وتعيد int. أما وسائط نوع Action فكلها معاملات.

العائلة

النوعالمعاملاتما تعيده
Actionلا شيءvoid
Action<T>Tvoid
Action<T1, T2> ... حتى 16T1, T2, ...void
Func<TResult>لا شيءTResult
Func<T, TResult>TTResult
Func<T1, T2, TResult> ... حتى 16T1, T2, ...TResult
Predicate<T>Tbool
Comparison<T>T, Tint (سالب، صفر، موجب)
Converter<TIn, TOut>TInTOut

Predicate وComparison وConverter أقدم وتظهر في دوال List<T> وArray: FindAll(Predicate<T>) وSort(Comparison<T>) وConvertAll(Converter<T, TOut>). يستخدم LINQ النوع Func في كل مكان، ولهذا تأخذ list.Where(...) وlist.FindAll(...) تعابير lambda بالشكل نفسه لكن بنوعين مختلفين:

المخرجات:

17, 15
34, 52, 28
52, 34, 28, 17, 15
52y 34y 28y 17y 15y

تمرير الدوال إلى الدوال

يتيح معامل Func أو Action للمستدعي أن يقدّم الجزء المتغيّر من خوارزمية. شكلان شائعان: "نفّذ هذا وقِسه" و"جرّب هذا حتى ينجح":

المخرجات:

attempt 1 failed
attempt 2 failed
<html>ok</html>
clicks: 2

Retry عامة، فيمكن لـ Func<int, T> فيها أن تعيد أي شيء؛ واستنتج المترجم T على أنه string من تعبير lambda. ويغيّر تعبير lambda الممرّر إلى Twice المتغيّر المحلي clicks، وهذا يعمل لأن تعبير lambda يلتقط المتغيّر نفسه لا نسخة من قيمته.

مجموعات الدوال: تمرير دالة باسمها

لا تحتاج إلى تعبير lambda لملء Func أو Action. اسم دالة موجودة بتوقيع مطابق ("مجموعة دوال" أو method group) يتحوّل مباشرة:

المخرجات:

parsed: 43
sat is weekend: True
total quantity: 20

Select(int.Parse) مثل Select(s => int.Parse(s))، مع طبقة استدعاء أقل. يختار المترجم الشكل الذي يناسب النوع المستهدف: لـ int.Parse عدة أشكال، ولا يطابق Func<string, int> إلا Parse(string). وحين لا يستطيع المترجم الاستقرار على واحد (عدة أشكال تناسب بالقدر نفسه، أو لا يمكن استنتاج وسائط نوع دالة عامة من مجموعة دوال) لا يُترجم الاستدعاء، ويصلحه تعبير lambda يجعل الاستدعاء صريحًا.

مجموعة الدوال المستخدمة كـ Func ما زالت تُنفَّذ لاحقًا، كلما استُدعي المفوّض، تمامًا مثل تعبير lambda. الفرق فقط في طريقة الكتابة.

إعادة الدوال

يمكن للدالة أن تبني دالة وتعيدها. وتحمل Func المعادة القيم التي بُنيت منها:

المخرجات:

140
180
162

تبني Twice(staff) دالة جديدة تطبّق خصم الموظفين مرتين: 200، ثم 180، ثم 162.

قاموس من الدوال

تخزين الدوال في قاموس يحوّل switch طويلة إلى جدول بحث. وهو الشكل المعتاد لمعالجات الأوامر والآلات الحاسبة وأفعال القوائم، ويمكن إضافة مدخلات جديدة وقت التشغيل:

المخرجات:

12 + 30 = 42
7 * 6 = 42
9 / 0 = cannot divide by zero
2 ^ 3 = unknown operator
Hello, Sam
DONE

إضافة معامل جديد مدخل واحد إضافي في القاموس، دون تغيير في الحلقة. ويجعل StringComparer.OrdinalIgnoreCase الممرّر إلى القاموس الثاني أسماء الأوامر غير حساسة لحالة الأحرف.

الدوال غير المتزامنة: Func<Task> لا Action

مع الشيفرة async يحدد نوع المفوّض هل يستطيع المستدعي انتظار العمل:

Func<Task> save = async () => await File.WriteAllTextAsync("a.txt", "data");
await save();                       // the caller can await it and see its exceptions

Func<int, Task<string>> load = async id => await FetchUserAsync(id);
string user = await load(42);

Action bad = async () => await File.WriteAllTextAsync("a.txt", "data");
bad();                              // async void: fire and forget, exceptions escape

تعبير lambda غير المتزامن المُسند إلى Action يصبح دالة async void: لا شيء يستطيع انتظارها، والاستثناء داخلها لا يصل أبدًا إلى المستدعي. يُعاد رميه على مجمّع الخيوط (أو خيط الواجهة في تطبيق سطح المكتب)، حيث يُسقط العملية عادة. حين تكتب دالة تقبل عملًا غير متزامن فخذ Func<Task> (أو Func<Task<T>>).

متى تصرّح بمفوّضك الخاص

تغطي Func وAction معظم الحاجات. صرّح بنوع مفوّض مسمّى حين:

  • يحتوي التوقيع على معاملات ref أو out أو in. لا تستطيع Func التعبير عنها، فتحتاج دالة بأسلوب TryParse إلى delegate bool TryParser<T>(string text, out T value);.
  • يوثّق الاسم دورًا في واجهة API عامة. تقول delegate bool Validator(string input) في توقيع دالة أكثر مما تقوله Func<string, bool>، وتظهر أسماء معاملاتها في IntelliSense.
  • تحتاج إلى معامل params، الذي لا تستطيعه المفوّضات العامة أيضًا.

الأحداث هي المكان الآخر الذي ما زالت تظهر فيه أنواع المفوّضات المخصصة، مع أن EventHandler<TEventArgs> المدمج يغطي معظمها. تغطي صفحة المفوّضات التصريح بأنواع المفوّضات ودمجها.

الأسئلة الشائعة

ما الفرق بين Func وAction في C#؟

تعيد Func قيمة ولا تعيد Action. في Func<int, string, bool> يكون وسيط النوع الأخير (bool) نوع الإرجاع والبقية معاملات. وتأخذ Action<int, string> قيمة int وقيمة string وتعيد void. كلتاهما تأتي بنسخ من 0 إلى 16 معاملًا.

كيف أمرّر دالة كمعامل في C#؟

صرّح بالمعامل كـ Func أو Action بالشكل المناسب واستدعه داخل الدالة: static decimal Apply(decimal price, Func<decimal, decimal> rule) => rule(price);. يمرّر المستدعون تعبير lambda (Apply(80m, p => p * 0.9m)) أو اسم دالة (Apply(80m, HalfPrice)).

ما هو Predicate<T> في C#؟

نوع مفوّض يأخذ T ويعيد bool، بشكل Func<T, bool> نفسه. أقدم من Func وتستخدمه List<T>.Find وFindAll وRemoveAll وExists وArray.Find. لا يتحوّل أحد النوعين إلى الآخر، لكن تعبير lambda يتحوّل إلى أي منهما.

كيف أستخدم Func مع الشيفرة غير المتزامنة؟

استخدم Func<Task> لدالة غير متزامنة بلا نتيجة وFunc<T, Task<TResult>> لدالة بنتيجة، كي يستطيع المستدعي انتظارها بـ await. تجنّب إسناد تعبير lambda غير متزامن إلى Action: هذا يجعله async void، الذي لا يمكن انتظاره ولا يستطيع المستدعي التقاط استثناءاته.

هل يمكن أن تملك Func معاملات out أو ref؟

لا. معاملات الأنواع العامة في Func وAction معاملات قيم عادية، فلا سبيل لكتابة Func<string, out int, bool>. لتوقيع مثل TryParse صرّح بمفوّض مخصص: delegate bool TryParser<T>(string text, out T value);.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن