الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

Generics could be an abomination

بدأه Robatic في 19 أكتوبر 2010 · 4 رد · 1,185 مشاهدة · في Microsoft Visual C#.NET
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

السلام عليكم ورحمة الله وبركاته

سيداتي سادتي لن أتطرق لموضوع الـ 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

1
#2

شكله الموضوع رائع دام انه من طرفك

بس في البداية حشوف مقالة الأخ هاني وبعد كذا ارجع لمقالتك

مشكور حبيبي .

#3

أولاً : يمكنك تجنب عمل Implementation للواجهة IComparable ويتم باستخدام الفئة EqualityComparer , واإجراء EqualityComparer<K>.Default.Equals(K obj1 , K obj2) بدل استخدام ال Reference Equality Operator . وهو يوفّر الكثير فهو سيقوم بالمطلوب مهما كان النوع K سواء Value Type أو Reference Type .

وبهذه الطريقة لن تحتاج إلى أن تفرض على المستخدم أن يستخدم نوع يحقق واجهة معينة , افترض مثلاً أن المستخدم ليس لديه تحكم على النوع K أي لا يستطيع جعل النوع K يحقق الواجهة IComparable ?

ثانيًا: عملية Boxing/Unboxing لا تتم سوى مع الأنواع ذات القيمة Value Types والنوع FileStream ليس Value Type ! وهي عملية يتم فيها نسخ الكائن كاملاً كما هو من الرصة Stack إلى Heap وهي بالتالي ليست عملية تحويل Casting فهناك فرق التحويل Casting والصندقة Boxing .

1
#4
IslamDotNet كتب:

أولاً : يمكنك تجنب عمل Implementation للواجهة IComparable ويتم باستخدام الفئة EqualityComparer , واإجراء EqualityComparer<K>.Default.Equals(K obj1 , K obj2) بدل استخدام ال Reference Equality Operator . وهو يوفّر الكثير فهو سيقوم بالمطلوب مهما كان النوع K سواء Value Type أو Reference Type .

وبهذه الطريقة لن تحتاج إلى أن تفرض على المستخدم أن يستخدم نوع يحقق واجهة معينة , افترض مثلاً أن المستخدم ليس لديه تحكم على النوع K أي لا يستطيع جعل النوع K يحقق الواجهة IComparable ?

ثانيًا: عملية Boxing/Unboxing لا تتم سوى مع الأنواع ذات القيمة Value Types والنوع FileStream ليس Value Type ! وهي عملية يتم فيها نسخ الكائن كاملاً كما هو من الرصة Stack إلى Heap وهي بالتالي ليست عملية تحويل Casting فهناك فرق التحويل Casting والصندقة Boxing .

أخي اللعزيز IslamDotNet

أشكرك جزيل الشكر على مرورك الكريم، بالنسبة للـ Boxing/Unboxing فكلامك صحيح 100% وزي ما يقولوا جل من لا يسهوا، أما بالنسبة للـ EqualityComparer فياليت لو تكرمنا بمثال صغير عشان تعم الفائدة.

ومنكم نستفيد

#5

يار يت يا استاذنا الجليل الشرح لتلمذيك هذا الدرس بس فيديو لانى مش عارف استوعب كود بس

جزاك الله خير باذن الله

مواضيع مشابهة