C#: المجموعات والأنواع العامة
1. المفاهيم العامة والدوافع
تتيح لنا العناصر العامة كتابة كود معلم بالأنواع، مما يحقق أمان الأنواع وإعادة استخدام الكود. قبل ظهور العناصر العامة، كان ArrayList يخزن أنواع object؛ حيث كانت أنواع القيم تُعبَّأ عند إضافتها وتُفكَّ عند استرجاعها، مما أضر بالأداء وأدى إلى عدم وجود فحص للأنواع في وقت التحويل البرمجي.
▶ مثال
using System;
using System.Collections;
ArrayList list = new ArrayList();
list.Add(42);
list.Add("hello");
int value = (int)list[0];
foreach (object item in list)
{
Console.WriteLine(item);
}
42
hello
تحدد النسخة العامة أنواع العناصر في وقت التحويل البرمجي، ولا تتطلب أي عملية تغليف أو فك تغليف، ويتم اكتشاف الأخطاء أثناء التحويل البرمجي:
using System;
using System.Collections.Generic;
List````<int>```` numbers = new List````<int>````();
numbers.Add(42);
foreach (int n in numbers)
{
Console.WriteLine(n);
}
42
2. قائمة <T> قائمة ديناميكية
List<T> هي المجموعة العامة الأكثر استخدامًا، حيث تدعم قائمة مرتبة قابلة لتغيير حجمها ديناميكيًّا، مع مجموعة غنية من الأساليب لإضافة العناصر وحذفها والبحث عنها وتعديلها.
▶ مثال
using System;
using System.Collections.Generic;
List````<string>```` fruits = new List````<string>```` { "apple", "banana", "cherry" };
fruits.Add("date");
fruits.AddRange(new string[] { "elderberry", "fig" });
fruits.Insert(1, "blueberry");
bool hasApple = fruits.Contains("apple");
int idx = fruits.IndexOf("cherry");
string found = fruits.Find(f => f.StartsWith("b"));
List````<string>```` allB = fruits.FindAll(f => f.StartsWith("b"));
fruits.Sort();
fruits.ForEach(f => Console.WriteLine(f));
Console.WriteLine("---");
Console.WriteLine($"Count: {fruits.Count}");
Console.WriteLine($"Has apple: {hasApple}");
Console.WriteLine($"Cherry index: {idx}");
Console.WriteLine($"First b-word: {found}");
Console.WriteLine($"All b-words: {string.Join(", ", allB)}");
apple
blueberry
banana
cherry
date
elderberry
fig
---
Count: 7
Has apple: True
Cherry index: 3
First b-word: blueberry
All b-words: blueberry, banana
(1) إزالة العناصر
using System;
using System.Collections.Generic;
List````<int>```` nums = new List````<int>```` { 10, 20, 30, 40, 50 };
nums.Remove(30);
nums.RemoveAt(0);
Console.WriteLine(string.Join(", ", nums));
Console.WriteLine($"Count: {nums.Count}");
20, 40, 50
Count: 3
3. القاموس`<TKey, TValue>` القاموس
يخزن Dictionary<TKey, TValue> أزواج القيم والمفاتيح، مما يتيح البحث السريع عن القيم باستخدام المفتاح بزمن بحث يقارب O(1).
▶ مثال
using System;
using System.Collections.Generic;
Dictionary`<string, int>` scores = new Dictionary`<string, int>`
{
{ "Alice", 90 },
{ "Bob", 85 }
};
scores.Add("Charlie", 78);
scores["Bob"] = 92;
bool exists = scores.ContainsKey("Alice");
if (scores.TryGetValue("Dave", out int daveScore))
{
Console.WriteLine($"Dave: {daveScore}");
}
else
{
Console.WriteLine("Dave not found");
}
scores.Remove("Charlie");
Console.WriteLine($"Count: {scores.Count}");
Console.WriteLine("---Keys---");
foreach (string key in scores.Keys)
{
Console.WriteLine(key);
}
Console.WriteLine("---Values---");
foreach (int val in scores.Values)
{
Console.WriteLine(val);
}
Console.WriteLine("---Pairs---");
foreach (KeyValuePair`<string, int>` kv in scores)
{
Console.WriteLine($"{kv.Key}: {kv.Value}");
}
Dave not found
Count: 2
---Keys---
Alice
Bob
---Values---
90
92
---Pairs---
Alice: 90
Bob: 92
4. الطرق العامة والفئات العامة
لا تقتصر العناصر العامة على المجموعات فحسب؛ بل يمكن للطرق والفئات أيضًا استخدام معلمات الأنواع، مما يجعل الخوارزميات قابلة للتطبيق على أنواع متعددة.
▶ مثال
using System;
T Max```<T>```(T a, T b) where T : IComparable```<T>```
{
return a.CompareTo(b) >= 0 ? a : b;
}
Console.WriteLine(Max(3, 7));
Console.WriteLine(Max("apple", "banana"));
7
banana
مثال على الفئة العامة:
using System;
class Box```<T>```
{
private T _value;
public Box(T value)
{
_value = value;
}
public T Value => _value;
public override string ToString() => $"Box<{typeof(T).Name}>: {_value}";
}
Box````<int>```` intBox = new Box````<int>````(42);
Box````<string>```` strBox = new Box````<string>````("hello");
Console.WriteLine(intBox);
Console.WriteLine(strBox);
Box<Int32>: 42
Box<String>: hello
5. القيود العامة
استخدم الكلمة الرئيسية where لفرض قيود على معلمات الأنواع، مما يحد من نطاق الأنواع التي يمكن استخدامها.
▶ مثال
using System;
class Repository```<T>``` where T : class, new()
{
public T Create()
{
T instance = new T();
Console.WriteLine($"Created: {typeof(T).Name}");
return instance;
}
}
class Player
{
public string Name { get; set; } = "Unknown";
}
Repository<Player> repo = new Repository<Player>();
Player p = repo.Create();
Console.WriteLine($"Name: {p.Name}");
Created: Player
Name: Unknown
مرجع للقيود الشائعة:
| القيد | الوصف |
|---|---|
where T : class |
يجب أن يكون T من النوع المرجعي |
where T : struct |
يجب أن يكون T نوعًا قيمياً |
where T : new() |
يجب أن تحتوي T على مُنشئ عام بدون معلمات |
where T : IFoo |
يجب أن تنفذ T واجهة IFoo |
where T : Base |
يجب أن ترث T من الفئة الأساسية |
6. قائمة الانتظار <T> قائمة الانتظار
الطابور هو بنية بيانات تعمل وفقًا لمبدأ «الدخول أولاً، الخروج أولاً» (FIFO). استخدم Enqueue للإضافة وDequeue للحذف.
▶ مثال
using System;
using System.Collections.Generic;
Queue````<string>```` queue = new Queue````<string>````();
queue.Enqueue("first");
queue.Enqueue("second");
queue.Enqueue("third");
string front = queue.Peek();
Console.WriteLine($"Peek: {front}");
while (queue.Count > 0)
{
Console.WriteLine($"Dequeue: {queue.Dequeue()}");
}
Peek: first
Dequeue: first
Dequeue: second
Dequeue: third
7. المكدس <T> المكدس
المكدس هو بنية بيانات تعمل وفقًا لمبدأ «آخر ما يدخل أول ما يخرج» (LIFO). استخدم Push للإضافة وPop للحذف.
▶ مثال
using System;
using System.Collections.Generic;
Stack````<int>```` stack = new Stack````<int>````();
stack.Push(10);
stack.Push(20);
stack.Push(30);
int top = stack.Peek();
Console.WriteLine($"Peek: {top}");
while (stack.Count > 0)
{
Console.WriteLine($"Pop: {stack.Pop()}");
}
Peek: 30
Pop: 30
Pop: 20
Pop: 10
8. مُهيئات المجموعات
تتيح لك مُهيئات المجموعات ملء العناصر مباشرةً عند إنشاء المجموعة، وذلك باستخدام صيغة موجزة وسهلة الفهم.
▶ مثال
using System;
using System.Collections.Generic;
List````<int>```` nums = new List````<int>```` { 1, 2, 3, 4, 5 };
Dictionary`<string, double>` prices = new Dictionary`<string, double>`
{
{ "coffee", 3.5 },
{ "tea", 2.8 },
{ "juice", 4.0 }
};
Console.WriteLine(string.Join(", ", nums));
foreach (var kv in prices)
{
Console.WriteLine($"{kv.Key}: ${kv.Value}");
}
1, 2, 3, 4, 5
coffee: $3.5
tea: $2.8
juice: $4
9. مقدمة إلى IEnumerable والمكررات
IEnumerable<T> هي الواجهة الأساسية لجميع المجموعات القابلة للتجول، وتعتمد حلقة foreach عليها. باستخدام yield return، يمكنك بسهولة كتابة متكررات مخصصة (تجد التفاصيل في الدرس 35).
▶ مثال
using System;
using System.Collections.Generic;
IEnumerable````<int>```` GetRange(int start, int end)
{
for (int i = start; i <= end; i++)
{
yield return i;
}
}
foreach (int n in GetRange(1, 5))
{
Console.WriteLine(n);
}
1
2
3
4
5
yield return يحول الدالة إلى مُكرر: حيث يُرجع كل استدعاء قيمة واحدة ثم يتوقف مؤقتًا، وتستأنف الدورة التالية من نقطة التوقف. للاطلاع على شرح كامل، انظر الدرس 35.
❓ أسئلة شائعة
<T> والمصفوفات؟<T> إضافة العناصر وحذفها ديناميكيًا، بينما تتميز المصفوفات بطول ثابت. يتم دعم List داخليًا بواسطة مصفوفة؛ وعند الحاجة إلى تغيير الحجم، تقوم تلقائيًا بإنشاء مصفوفة أكبر ونسخ العناصر إليها.dict[key] = value فيؤدي إلى استبدال القيمة الموجودة.where T : class وwhere T : struct معًا؟where T : new() ضروريًا؟new T() لإنشاء مثيل داخل دالة. فبدون هذا القيد، لا يسمح المُترجم باستخدام عامل new على T.📖 ملخص
- توفر العناصر العامة أمان الأنواع وإمكانية إعادة استخدام الكود، مما يقلل من العبء الناتج عن عمليات التعبئة والتفريغ
List<T>هو مصفوفة ديناميكية تدعم مجموعة واسعة من العمليات مثل الإضافة والحذف والبحث والتعديل والفرز والعثورDictionary<TKey, TValue>يخزن أزواج المفتاح-القيمة، ويوفر أداءً للبحث من الدرجة O(1)- تتيح الطرق والفئات العامة تطبيق الخوارزميات وهياكل البيانات على أنواع متعددة
- تحدد قيود
whereنطاق معلمات الأنواع: الفئات، والبنى، وnew()، والواجهات، والفئات الأساسية Queue<T>تعني «أول ما يدخل أول ما يخرج»، وStack<T>تعني «آخر ما يدخل أول ما يخرج»- تعمل مُهيئات المجموعات على تبسيط عملية إنشاء المجموعات وتعبئتها
IEnumerable<T>هو الأساس لعملية التصفح، ويمكن لـyield returnإنشاء مُكرِّرات
📝 تمارين
- قم بإنشاء
List<double>، وأضف 5 درجات، واستخدمFindAllلتصفية الدرجات التي تزيد عن 80 وعرضها - إنشاء
Dictionary<string, string>لتخزين 3 توزيعات بين العواصم والمقاطعات، وتنفيذ عملية البحث عن مقاطعة باستخدام اسم المدينة (باستخدامTryGetValue) - اكتب دالة عامة `T Clamp
<T>(T value, T min, T max) where T : IComparable<T>تحدد قيمة ضمن النطاق [min, max] - استخدم
Queue<string>لمحاكاة قائمة انتظار الطباعة: أضف 3 أسماء مستندات إلى قائمة الانتظار، ثم أخرجها من القائمة واطبعها بالترتيب - اكتب دالة تكرارية
IEnumerable<int>باستخدامyield returnلتوليد أول 10 حدود لمتتابعة فيبوناتشي