الـ Generics (الأصناف والدوال)
جزء من قسم Object Oriented Programming في رحلة C# على Coddy — الدرس 42 من 70.
تسمح لك الأنواع العامة (Generics) بكتابة فئات (classes) وطرق (methods) تعمل مع أي نوع بيانات مع الحفاظ على سلامة النوع (type safety). فبدلاً من كتابة فئات منفصلة لكل من IntList و StringList و DoubleList، يمكنك كتابة فئة عامة واحدة تتعامل معها جميعاً.
يستخدم الصنف العام (generic class) معامل نوع، عادةً ما يسمى T، محاطاً بأقواس زاوية:
public class Box<T>
{
private T content;
public void Store(T item) => content = item;
public T Retrieve() => content;
}
// الاستخدام مع أنواع مختلفة
Box<int> intBox = new Box<int>();
intBox.Store(42);
Box<string> stringBox = new Box<string>();
stringBox.Store("Hello");عندما تقوم بإنشاء Box<int>، يقوم المترجم باستبدال كل T بـ int. يوفر هذا فحصاً للأنواع في وقت الترجمة - لا يمكنك تخزين سلسلة نصية عن طريق الخطأ في صندوق int.
تعمل الدوال العامة (Generic methods) بشكل مشابه، مما يسمح لدالة واحدة بالعمل على أنواع مختلفة:
public static void Swap<T>(ref T a, ref T b)
{
T temp = a;
a = b;
b = temp;
}
int x = 1, y = 2;
Swap(ref x, ref y); // x=2, y=1يستنتج المترجم النوع من المعاملات، لذا لا تحتاج إلى كتابة Swap<int> بشكل صريح. تعد الـ Generics هي الأساس لمجموعات مثل List<T> و Dictionary<TKey, TValue>، مما يتيح كوداً قابلاً لإعادة الاستخدام وآمناً من حيث النوع.
التحدي
سهللنقم ببناء نظام Storage<T> عام يمكنه الاحتفاظ بالعناصر من أي نوع وإدارتها. ستقوم بإنشاء فئة حاوية قابلة لإعادة الاستخدام توضح قوة الأنواع العامة (generics) - اكتبها مرة واحدة، واستخدمها مع الأعداد الصحيحة، أو النصوص، أو أي نوع آخر!
ستقوم بتنظيم الكود الخاص بك عبر ملفين:
Storage.cs: أنشئ فئةStorage<T>عامة داخل مساحة الأسماءDataStorage. يجب أن تقوم حاوية التخزين الخاصة بك بما يلي:- امتلاك مصفوفة خاصة من النوع
Tللاحتفاظ بالعناصر وخاصيةCountلتتبع عدد العناصر المخزنة. - قبول سعة (capacity) في منشئ الفئة (constructor) لتهيئة المصفوفة الداخلية.
- امتلاك طريقة
Add(T item)تضيف عنصراً إذا كانت هناك مساحة متوفرة. - امتلاك طريقة
Get(int index)تعيد العنصر الموجود في الفهرس المحدد. - امتلاك طريقة
GetAll()تعيد جميع العناصر المخزنة مدمجة بفاصلة", ".
- امتلاك مصفوفة خاصة من النوع
Program.cs: في ملفك الرئيسي، قم بتوضيح عمل فئتك العامة مع نوعين مختلفين. أنشئStorage<int>وStorage<string>، وأضف عناصر إلى كل منهما، وأظهر أن نفس الفئة تعمل بسلاسة مع كلا النوعين.
ستتلقى أربعة مدخلات:
- العدد الصحيح الأول لتخزينه
- العدد الصحيح الثاني لتخزينه
- النص الأول لتخزينه
- النص الثاني لتخزينه
أنشئ مخزناً للأعداد الصحيحة بسعة 5 ومخزناً للنصوص بسعة 5. أضف العددين الصحيحين إلى مخزن الأعداد الصحيحة والنصين إلى مخزن النصوص.
اطبع المخرجات بهذا التنسيق:
Int storage: {GetAll()}
Int count: {Count}
First int: {Get(0)}
String storage: {GetAll()}
String count: {Count}
First string: {Get(0)}على سبيل المثال، إذا كانت المدخلات هي 42، و 100، و Hello، و World، يجب أن تكون المخرجات:
Int storage: 42, 100
Int count: 2
First int: 42
String storage: Hello, World
String count: 2
First string: Helloلاحظ كيف كتبت فئة Storage<T> مرة واحدة، ولكنها تعمل بشكل مثالي مع كل من int و string - هذا هو سحر الأنواع العامة (generics)! يضمن المترجم (compiler) سلامة النوع، لذا لا يمكنك إضافة نص بالخطأ إلى مخزن الأعداد الصحيحة الخاص بك.
ورقة مرجعية
تسمح لك الأنواع العامة (Generics) بكتابة فئات (classes) وطرق (methods) تعمل مع أي نوع بيانات مع الحفاظ على سلامة النوع (type safety).
تستخدم الفئة العامة معامل نوع (عادةً ما يكون T) محاطاً بأقواس زاوية:
public class Box<T>
{
private T content;
public void Store(T item) => content = item;
public T Retrieve() => content;
}عند إنشاء مثيل (instance)، حدد النوع الذي سيحل محل T:
Box<int> intBox = new Box<int>();
intBox.Store(42);
Box<string> stringBox = new Box<string>();
stringBox.Store("Hello");تسمح الطرق العامة (Generic methods) لطريقة واحدة بالعمل على أنواع مختلفة:
public static void Swap<T>(ref T a, ref T b)
{
T temp = a;
a = b;
b = temp;
}
int x = 1, y = 2;
Swap(ref x, ref y); // Compiler infers type from argumentsتوفر الأنواع العامة (Generics) فحصاً للنوع في وقت التجميع (compile-time) وهي الأساس لمجموعات مثل List<T> و Dictionary<TKey, TValue>.
جرّب بنفسك
using System;
using DataStorage;
class Program
{
public static void Main(string[] args)
{
// قراءة المدخلات
int num1 = Convert.ToInt32(Console.ReadLine());
int num2 = Convert.ToInt32(Console.ReadLine());
string str1 = Console.ReadLine();
string str2 = Console.ReadLine();
// TODO: بسعة 5 Storage<int> إنشاء
// TODO: إضافة العددين الصحيحين إلى مخزن الأعداد الصحيحة
// TODO: بسعة 5 Storage<string> إنشاء
// TODO: إضافة السلسلتين النصيتين إلى مخزن النصوص
// TODO: طباعة المخرجات بالتنسيق المطلوب
// Int storage: {GetAll()}
// Int count: {Count}
// First int: {Get(0)}
// String storage: {GetAll()}
// String count: {Count}
// First string: {Get(0)}
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات OOP
الملفات الخارجيةNamespaces والتوجيهاتمقدمة في Classes و Objectsالكلمة المفتاحية 'this'Methods والمعاملاتFields مقابل PropertiesConstructorsObject Initializersمراجعة - آلة حاسبة بسيطة4الوراثة
صيغة الوراثة الأساسية (:)الكلمة المفتاحية 'base'الكلمات المفتاحية Virtual و Overrideالأصناف المختومة (Sealed Classes)الفئة الأساسية 'object'مراجعة - هيكلية الموظفين7ميزات متقدمة
تحميل العوامل (Operator Overloading)المفهرسات (Indexers (this[]))إعادة تعريف ()ToStringدوال التوسعة (Extension Methods)ملخص - Custom List2الخصائص والأعضاء الساكنة (Static Members)
الخصائص المطبقة تلقائيًا (Auto-Implemented Properties)خصائص القراءة فقط والكتابة فقطالحقول والدوال الساكنة (Static)الفئات الساكنة (Static Classes)الأعضاء ذات البنية التعبيرية (Expression-Bodied Members)5تعدد الأشكال والواجهات (Polymorphism & Interfaces)
تعدد الأشكال: وقت التجميع مقابل وقت التشغيلالواجهة مقابل الفئة المجردة (Abstract Class)الواجهات المتعددةالواجهات الصريحةUpcasting و Downcastingملخص - حاسبة الأشكال8مفاهيم البرمجة كائنية التوجه (OOP) المتقدمة
التركيب بدلاً من الوراثةالـ Generics (الأصناف والدوال)الـ Delegates والأحداثالـ Attributes والـ Reflectionواجهة IDisposable وجملة usingأساسيات Dependency Injection