26. Generic Types

הטיפים הקרובים הם הכנה בשביל הטיפים שאחריהם.

(זהירות, הרבה קוד משוכפל)

נניח שיש לנו את המחלקה הבאה:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class IntCollection
{
private int[] m_InternalArray = new int[4];
private int m_NumOfValues;
public int this[int index]
{
get
{
return m_InternalArray[index];
}
set
{
m_InternalArray[index] = value;
}
}
public void Add(int item)
{
if (m_NumOfValues == m_InternalArray.Length)
{
int[] copyArray = new int[m_InternalArray.Length * 2];
m_InternalArray.CopyTo(copyArray, 0);
m_InternalArray = copyArray;
}
m_InternalArray[m_NumOfValues] = item;
m_NumOfValues++;
}
}

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

עכשיו נניח שאנחנו רוצים אותו מימוש גם עבור מחלקות אחרות, לאו דווקא int, אז מה שהיינו צריכים לעשות בימי framework 1 העליזים זה לשכפל את הקוד, ובכל מקום להחליף את int במחלקה שאנחנו מעוניינים לבנות עבורה את הCollection:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class PersonCollection
{
private Person[] m_InternalArray = new Person[4];
private int m_NumOfValues;
public Person this[int index]
{
get
{
return m_InternalArray[index];
}
set
{
m_InternalArray[index] = value;
}
}
public void Add(Person item)
{
if (m_NumOfValues == m_InternalArray.Length)
{
Person[] copyArray = new Person[m_InternalArray.Length * 2];
m_InternalArray.CopyTo(copyArray, 0);
m_InternalArray = copyArray;
}
m_InternalArray[m_NumOfValues] = item;
m_NumOfValues++;
}
}

וכך באמת היה קורה באותם הימים. כך קיבלנו למשל DataRowCollection, DataTableCollection,DataColumnCollection ושלל מחלקות אחרות מFramework 1.0 שכולן פחות או יותר עם אותה פונקציונאליות, רק על Type שונה.

יכולנו להתחכם וליצור מחלקה כזאת שעובדת עם object

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Collection
{
private object[] m_InternalArray = new object[4];
private int m_NumOfValues;
public object this[int index]
{
get
{
return m_InternalArray[index];
}
set
{
m_InternalArray[index] = value;
}
}
public void Add(object item)
{
if (m_NumOfValues == m_InternalArray.Length)
{
object[] copyArray = new object[m_InternalArray.Length * 2];
m_InternalArray.CopyTo(copyArray, 0);
m_InternalArray = copyArray;
}
m_InternalArray[m_NumOfValues] = item;
m_NumOfValues++;
}
}

אבל אז בכל גישה למחלקה היינו צריכים לעשות הסבה לint / Person / הסוג המבוקש של האוסף.

בנוסף, עבור primitive types היינו מקבלים boxing וunboxing בכל גישה (הכנסה והוצאה מobject), דבר שהיה גורע מהביצועים.

בFramework 2.0 אחד הדברים הכי חשובים שהכניסו זה Generics. הדבר מאפשר לנו לכתוב קוד גנרי ולתת למשתמש במחלקה שלנו להחליט מבחוץ מה יהיה הType.

הקוד שכתבנו קודם לכן יראה כך:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Collection<T>
{
private T[] m_InternalList = new T[4];
private int m_NumOfValues;
public T this[int index]
{
get
{
return m_InternalList[index];
}
set
{
m_InternalList[index] = value;
}
}
public void Add(T item)
{
if (m_NumOfValues == m_InternalList.Length)
{
T[] copyArray = new T[m_InternalList.Length*2];
m_InternalList.CopyTo(copyArray, 0);
m_InternalList = copyArray;
}
m_InternalList[m_NumOfValues] = item;
m_NumOfValues++;
}
}

בעצם כתבנו מחלקה, שעובדת עם איזה טיפוס "T". את הT הזה קובע מי שמשתמש במחלקה מבחוץ. כדי להשתמש במחלקה שלנו הוא צריך לעשות משהו כזה:

1
2
3
4
5
6
7
8
Collection<Person> personCollection = new Collection<Person>();
// a collection of Person
Collection<int> intCollection = new Collection<int>();
// a collection of int
Collection<object> objectCollection = new Collection<object>();
// a collection of object

כך בעצם אנחנו חוסכים המון שכפול קוד ומאפשרים Reuse מיטבי של הקוד שלנו.

מה שקורה בעצם זה שבזמן ריצה נוצרת מחלקה חדשה לType (במידה ולא נוצרה קודם) שבה מוחלף בכל מקום ה"T" בטיפוס שמוזרק מבחוץ.

קצת על הsyntax: בתוך המחלקה אנחנו מציינים את הGeneric Types שלנו בסוגריים כאלה <> המופרדים בפסיקים ביניהם. אנחנו יכולים לתת כל שם שעולה על רוחנו, אבל מומלץ להשתמש בT אם יש רק Type אחד כזה בתור פרמטר, ואחרת בשם עם תחילית T.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Pair<TFirst, TSecond>
{
private TFirst m_First;
private TSecond m_Second;
public TFirst First
{
get { return m_First; }
set { m_First = value; }
}
public TSecond Second
{
get { return m_Second; }
set { m_Second = value; }
}
}

הדבר תקף גם לגבי ממשקים. נוכל להגדיר באופן זהה Generic Types ולקבל ממשק שבו מגדירים טיפוס מבחוץ. למשל:

1
2
3
4
5
6
7
8
9
10
11
12
private interface IPair<TFirst, TSecond>
{
TFirst First
{
get;
}
TSecond Second
{
get;
}
}

בנוסף לFeature המהפכני, קיבלנו את הnamespace ששמו System.Collections.Generic בו ממשקים ומימושים עם Generics, ביניהם List, Dictionary ושאר הממשקים והטיפוסים שכבר למדנו לאהוב.

שבוע גנרי טוב

שתף