تصميم امتدادات جيدة
طبّقوا أفضل الممارسات وتجنبوا الأخطاء الشائعة
تصميم امتدادات جيدة درس مجاني في C# Academy على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في C# Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة C# Academy 4 دروس في المجموع.
تصميم امتدادات يقدّرها المطوّرون
تتمتع أساليب الامتداد بقوة كبيرة، لكن القوة قد تؤدي إلى إساءة الاستخدام. تبدو الامتدادات الجيدة جزءًا طبيعيًا من النوع، ويسهل اكتشافها، ولا تفاجئ المستدعي أبدًا. يغطي هذا الدرس إرشادات عملية.
مدّدوا السلوك لا الهوية
فضّلوا الامتدادات للسلوكيات المساعدة التي تُقرأ بوضوح داخل مسار معالجة. وإذا كان الأسلوب ينتمي فعلًا إلى نوع تملكونه، فاجعلوه عضوًا حقيقيًا فيه بدلًا من ذلك.
using System;
public static class TimeExtensions
{
// A pleasant, focused helper
public static bool IsWeekend(this DayOfWeek day)
=> day == DayOfWeek.Saturday || day == DayOfWeek.Sunday;
}
public class Program
{
public static void Main()
{
Console.WriteLine(DayOfWeek.Sunday.IsWeekend());
Console.WriteLine(DayOfWeek.Monday.IsWeekend());
}
}حافظوا على نقاء الأساليب متى أمكن
يسهل فهم الامتدادات التي تُرجع قيمة جديدة وتتجنب الآثار الجانبية وربطها بامتدادات أخرى. استهدفوا نمط: إدخال ثم قيمة ناتجة.
using System;
public static class StringExtensions
{
// Pure: depends only on input, returns a new string
public static string Truncate(this string s, int max)
=> s.Length <= max ? s : s.Substring(0, max) + "...";
}
public class Program
{
public static void Main()
{
Console.WriteLine("Extension methods are great".Truncate(10));
}
}تحققوا من المدخلات الفارغة
بما أن المستدعين يستطيعون استدعاء امتداد على كائن مستقبل قيمته null، فقرروا بعناية ما ينبغي أن يحدث. إما أن تتعاملوا مع القيمة الفارغة بسلاسة، أو تطرحوا ArgumentNullException واضحًا.
using System;
public static class StringExtensions
{
public static string SafeUpper(this string s)
{
if (s is null) throw new ArgumentNullException(nameof(s));
return s.ToUpper();
}
}
public class Program
{
public static void Main()
{
try { ((string)null).SafeUpper(); }
catch (ArgumentNullException ex) { Console.WriteLine("Caught: " + ex.ParamName); }
Console.WriteLine("ok".SafeUpper());
}
}سمّوا الامتدادات لتسهيل اكتشافها
يعثر المطوّرون على الامتدادات بكتابة النقطة واستعراض IntelliSense. وتجعل الأسماء الواضحة القائمة على الأفعال، والتي تصف النتيجة، أدواتكم المساعدة سهلة الاكتشاف وموضحةً لوظيفتها ذاتيًا.
using System;
using System.Collections.Generic;
public static class CollectionExtensions
{
public static bool HasItems<T>(this ICollection<T> c)
=> c != null && c.Count > 0;
}
public class Program
{
public static void Main()
{
var list = new List<int> { 1 };
Console.WriteLine(list.HasItems());
}
}اجمعوا الامتدادات المرتبطة بتناسق
ضعوا الامتدادات الخاصة بمفهوم واحد في صنف ثابت واحد ذي اسم واضح، وداخل مساحة أسماء واحدة. يحافظ ذلك على معنى عمليات الاستيراد، ويتيح للمستخدمين جلب الأدوات المساعدة التي يريدونها تحديدًا.
using System;
using System.Collections.Generic;
public static class EnumerableExtensions
{
public static IEnumerable<T> TakeFirst<T>(this IEnumerable<T> source, int n)
{
int taken = 0;
foreach (var item in source)
{
if (taken++ >= n) yield break;
yield return item;
}
}
}
public class Program
{
public static void Main()
{
var nums = new List<int> { 1, 2, 3, 4 };
Console.WriteLine(string.Join(",", nums.TakeFirst(2)));
}
}لا تُخفوا السلوك المفاجئ
ينتهك الامتداد الذي يبدو بسيطًا لكنه ينفذ عملًا مكلفًا أو يعتمد على الحالة توقعات المستخدم. اجعلوا التكلفة واضحة؛ وإذا كان يستعلم من قاعدة بيانات أو يغيّر الحالة، فسمّوه بما يعكس ذلك.
using System;
public static class MathExtensions
{
// Cheap and obvious: no hidden cost
public static int ClampMin(this int value, int min)
=> value < min ? min : value;
}
public class Program
{
public static void Main()
{
Console.WriteLine((-3).ClampMin(0));
Console.WriteLine(5.ClampMin(0));
}
}تجنبوا تمديد object
يؤدي تمديد object إلى تلويث كل نوع في البرنامج بأسلوبكم، مما يزدحم معه IntelliSense في كل مكان. استهدفوا بدلًا من ذلك النوع أو الواجهة الأكثر تخصيصًا التي تكون مناسبة.
using System;
public static class IntExtensions
{
// Specific to int, not object - far less noisy
public static string ToOrdinal(this int n)
{
if (n % 10 == 1 && n % 100 != 11) return n + "st";
if (n % 10 == 2 && n % 100 != 12) return n + "nd";
if (n % 10 == 3 && n % 100 != 13) return n + "rd";
return n + "th";
}
}
public class Program
{
public static void Main()
{
Console.WriteLine(1.ToOrdinal());
Console.WriteLine(22.ToOrdinal());
}
}فضّلوا إرجاع IEnumerable للتسلسلات
بالنسبة إلى عوامل تشغيل التسلسلات، أرجعوا IEnumerable<T> واستخدموا التنفيذ المؤجل. فهذا يتيح تركيب العمليات جيدًا، ويتجنب إجبار المستدعين على إنشاء البيانات التي قد لا يحتاجون إليها.
using System;
using System.Collections.Generic;
public static class SeqExtensions
{
public static IEnumerable<int> Doubled(this IEnumerable<int> source)
{
foreach (var n in source) yield return n * 2;
}
}
public class Program
{
public static void Main()
{
var nums = new List<int> { 1, 2, 3 };
foreach (var n in nums.Doubled()) Console.WriteLine(n);
}
}قائمة تحقق للامتدادات الجيدة
قبل نشر أي امتداد، اسألوا: هل الاسم واضح؟ هل الامتداد نقي أم أن آثاره الجانبية واضحة؟ هل جرى التعامل مع القيمة الفارغة؟ هل النوع المستهدف محدد؟ هل جرى تجميعه بطريقة منطقية؟ إذا كانت الإجابة نعم عن كل ذلك، فلديكم أداة مساعدة سيستمتع الآخرون باستخدامها.
using System;
public static class StringExtensions
{
public static string OrDefault(this string s, string fallback)
=> string.IsNullOrEmpty(s) ? fallback : s;
}
public class Program
{
public static void Main()
{
Console.WriteLine("".OrDefault("N/A"));
Console.WriteLine("value".OrDefault("N/A"));
}
}جرّبوا بأنفسكم
طبّقوا قائمة التحقق: نوع مستهدف محدد، واسم واضح، والتعامل مع القيمة الفارغة، ونتيجة نقية. يحقق هذا الامتداد الصغير كل الإرشادات.
using System;
using System.Collections.Generic;
public static class DictionaryExtensions
{
// Clear name, specific type, null-safe, pure
public static TValue GetOrDefault<TKey, TValue>(
this IReadOnlyDictionary<TKey, TValue> dict, TKey key, TValue fallback)
{
if (dict == null) return fallback;
return dict.TryGetValue(key, out var value) ? value : fallback;
}
}
public class Program
{
public static void Main()
{
var ages = new Dictionary<string, int> { ["Ann"] = 30 };
Console.WriteLine(ages.GetOrDefault("Ann", -1));
Console.WriteLine(ages.GetOrDefault("Bob", -1));
}
}تحقق سريع
طبّقوا إرشادات التصميم.
مراجعة
تبدو الامتدادات المصممة جيدًا طبيعية ومتوقعة.
- فضّلوا الأدوات المساعدة النقية التي تُرجع قيمًا ويمكن ربطها بسهولة.
- قرروا سلوك القيمة الفارغة ووثّقوه.
- استخدموا أسماء واضحة قائمة على الأفعال لتسهيل الاكتشاف.
- استهدفوا أنواعًا أو واجهات محددة، لا
object. - اجمعوا الأدوات المساعدة المرتبطة، وأرجعوا
IEnumerable<T>للتسلسلات.
تعلم C# مع معلم ذكاء اصطناعي — مجانًا
اكتب وقم بتشغيل أكوادك الفعلية في المتصفح، واحصل على مساعدة فورية من معلم ذكاء اصطناعي متاح 24/7، واستمر من حيث توقفت على الويب أو في التطبيق.
- الدورات
- 93
- الدروس
- 346
الأسئلة الشائعة
هل درس «تصميم امتدادات جيدة» مجاني؟
نعم — نص درس «تصميم امتدادات جيدة» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة C# Academy، انتقل إلى CoddyKit PRO. تتضمن دورة C# Academy 4 دروس في المجموع.
ماذا ستتعلم في «تصميم امتدادات جيدة»؟
طبّقوا أفضل الممارسات وتجنبوا الأخطاء الشائعة تتمرن على C# Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ C# Academy؟
لا تُشترط خبرة سابقة. C# Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.
كم من الوقت يستغرق درس «تصميم امتدادات جيدة»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس C# Academy هذا؟
نعم. كل درس في C# Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- تعريف Extension Methods
- توسيع الواجهات والأنواع العامة
- حلّ Extension Methods
- تصميم امتدادات جيدة