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

Extension Methods

بدأه AliBazzi في 23 سبتمبر 2010 · 2 رد · 2,401 مشاهدة · في قسم تكنولوجيا Microsoft .NET العام
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

تسمح الـ Extension Methods للمبرمج بإضافة Method جديدة إلى أي Type موجود مسبقا ,دون الحاجة لحصولك على الـ Source Code و لا تحتاج إلى إعادة عمل Compiling للـ Library التي تحوي ذاك الـ Type ... أغلب تطبيقاتنا اليومية في البرمجة , ممكن أن تتحول إلى شيء أكثر جمالا و منطقية بإستعمال الـ Extension Methods , فلنفرض أننا نقوم بعمل برنامج بسيط كالتالي :

static class Program
    {
        static int  WordsCount(string str)
        {
            string[] words =  str.Split(new[] {' '});
            return words.Length;
        }
 		static void Main(string[] args)
        {
 			Console.WriteLine("Please type your Sentence:");
            string  sentence = Console.ReadLine();
            Console.WriteLine("number  of Words : {0}", WordsCount(sentence) );
        }
    }

يقوم هذا البرنامج بقراءة String من المستخدم و يحاول إحصاء عدد الكلمات الموجودة في هذا الـ String , لذلك قمنا بكتابة WordsCount Method التي تساعدنا في إحصاء عدد الكلمات ... وقمنا بإستعمالها كما ترى في اّخر سطر ...

جميل , و لكن , ألم يكن من المفترض أن تقوم مايكروسوفت ببرمجة مثل هكذا Method لتقوم بما قامت به WordsCount ؟

لحظة , وصديقي أيضا يريد الـ Method الفلانية أن تكون موجودة في الـ Type الفلاني أيضا ... لما لا نقوم بالتوقيع على عريضة و كتابة كل مطالبنا "التوسيعية" الجديدة للـ Net. ونرسلها إلى مايكروسوفت ... لحسن الحظ , هناك Extension Methods Concept !

static class Program
    {
        static int  WordsCount(this string str)// we added the this keyword before  mentioning the type of parameter
        {
            string[]  words = str.Split(new[] {' '});
            return words.Length;
 		}
        static void Main(string[] args)
        {
 			Console.WriteLine("Please type your Sentence:");
 			string sentence = Console.ReadLine();
 			Console.WriteLine("number of Words : {0}", sentence.WordsCount() );//now  , we can call WordsCount as if it's from the heart of string primitive  type !
        }
    }

لتقوم بتعريف Extension Method , ما عليك إلا إضافة كلمة this قبل ذكر نمط الـ parameter (وعدة شروط أخرى , تذكر لاحقا ) , وبالتالي أنت تقوم بتوسيع الصنف المذكور في الـ parameter , وفي حالتنا هذه قمنا بتوسيع الـ string , و إلحاق الـ WordsCount Method الجديدة , والتي ستظهر من الاّن فصاعدا كأنها Member من أي String Object , وسيعرضها الـ Visual Studio intellisense كالتالي :

Untitled.png

لكن , لاحظ الشكل الموجود بقرب الـ WordsCount , لا يشبه بقية الـ Methods , هذا الشكل يرمز إلى أن هذه الـ method ما هي إلا Extension Method ...

الـ Extension Method ببساطة تمكّن أي شخص , من توسيع أي Type موجود , دون الحاجة لعمل Recompile للـ Library التي تحوي الـ Type المستهدف بالتوسعة , لكن هناك عدة شروط أخرى :

1- يجب أن تكون الـ Method معرفة كـ Static

2- يجب أن تكون الـ Extension Method محتواة داخل Static Class حصرا

3- أن توضع الكلمة المفتاحية this قبل إسم الـ type المستهدف بالتوسعة , والذي سيمرر للـ Extension Method كـ Parameter

4- يمكن وضع Parameters أخرى , بعد الـ Parameter الأول (والذي سيشير إلى الصنف الذي يتم توسعته ) , وتوضع بعده حصرا

5- الـ Parameter الأول لا يظهر عند البدء بتمرير الـ Parameters للـ extension Method , بل سيقوم الـ Compiler بتمرير الـ Object الذي يتم إستهدافه حاليا في تنفيذ الـ Extension Method

6- لا يمكن توسيع Static Class (أصلا لا فائدة لذلك , ومن غير المجدي أيضا ).

7- تستطيع توسيع أي Interface , Delegate , Class , Struct , Enum موجودة ...

8- لا تستطيع أي Extension Method الوصول إلى أي member بالـ extended Type إلا إذا كان يحمل الـ Public Access Modifier (أو Internal إذا كانا في نفس الـ Library , مع أن ذلك أمر مستهجن , فلماذا أقوم بتوسعة شيء معي الـ Source Code خاصت ؟), وبالتالي لا يوجد أي خرق لأي مبدأ من مبادئ الـ Object Oriented , فما تستطيع الوصول له الـ Extension Method هو ما نستطيع نحن الوصول له في الحالة الطبيعية .

لنأخذ أمثلة أكثر تعقيدا , وربما فائدة . فلنقم بتوسيع الـ List<T> Class بعدة Methods كالتالي:

static class ListExtensions
    {
 		public static void PrintAll<T>(this List<T> list)
 		{
            foreach (var item in list)
     			Console.WriteLine(item);
        }

        public static void  PrintAll<T>(this List<T> list,ConsoleColor color)
 		{
            Console.ForegroundColor = color;
 			list.PrintAll<T>();//print them , using previous  PrintAll<T>() extension Method ...
 			Console.ForegroundColor = ConsoleColor.Gray;
        }

 		public static void DoOnAll<T>(this List<T> list,  Action<T> action)
        {
            foreach (var item in  list)
                action.Invoke(item);
        }
    }

ثلاث Extension Methods السابقة بسيطة , كود الإختبار هو :

static class Program
    {
        static void  Main(string[] args)
        {
            List<int> myList =  new List<int> { 78,56,34,32,54,56,67,78,56,34,321 };

 			myList.PrintAll();
 			myList.PrintAll(ConsoleColor.Green);
            myList.DoOnAll(item  => Console.WriteLine(item * item));
        }
    }

لا أعتقد أن هناك حاجة للشرح ...

Extension methods with Interfaces :

لكن لننتقل إلى توسيع الـ Interfaces وهو بإعتقداي الأهم , سأقوم بتوسيع IEnumerable<T> interface بعدة Methods تشبه ما قمنا به للـ List ...

static  class ListExtensions
    {
        public static void  PrintAll<T>(this IEnumerable<T> list)
        {
 			foreach (var item in list)
     			Console.WriteLine(item);
        }

        public static void  PrintAll<T>(this IEnumerable<T> list,ConsoleColor color)
 		{
            Console.ForegroundColor = color;
 			list.PrintAll<T>();//print them , using previous  PrintAll<T>() extension Method ...
 			Console.ForegroundColor = ConsoleColor.Gray;
        }

 		public static void DoOnAll<T>(this IEnumerable<T> list,  Action<T> action)
        {
            foreach (var item in  list)
                action.Invoke(item);
        }
    }

وهنا , سنصبح قادرين على الوصول لهذه الـ Methods من أي Object يتبع لـ Type يقوم بتطبيق هذه الـ interface وهو شيء ليس بالقليل إطلاقا !

مثال :

static class Program
    {
        static void  Main(string[] args)
        {
            List<int> myList =  new List<int> { 78,56,34,32,54,56,67,78,56,34,321 };

 			myList.PrintAll(ConsoleColor.Green);
 			myList.DoOnAll(item => Console.WriteLine(item * item));

 			string[] myArray = new string[] {"Hello","Hi","Bye" };

 			myList.DoOnAll(str => Console.WriteLine("He says:{0}",str));

 			Queue<char> myQueue = new Queue<char>();
 			myQueue.Enqueue('a');
            myQueue.Enqueue('b');
 			myQueue.Enqueue('c');

            myQueue.PrintAll();
 		}
    }

بهذه البساطة , إستطعنا الوصول و تنفيذ نفس الـ Extension Methods السابقة , على كل بٌنى المعطيات السابقة , لأنها تقوم كلها بتطبيق الـ IEnumerable<T> interface ...

وبالتالي , عند قيامك بتوسعة Interface , فأنت عمليا تقوم بتوسعة أي Type يقوم بتطبيق الـ Interface أو حتى أي Interface ترث من الـ Interface المٌوسعة بواسطة الـ Extension Methods , وهذا يٌنتج أن نفس الشروط السابقة تنطبق على الـ Interface الوارثة من الأصلية المٌوسعة ...

أكثر من ذلك , بفرض أننا نملك المعلومات التالية :

ClassDiagram.png

والتي توضح أن كلا من List , array تطبق الـ IList<T> Interface . والـ Queue تطبق الـ IEnumerable<T> Interface فقط .

وعلما أن الـ IList<T> interface ترث الواجعة IENumerable<T> Interface ... وبالتالي أي Type يطبق IList<T> Interface حكما سيطبق الـ IEnmerable<T> Interface بالتأكيد ...

وقمنا بكتابة الـ Extension Methods التالية , و التي تستهدف كلا من الـ IList<T> Interface و الـ IEnumerable<T> Interface :

static class Extensions
    {
        public  static void PrintAll<T>(this IEnumerable<T> list)
 		{
            Console.WriteLine("Hello from IEnumerable<T>:");
 			foreach (var item in list)
     			Console.WriteLine(item);
        }
        public static void  PrintAll<T>(this IList<T> list)
        {
 			Console.WriteLine("Hello from IList<T>:");
            foreach  (var item in list)
                Console.WriteLine(item);
 		}
    }

وقمنا بتجربة المثال التالي :

static class Program
    {
        static void  Main(string[] args)
        {
            List<int> myList =  new List<int> { 78,56,34,321 };
            myList.PrintAll();

 			Queue<char> myQueue = new Queue<char>();
 			myQueue.Enqueue('a');
            myQueue.Enqueue('b');
 			myQueue.Enqueue('c');
            myQueue.PrintAll();

 			string[] myArray = new string[] {"Hello","Hi","Bye" };
 			myList.PrintAll();
        }
    }

فإن النتيجة ستكون :

Hello from IList<T>:

78

56

34

321

Hello from IEnumerable<T>:

a

b

c

Hello from IList<T>:

78

56

34

321

يلاحظ من النتيجة ما يلي :

قام الـ compiler بإختيار تطبيق الـ PrintAll<T>(this IEnumerable<T> list):void على الـ Queue ...

وقام بإختيار PrintAll<T>(this IList<T> list):void على كل من الـ Array والـ List ...

فيتضح هنا , أن الـ Compiler يقوم بإختيار الـ Extension Method الأخص (التي تعني الواجهة الأكثر تخصصا ) للـ Objects ...

فوجود Extension Method بنفس الإسم و التوقيع (بتجاهل أول Parameter ) للـ IList غطى على وجود نفس الـ Extension Method بالنسبة للـ IEnumerable ...

إذا و كخلاصة : الـ Compiler يختار الـ Extension method من الـ Interface الأكثر إختصاصا (في شجرة الوراثة ) ...

لكن , كيف يمكنني إجبار الـ Compiler على تطبيق Extension Method بعينها , إذا وجد أكثر من واحدة ممكنة التطبيق , دون اللجوء إلى خيار (الإختصاص) الذي يقوم به الـ Compiler ؟... كالتالي :

static class Program
    {
        static  void Main(string[] args)
        {
            List<int>  myList = new List<int> { 78, 56, 34, 321 };
 			myList.PrintAll();//the compiler would choose  PrintAll<T>(IList<T>):void by default
            //but I  Can Force the Compiler to Choose  PrintAll<T>(IEnumerable<T>):void by :
            (myList  as IEnumerable<int>).PrintAll(); // now , the  PrintAll<T>(IEnumerable<T>):void will be Excuted ...
 		}
    }

وبالتأكيد الـ Compiler سيتبع نفس الإستراتيجية في إختيار الأكثر إختصاصا إذا ما واجه نفس المشكلة السابقة لكن مع وجود Class أو Struct يقوم بالوراثة من Interface , مع وجود Two Extension Methods بنفس الإسم و التوقيع في كل من الـ Interface و الـ Type ... و يمكن حلها بنفس الطريقة السابقة .

Extension Methods with Delegates :

الـ Delegates مصيبتها أنها لا يمكن إضافة أي Member لها بالطريقة التقليدية , حتى لو إمتلكت الـ Source Code (فـ Compiler الـ #C يقوم بتوليد الـ Delegate Type وعندها لا يمكن تعديله ).

لكن لحسن الحظ , مع وجود الـ Extension Method , يمكننا توسيع أي Delegate بالأسلوب الذي نعرفه , مثال سريع ومباشر :

static  class Extensions
    {
        public static void MyInvoke(this  Action action)
        {
            Console.WriteLine("Calling  Action :");
            action.Invoke();
 			Console.WriteLine("End of call Action");
        }
    }
 	static class Program
    {
        static void Main(string[] args)
 		{
            Action myAction = new  Action(()=>Console.WriteLine(Math.Pow(3,10)));
 			myAction.MyInvoke();
        }
    }

شكرا للـ Extension Methods , عن طريقها , إستطعنا تخطي عقبة كبيرة في جمود الـ Delegate كبنية برمجية ...

بنفس الطرق السابقة , يمكن توسعة كل من الـ Struct و الـ Enum ... ولكن أترك الموضوع لك للإستكشاف ...

خلف الكواليس :

عمليا , قد إنتهينا من إستعراض الـ Extension Methods لكن , لكل شخص يحب معرفة ما يحصل خلف الكواليس , فهذه الفقرة ستعرض الطريقة التي يكتشف فيها الـ Compiler وجود Extension Methods ... وكيف يقوم بالربط الصحيح .

كل Compiler تنتجه مايكروسوفت يقوم بالبحث في كل الـ Name Spaces المستوردة في النطاق البرمجي الحالي (Current Context) عن Static Classes تحوي Extension Methods , وترفقها في الـ intellisense عند طلبها على أحد الـ Object التي تكون أنماطها مستهدفة في التوسعة ...

وعند تحويل الـ Code إلى MSIL تقوم بتمرير الـ object الذي طٌلب عليه الـ Extension Method إلى أول Parameter كالتالي:

class  MyClass
    {
        //...
    }
    static class  Extensions
    {
        public static void foo(this MyClass  action,int num) { /*... */ }
    }
    static class Program
 	{
        static void Main(string[] args)
        {
 			MyClass obj = new MyClass();
            obj.foo(123);
 			//it will be converted to :
            Extensions.foo(obj,123); //  you can even call foo in this way without any problem with the compiler  (but if you will , forget this article )
            //so , obk.foo()  and Extensions.foo(obj) are Equivalent !
        }
    }

ملاحظة أخيرة قبل الختام , عندما تعّرف Extension Methods وتكون محتواة في class ما داخل name space معين , فيجب على مستعمل الـ Extension Methods خاصتك أن يقوم بإستيراد الـ namespace خاصتك في حال رغبته بإستعمال الـ Extension Methods المكتوبين من قبلك ...

وهنا يوجد نصيحتين من مايكروسوف :

1- إذا كنت بشكل إفتراضي تريد للـ extension Methods التواجد عند إستعمال الـ Type المستهدف بالتوسعة , فعليك تعريف الـ Extension Methods في نفس الـ Namespace الذي يتواجد فيه الـ Type المستهدف :

using System;
namespace System
{
 	public static class StringExtensions
    {
        public static  int WordsCount(this string str)
        {
            return  str.Split(new[] {' '}).Length;
        }
    }
}
namespace  myProgram
{
    static class Program
    {
        static  void Main(string[] args)
        {
            Console.WriteLine(  "I hate Dogs".WordsCount() );
        }
    }
}

2- في حال عدم رغبتك بتوفير الـ Extension Methods بشكل مباشر كما في المثال السابق , فقم بوضع الـ Extension Methods خاصتك في Namespace جديد يكون يحمل الشكل العام التالي :

"Extended Type Namespace ".Extensions

المثال السابق لكن معدل :

using System;
namespace System.Extensions
{
 	public static class StringExtensions
    {
        public  static int WordsCount(this string str)
        {
 			return str.Split(new[] {' '}).Length;
        }
    }
}
namespace  myProgram
{
    using System.Extensions;
    static class  Program
    {
        static void Main(string[] args)
        {
 			Console.WriteLine( "I hate Dogs".WordsCount() );
        }
 	}
}

الـ Extension Methods قامت مايكروسوفت بإختراعها خصيصا لتدعم تصميم الـ Linq المعماري , دون أن تنبش الـ Libraries القديمة و تكسر الـ Backward Compatibility ...

شكرا للـ Linq الذي سمح لنا برؤية Concept بجمال الـ Extension Methods ...

مستعد للأسئلة ...

تم تعديل هذه المشاركة بواسطة AliBazzi في 23 سبتمبر 2010 في 02:02

6
#2

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

جميل جداً وشرح وافي وكامل , جزاك الله خير :) .

((إِنَّ اللَّهَ وَمَلائِكَتَهُ يُصَلُّونَ عَلَى النَّبِيِّ يَا أَيُّهَا الَّذِينَ آمَنُوا صَلُّوا عَلَيْهِ وَسَلِّمُوا تَسْلِيمًا))

#3

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

شكرا اخي على المجهود, اريد ان اضيف الرابط التالي للتعرف اكثر على الـ Extension Methods وماهي ايجابيتها وسلبياتها http://www.vitalygorn.com/blog/post/2008/01/Extension-Methods---Extension-or-Confusion.aspx .. بالتوفيق ..

.:: Wish to become better and better ::.

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