السلام عليكم ورحمة الله وبركاته
سيداتي سادتي لن أتطرق لموضوع الـ Generics بشكل عام هنا، فلقد قام أستاذي الأخ هاني الأتاسي (مؤجورا إن شاء الله) بشرح وافي وكامل عن الـ Generics . ولكن أود أن أشير إلى بعض النقاط التي قد تضعك في حالة هيستيريا ومن ثم تبداء بنتف شعر رأسك. ولكي توضح الفكرة دعونا نقوم بكتابة كود لبناء Linked List بإستخدام الـ Generics، سيكون الكود بالشكل التالي
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace generics
{
public class Node<K, T>
{
public K Key;
public T Item;
public Node<K, T> NextNode;
public Node()
{
Key = default(K);
Item = default(T);
NextNode = null;
}
public Node(K Key, T Itme, Node<K, T> NextNode)
{
this.Key = Key;
this.Item = Itme;
this.NextNode = NextNode;
}
public override string ToString()
{
return string.Format("Key: {0}, Item: {1}", Key, Item);
}
}
public class LinkedList<K, T>
{
Node<K, T> _head;
public LinkedList()
{
_head = new Node<K, T>();
}
public void Add(K Key, T Item)
{
Node<K, T> newNode = new Node<K, T>(Key, Item, _head.NextNode);
_head.NextNode = newNode;
}
public void Print()
{
Node<K, T> tempNode = _head;
while ((tempNode = tempNode.NextNode) != null)
Console.WriteLine(tempNode);
}
}
}إلى الآن الكود لا يحوي الشيء الجديد، ويمكننا تجريبه عن طريق الكود التالي
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace generics
{
class Program
{
static void Main(string[] args)
{
try
{
LinkedList<int,string> list = new LinkedList<int,string>();
for(int i = 1; i <= 10; i++)
list.Add(i, "Robatic " + i);
list.Print();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}الكود ولله الحمد شغال وما عليه غبار، طيب بدافع الفضول انت حبيت تضيف خاصية الـ Indexing والبحث لعنصر معين عن طريق الـ Key وكانت الفكرة بسيطة في بالك وقلت في نفسك إنه تطبيقها حيكون سهل جد وزي ما يقولوا Straight forward وقمت بتعديل الكلاس LinkedList<K,T> بالشكل التالي
public class LinkedList<K, T>
{
Node<K, T> _head;
public LinkedList()
{
_head = new Node<K, T>();
}
public void Add(K Key, T Item)
{
Node<K, T> newNode = new Node<K, T>(Key, Item, _head.NextNode);
_head.NextNode = newNode;
}
public void Print()
{
Node<K, T> tempNode = _head;
while ((tempNode = tempNode.NextNode) != null)
Console.WriteLine(tempNode);
}
public T this[K Key]
{
get { return Find(Key); }
}
T Find(K Key)
{
Node<K, T> tempNode = _head;
while ((tempNode = tempNode.NextNode) != null)
if (tempNode.Key == Key)
break;
return tempNode != null ? tempNode.Item : null;
}
}لو حاولت تعمل compile للكود الي فوق حيعترض عليك الـ compiler بالتحديد في السطرين
if (tempNode.Key == Key) /***** AND *****/ return tempNode != null ? tempNode.Item : null;
السبب في السطر الأول هو إن الـ compiler ما عنده علم إذا كان نوع الـ K يدعم الـ == operator، أما بالنسبة للسطر الثاني فالسبب يرجع لإحتمالية إنه نو الـ K الي راح يستخدم الـ User ممكن يكون Value Type مش reference Type زي مثلا int, char, double ... etc، عشان نتخلص من المشكلة الموجودة في سطر الثاني راح نستخدم العبارة default(Type) which was introduced in C# 2.0 والغرض منها ترجيع null إذا كان الـ Type عبارة عن reference type أو القيمة الإفتراضية للـ Type إذا كان Value type زي مثلا 0 للنوع int، أما بالنسبة للسطر الأول فقد يختر في بالك إستخدام الدالة CompareTo ولكن برضه الـ Compiler راح يعترض بسبب إنه مافي ضمانات تدل على أن الـ Type K راح يحوي Implementation للدالة CompareTo. ممكن يختر في بالك أن تتحايل على الـ Compiler عن طريق عمل Cast للـ tempNode.Key إلى IComparable لتفرض إستخدام الدالة CompareTo وراح يعمل Compile وراح يشتغل الكود، ولكن في إحتمال إنه يطلعلك Run Time Error وهذا ممكن يصير لو كان الـ K من نوع لا يحتوي على Implementation للـ IComparable، على سبيل المثال أفرض لو كان Struct، خلونا نشوف المثال التالي
// Classes
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace generics
{
public class Node<K, T>
{
public K Key;
public T Item;
public Node<K, T> NextNode;
public Node()
{
Key = default(K);
Item = default(T);
NextNode = null;
}
public Node(K Key, T Itme, Node<K, T> NextNode)
{
this.Key = Key;
this.Item = Itme;
this.NextNode = NextNode;
}
public override string ToString()
{
return string.Format("Key: {0}, Item: {1}", Key, Item);
}
}
public class LinkedList<K, T>
{
Node<K, T> _head;
public LinkedList()
{
_head = new Node<K, T>();
}
public void Add(K Key, T Item)
{
Node<K, T> newNode = new Node<K, T>(Key, Item, _head.NextNode);
_head.NextNode = newNode;
}
public void Print()
{
Node<K, T> tempNode = _head;
while ((tempNode = tempNode.NextNode) != null)
Console.WriteLine(tempNode);
}
public T this[K Key]
{
get { return Find(Key); }
}
T Find(K Key)
{
Node<K, T> tempNode = _head;
while ((tempNode = tempNode.NextNode) != null)
if (((IComparable)tempNode.Key).CompareTo(Key) == 0)
break;
return tempNode != null ? tempNode.Item : default(T);
}
}
// Let us see what happens when we use a struct
struct MyStruct
{
}
}
// Main Method
static void Main(string[] args)
{
try
{
LinkedList<int,string> list = new LinkedList<int,string>();
for(int i = 1; i <= 10; i++)
list.Add(i, "Robatic " + i);
Console.WriteLine(list[5]);
// Using a struct
MyStruct[] structs = new MyStruct[10];
LinkedList<MyStruct, string> list2 = new LinkedList<MyStruct, string>();
for (int i = 0; i < structs.Length; i++)
{
MyStruct key = new MyStruct();
structs = key;
list2.Add(key, "Robatic " + (i + 1));
}
Console.WriteLine(list2[structs[5]]); // Boooooooooooom
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}المشكلة واضحة جدا وهذا ما يسمونه بالـ Type safe، صحيح إنه الكود يقوم بالتحويل Casting ولكن من غير ما يشيك على النوع، فالحل الأمثل هو نضيف Generic Constraint بحيث نحدد إنه النوع K لازم يكون عامل Implementation للـ IComperable بحيث لو إنه المستخدم حاول يضيف نوع ما فيه تطبيق للـ IComperable زي الحالة الي فوق راح يعترض الـ Compiler وإنت كذا فرضت مبداء الـ Type Safety، حيكون التعديل بالشكل التالي
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace generics
{
public class Node<K, T>
{
public K Key;
public T Item;
public Node<K, T> NextNode;
public Node()
{
Key = default(K);
Item = default(T);
NextNode = null;
}
public Node(K Key, T Itme, Node<K, T> NextNode)
{
this.Key = Key;
this.Item = Itme;
this.NextNode = NextNode;
}
public override string ToString()
{
return string.Format("Key: {0}, Item: {1}", Key, Item);
}
}
public class LinkedList<K, T>
where K : IComparable
{
Node<K, T> _head;
public LinkedList()
{
_head = new Node<K, T>();
}
public void Add(K Key, T Item)
{
Node<K, T> newNode = new Node<K, T>(Key, Item, _head.NextNode);
_head.NextNode = newNode;
}
public void Print()
{
Node<K, T> tempNode = _head;
while ((tempNode = tempNode.NextNode) != null)
Console.WriteLine(tempNode);
}
public T this[K Key]
{
get { return Find(Key); }
}
T Find(K Key)
{
Node<K, T> tempNode = _head;
while ((tempNode = tempNode.NextNode) != null)
if (tempNode.Key.CompareTo(Key) == 0)
break;
return tempNode != null ? tempNode.Item : default(T);
}
}
// Let us see what happens when we use a struct
struct MyStruct
{
}
}
// Main Class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace generics
{
class Program
{
static void Main(string[] args)
{
try
{
LinkedList<int,string> list = new LinkedList<int,string>();
for(int i = 1; i <= 10; i++)
list.Add(i, "Robatic " + i);
Console.WriteLine(list[5]);
// Using a struct
MyStruct[] structs = new MyStruct[10];
LinkedList<MyStruct, string> list2 = new LinkedList<MyStruct, string>();
for (int i = 0; i < structs.Length; i++)
{
MyStruct key = new MyStruct();
structs = key;
list2.Add(key, "Robatic " + (i + 1));
}
Console.WriteLine(list2[structs[5]]); // Boooooooooooom
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}لاحظ لما تحاول تمل Compile راح يعترض الـ Compiler. بس هنا في مشكلة تقنية وهي إنه الدالة IComperable.ConpareTo راح تقوم بعملية الـ Boxing/Unboxing وهذا يطون على حساب الـ Performance، لإنه في كل مرة راح يسوي مقارنة راح يحول من نوع object إلى K فتخيل لو كان عندك مليون عنصر في الـ LinkedList وكمان تخيل لو كان نوع الـ K هو FileStream أو حاجة ثانية كبيرة صورة مثلا أو ملف صوتي ؟ الحل إنك تستخدم الـ
IComparable<T>
حيث إنه في الـ Intermediate Language (IL) راح يتم تحديد النوع بدل ما يكون object وبعدين يصيرله كاست للنوع K، راح يصير الكود
public class LinkedList<K, T>
where K : IComparable<K>ملاحظة أخيرة وهي ما يحتاج نظيف الـ Generic Constraint للكلاس Node حيث أن عملية المقارنة تصير فقط داخل الكلاس LinkedList، ولو أضفتها على الـ Node راح تضطر تضيفها كمان للـ LinkedList حتى لو ما كان هناك عملية مقارنة.
أتمنى أن اكون قد أصبت وأتمنى للجميع التوفيق والنجاح، ولمزيد من المعلومات عن الـ Generics لا تنسوا إنه أخونا الأستاذ هاني الأتاس ما قصر في موضوعه عن شرح الـ Generics