Generic

סגור באמצעות טופס זה תוכלו לספר ולהמליץ לחבריכם..
שם השולח:
כתובת דוא"ל של השולח:
שם המקבל:
שלח לכתובת דוא"ל:
הוסף הערה:
QR
בגרסה השניה של .NET Framework התווספה תמיכה ב- Generic. Generics מאפשרים להגדיר טיפוסים חדשים וכלליים (בדרך כלל אוספים) אשר יכולים לטפל בטיפוסים אחרים המוגדרים בתוכנית.
 
מאמר בנושא generics Generics

 

מאת: ארז קלר

הורדת דוגמאות קודהורדת דוגמאות קוד 


.NET Framework מגדירה תקן המכונה CTS) Common Type System).
תקן זה מגדיר, בין השאר, את הטיפוסים הנתמכים על ידי ה-CLR)  Common Language Runtime).
בגרסה 2.0 של .NET Framework שופר ה-CTS והתווספה לו תמיכה ב- Generic.
תוספת זו היא אחת מהיותר משמעותיות מבין מגוון התוספות בגרסה 2.0 .
Generics מאפשרים להגדיר טיפוסים חדשים (בדרך כלל אוספים) אשר יכולים לטפל במגוון רחב של טיפוסים אחרים המוגדרים בתוכנית.
תגידו, אולי בצדק, שכבר קיימים מנגנונים כאלו המתבססים על פולימורפיזם, כגון: ArrayList , Hashtable וכדומה.
כל טיפוסי האוסף הללו המוגדרים ב- .NET Framework החל מהגרסה הראשונה מתבססים על מחלקת הבסיס Object ועל מנגנון הפולימורפיזם שקובע שייחוס מטיפוס בסיס יכול להכיל כתובת של אובייקט ממחלקה נגזרת.
טיפוסים אלו הינם אוספים הטרוגניים אשר אינם מוגבלים לסוג הטיפוס אשר יאכלס אותם.
בגישה זו צצות שתי בעיות עיקריות:
  1. שימוש נרחב ב- Boxing וב- Unboxing וב- Casting אשר פוגע בביצועי בתוכנית.
  2. זיהוי מוטעה של הטיפוסים המוגדרים באותן מחלקות האוסף ההטרוגניות שיגרור להמרה לטיפוס הלא נכון וכתוצאה תתקבל חריגה העלולה לגרום להתרסקות התוכנית (Type Safe).
ניתן לפתור את הבעיות הללו בשתי דרכים:
  1. כתיבת מחלקת אוסף לכל טיפוס בנפרד.
    אמנם יפתור את שתי הבעיות הללו, אולם יוצר בעיה אחרת - שכפול קוד.
  2. שימוש ב- Generics, פיתרון שיוסבר להלן.
הבסיס
המנגנון לכתיבה או שימוש ב- Generics כלול במרחב השמות System.Collections.Generics.
המנגנון מאפשר כתיבת מתודות, מחלקות, ממשקים ו- Delegates המגדירים "טיפוס כללי", או לחילופין להשתמש במחלקות הכתובות כבר והן חלק מה- Framework.
"טיפוס כללי" אינו טיפוס אמיתי בתוכנית, אלא מתפקד כשומר מקום לטיפוס אמיתי שיוגדר בעת השימוש במחלקה/מתודה, הקישור לטיפוס האמיתי מתבצע בזמן ריצה..
טיפוס כללי יוגדר בין סוגריים משולשים, לדוגמה: <T>.
דוגמת קוד (GenericMethod) לכתיבה ושימוש במתודה גנרית:
 1  : class Program 
 2  : { 
 3  :     static void Swap<T>(ref T t1, ref T t2) 
 4  :     { 
 5  :         T tmp = t1; 
 6  :         t1 = t2; 
 7  :         t2 = tmp; 
 8  :     } 
 9  :     static void Main(string[] args) 
 10 :     { 
 11 :         int num1 = 11; 
 12 :         int num2 = 22; 
 13 :         Console.WriteLine("num1={0}, num2={1}", num1, num2); 
 14 :         Swap(ref num1, ref num2); 
 15 :         Console.WriteLine("num1={0}, num2={1}", num1, num2); 
 16 :  
 17 :         string str1 = "Elchanan"; 
 18 :         string str2 = "Sonya"; 
 19 :         Console.WriteLine("str1={0}, str2={1}", str1, str2); 
 20 :         Swap(ref str1, ref str2); 
 21 :         Console.WriteLine("str1={0}, str2={1}", str1, str2); 
 22 :     } 
 23 : } 
בשורה 3 מוגדרת מתודה גנרית, המתודה מגדירה טיפוס כללי אחד <T>, שני הפרמטרים שיתקבלו הם מהטיפוס האמיתי שיוגדרו בזמן קומפילציה במקום T.
בשורה 14 הקריאה למתודה Swap היא באמצעות פרמטרים מהטיפוס int, 
T במקרה זה יומר ב-int.
בשורה 20 
הקריאה למתודה Swap היא באמצעות פרמטרים מהטיפוס string, 
T במקרה זה יומר ב-string.
 
פלט:
הפלט של תוכנית הדוגמה GenericMethod

המחלקה List
הדוגמה הבאה (GenericListSample) מדגימה שימוש במחלקה הגנרית List המוגדרת במרחב השמות System.Collections.Generics.
 1  : class Program 
 2  : { 
 3  :     static void Main(string[] args) 
 4  :     { 
 5  :         List<string> names_arr = new List<string> { "Shoshana", "Yerachmiel", "Zelda", "Pirchiya", "Zalman", "Fruma" }; 
 6  :         foreach (string str in names_arr) 
 7  :         { 
 8  :             Console.Write(str + " "); 
 9  :         } 
 10 :         Console.WriteLine(); 
 11 :  
 12 :         List<int> nums_arr = new List<int>(); 
 13 :  
 14 :         Random rnd = new Random(); 
 15 :         for (int i = 0; i <  10; i++) 
 16 :         { 
 17 :             nums_arr.Add(rnd.Next(1000)); 
 18 :         } 
 19 :         foreach (int num in nums_arr) 
 20 :         { 
 21 :             Console.Write(num + " "); 
 22 :         } 
 23 :         Console.WriteLine(); 
 24 :     } 
 25 : } 
המחלקה List הפופלארית היא מחלקת אוסף גנרית, המשמעות של "מחלקת אוסף גנרית" היא היכולת לנהל אוסף של כל טיפוס הקיים בתוכנית.
בשורה 5 <T> מתחלף עם string, ובשורה 12 הטיפוס הכללי <T> מתחלף עם int.
השירותים שהיא מספקת זהים, כמובן לכל הטיפוסים המשתמשים בה.

פלט:
הפלט של תוכנית הדוגמה GenericListSample

Custom Generic
ניתן להגדיר גם מחלקת Generic משלנו, לדוגמה: GenericSample01
המחלקה Person:

 1  : class Person 
 2  : { 
 3  :     private string m_FirstName; 
 4  :     private string m_LastName; 
 5  :     private byte m_Age; 
 6  :     private int m_ID; 
 7  :  
 8  :     public Person(string first, string last, byte age, int id) 
 9  :     { 
 10 :         this.m_FirstName = first; 
 11 :         this.m_LastName = last; 
 12 :         this.m_Age = age; 
 13 :         this.m_ID = id; 
 14 :     } 
 15 :     public override string ToString() 
 16 :     { 
 17 :         return m_LastName + "," + m_FirstName + "," + m_Age; 
 18 :     } 
 19 : } 

המחלקה MyGenericArray:
 1  : class MyGenericArray<T> 
 2  : { 
 3  :     private T[] m_Arr; 
 4  :     private int m_Counter; 
 5  :  
 6  :     public MyGenericArray() 
 7  :     { 
 8  :         m_Arr = new T[16]; 
 9  :     } 
 10 :     public void Add(T t) 
 11 :     { 
 12 :         if (m_Counter  >= m_Arr.Length) 
 13 :             Array.Resize(ref m_Arr, m_Arr.Length + 16); 
 14 :         m_Arr[m_Counter++] = t; 
 15 :     } 
 16 :     public T this[int index] 
 17 :     { 
 18 :         get 
 19 :         { 
 20 :             return m_Arr[index]; 
 21 :         } 
 22 :     } 
 23 :     public void Print() 
 24 :     { 
 25 :         for (int i = 0; i <  m_Counter; i++) 
 26 :         { 
 27 :             Console.WriteLine(m_Arr[i]); 
 28 :         } 
 29 :  
 30 :     } 
 31 : } 
הגדרת מחלקת Generic חדשה חיונית כאשר רוצים להגדיר פונקציונאליות שאינה מוגדרת במחלקות ה- Generic המוגדרות בתשתית, או למנוע שימוש בפונקציונאליות קיימת במחלקה List.
בדוגמה הגדרנו את המתודה Print אשר אינה מופיעה במחלקה List
.
בשורה 1 מוגדרת המחלקה כמחלקה גנרית בעלת הטיפוס הגנרי <T>.
בשורה 23 מוגדרת המתודה Print.

Main:

 1  : class Program 
 2  : { 
 3  :     static void Main(string[] args) 
 4  :     { 
 5  :         MyGenericArray<int> arr1 = new MyGenericArray<int>(); 
 6  :         arr1.Add(100); 
 7  :         arr1.Add(200); 
 8  :         arr1.Add(300); 
 9  :         arr1.Add(400); 
 10 :         arr1.Add(500); 
 11 :         arr1.Print(); 
 12 :  
 13 :         MyGenericArray<Person> arr2 = new MyGenericArray<Person>(); 
 14 :         arr2.Add(new Person("Elimelech", "Zorkin", 65, 123456)); 
 15 :         arr2.Add(new Person("Zelda", "Gurnisht", 44, 4545454)); 
 16 :         arr2.Add(new Person("Zrubavel", "Berkovitch", 32, 8854646)); 
 17 :         arr2.Add(new Person("Vladimir", "Swisa", 12, 3434843)); 
 18 :         arr2.Print(); 
 19 :     } 
 20 : } 


פלט:
פלט תוכנית הדוגמה GenericSample01


הורשת המחלקה List
לחילופין, במקום להגדיר מחלקה גנרית חדשה יש מאין, רצוי לרשת מחלקת Generic קיימת המוגדרת בתשתית ולהוסיף לה את הפונקציונאליות הנדרשת, לדוגמה תוכנית GenericInheritance.
המחלקה MyGenericList:

 1  : class MyGenericList< T > : List< T > 
 2  : { 
 3  :     public void Print() 
 4  :     { 
 5  :         for (int i = 0; i <  base.Count; i++) 
 6  :         { 
 7  :             Console.WriteLine(base[i]); 
 8  :         } 
 9  :  
 10 :     } 
 11 : } 
המחלקה MyGenericList יורשת את List ואיתה את כל הפונקציונאליות המוגדרת בה ומוסיפה עליה פונקציונאליות נוספת של הדפסת הרשימה.

Main:

 1  : class Program 
 2  : { 
 3  :     static void Main(string[] args) 
 4  :     { 
 5  :         MyGenericList<string> list = new MyGenericList<string>(); 
 6  :         list.Add("Shoshana"); 
 7  :         list.Add("Yerachmiel"); 
 8  :         list.Add("Zelda"); 
 9  :         list.Add("Pirchiya"); 
 10 :         list.Add("Zalman"); 
 11 :         list.Add("Fruma"); 
 12 :  
 13 :         list.Print(); 
 14 :         Console.WriteLine(); 
 15 :     } 
 16 : } 
בשורה 5 של Main מוגדר מופע של המחלקה MyGenericList.
בשורה 13 קריאה למתודה MyGenericList.Print מדפיסה את תוכנו של האוסף.

פלט:
פלט דוגמת הקוד GenericInheritance

ממשקים גנריים
ניתן להגדיר גם ממשקים גנריים כפי שמוצג בדוגמה GenericInterface:
הממשק IMyInterface

 1  : interface IMyInterface<T> 
 2  : { 
 3  :     void Print(); 
 4  :  
 5  : } 
הממשק MyInterface מגדיר טיפוס כללי <T>.
הטיפוס הכללי יוחלף בטיפוס אמיתי בזמן המימוש כפי שמוצג במחלקה Person:
 1  : class Person : IMyInterface<Person> 
 2  : { 
 3  :     private string m_FirstName; 
 4  :     private string m_LastName; 
 5  :     private byte m_Age; 
 6  :     public Person(string first, string last, byte age) 
 7  :     { 
 8  :         this.m_FirstName = first; 
 9  :         this.m_LastName = last; 
 10 :         this.m_Age = age; 
 11 :     } 
 12 :     public void Print() 
 13 :     { 
 14 :         Console.WriteLine(m_LastName + "," + m_FirstName + "," + m_Age); 
 15 :     } 
 16 : } 
בשורה 1 המחלקה Person ממשת את הממשק ומגדירה את הטיפוס הכללי כ- Person,
בזמן הגדרת מימוש הממשק, ניתן להגדיר גם טיפוסים אחרים כטיפוסים האמיתיים של <T>.
כמו כן, המחלקה חייבת לממש את המתודה Print המוגדרת בממשק.

מחלקת האוסף MyGenericArray:


 1  : class MyGenericArray< T > 
 2  : { 
 3  :     private T[] list; 
 4  :     private int counter; 
 5  :  
 6  :     public MyGenericArray() 
 7  :     { 
 8  :         list = new T[16]; 
 9  :     } 
 10 :     public void Add(T t) 
 11 :     { 
 12 :         if (counter  >= list.Length) 
 13 :             Array.Resize< T >(ref list, list.Length + 16); 
 14 :         list[counter++] = t; 
 15 :     } 
 16 :     public T this[int index] 
 17 :     { 
 18 :         get 
 19 :         { 
 20 :             return list[index]; 
 21 :         } 
 22 :     } 
 23 :     public void Print() 
 24 :     { 
 25 :         for (int i = 0; i <  counter; i++) 
 26 :         { 
 27 :             Console.WriteLine(list[i]); 
 28 :         } 
 29 :     } 
 30 :     public void PrintAll() 
 31 :     { 
 32 :         for (int i = 0; i <  counter; i++) 
 33 :         { 
 34 :             (list[i] as IMyInterface< T >).Print(); 
 35 :         } 
 36 :     } 
 37 : } 

Main:
 1  : class Program 
 2  : { 
 3  :     static void Main(string[] args) 
 4  :     { 
 5  :         MyGenericArray<Person> arr1 = new MyGenericArray<Person>(); 
 6  :         arr1.Add(new Person("Elimelech", "Zorkin", 65)); 
 7  :         arr1.Add(new Person("Zelda", "Gurnisht", 44)); 
 8  :         arr1.Add(new Person("Muhamad", "Berkovitch", 32)); 
 9  :         arr1.Add(new Person("Vladimir", "Swisa", 12)); 
 10 :         arr1.PrintAll(); 
 11 :         Console.WriteLine(); 
 12 :     } 
 13 : } 


במתודה MyGenericArray.PrintAll מבוצעת המרה מהייחוס האוסף arr לייחוס מטיפוס הממשק הגנרי,
חובה לבצע המרה זו משום שבזמן כתיבת המחלקה האוסף arr הוא מטיפוס כללי ולא מהטיפוס Person ולכן לא יכיר את המתודה Print.
פלט:
פלט התוכנית GenericInterface

מה יקרה, בדוגמה הקודמת, אם נחליף מחלקה MyGenericArray את הטיפוס הכללי <T> בטיפוס שלא מממש את הממשק IMyInterface?
נוסיף את המחלקה Dog לתוכנית:
 1  : class Dog 
 2  : { 
 3  :     private string name; 
 4  :     private int age; 
 5  :     public Dog(string name, int age) 
 6  :     { 
 7  :         this.name = name; 
 8  :         this.age = age; 
 9  :     } 
 10 :     public void Print() 
 11 :     { 
 12 :         Console.WriteLine(name + "," + age); 
 13 :     } 
 14 : } 
Dog היא מחלקה רגילה שאינה מממשת את הממשק IMyInterface.

ובמתודה Main נגדיר מופע נוסף של MyGenericArra y והפעם הטיפוס T יתחלף עם Dog:

 1  : class Program 
 2  : { 
 3  :     static void Main(string[] args) 
 4  :     { 
 5  :         MyGenericArray< Person > list1 = new MyGenericArray< Person >(); 
 6  :         list1.Add(new Person("Elimelech", "Zorkin", 65)); 
 7  :         list1.Add(new Person("Zelda", "Gurnisht", 44)); 
 8  :         list1.Add(new Person("Muhamad", "Berkovitch", 32)); 
 9  :         list1.Add(new Person("Vladimir", "Swisa", 12)); 
 10 :         list1.PrintAll(); 
 11 :         MyGenericArray< Dog > list2 = new MyGenericArray<Dog>(); 
 12 :         list2.Add(new Dog("Aldo", 1)); 
 13 :         list2.Add(new Dog("Edison", 6)); 
 14 :         list2.Add(new Dog("Boots", 6)); 
 15 :         list2.PrintAll(); 
 16 :     } 
 17 : } 

ניסיון להריץ את התוכנית יעורר חריגה:

די ברור למה, ההמרה המתבצעת לטיפוס הממשק אינה חוקית.
הבעיה היא שאת התקלה מגלים רק בזמן ריצה, בזמן רקומפילציה הכל תקין.

הפיתרון, המילה השמורה where.
באמצעות המילה השמורה where ניתן להגדיר אילוצים לשימוש במחלקה הגנרית,
דוגמה לאילוצים אפשריים: במחלקה המחליפה את T חייב להיות בנאי ברירת מחדל, המחלקה חייבת לממש ממשק זה או אחר וכדומה, המחלקה חייבת לרשת מחלקה זו או אחרת, המחלקה חייבת להיות Value Type או Reference Type וכדומה.

 
where T : struct   הטיפוס חייב להיות ValueType .
where T : class הטיפוס חייב להיות Reference Type.
where T : BaseClass הטיפוס חייב להיות מחלקת בסיס או אחת מניגזרותיה.
where T : IInterface הטיפוס חייב לממש את הממשק IInterface.
where T : new() לטיפוס חייב להיות Default Ctor.

בתוכנית הדוגמהGenericInreface03 הגדרנו את המחלקה MyGenericArray עם האילוץ הבא:
class MyGenericArray<T> where T:IMyInterface<T>

האילוץ שבדוגמה מגדיר שהטיפוס T יכול לשמור מקום רק לטיפוסים המממשים את הממשק IMyInterface, כל ניסיון להגדיר כעת את מחלקת האוסף הגנרית כאוסף של Dog יגרור שגיאת קומפילציה:
שגיאת קומפילציה
האילוץ מבטיח שהתוכנית לא תתרסק במתודה MyGenericArray.PrintAll עקב Type Safe.

Delegate גנריים

גם מקומם של ה- Delegate אינו נפקד מרשימת הטיפוסים הגנרים, כפי שניווכח בדוגמה הבאה (DelegateSample):

 1  : public delegate void MyDelegate< T >(T t); 
 2  : class Program 
 3  : { 
 4  :     static void Main(string[] args) 
 5  :     { 
 6  :         Person p = new Person("isaschar", "shemyafe", 78); 
 7  :         MyDelegate<Person> del1 = new MyDelegate<Person>(Program.Func); 
 8  :         del1(p); 
 9  :         MyDelegate<int> del2 = new MyDelegate<int>(Program.Func); 
 10 :         del2(123); 
 11 :     } 
 12 :     static void Func<T>(T t) 
 13 :     { 
 14 :         Console.WriteLine(t); 
 15 :     } 
 16 : } 
הערה - המחלקה Person ללא שינוי מהדוגמאות קוד הקודמות.
בשורה 7 - הגדרת מופע של ה-Delegate עם אובייקט מהמחלקה Person (
המחלקה Person ללא שינוי מהדוגמאות קוד הקודמות).
בשורה 10 - 
הגדרת מופע של ה-Delegate עם מופע של int.
בשורה 12 - הגדרת מתודה שחתימתה זהה לחתימת ה-Delegate ואשר מקבלת טיפוס כללי <T>.

פלט:
פלט דוגמת הקוד DelegateSample





כל הזכויות שמורות למחבר ©