זו מחלקה די פשוטה המייצגת מימוש דבילי של רשימה, שיש לה שתי פונקציות – להוסיף איבר ולמצוא איבר במקום מסוים.
עכשיו נניח שאנחנו רוצים אותו מימוש גם עבור מחלקות אחרות, לאו דווקא 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
publicclassPersonCollection
{
private Person[] m_InternalArray = new Person[4];
privateint m_NumOfValues;
public Person this[int index]
{
get
{
return m_InternalArray[index];
}
set
{
m_InternalArray[index] = value;
}
}
publicvoidAdd(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 שונה.
אבל אז בכל גישה למחלקה היינו צריכים לעשות הסבה ל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];
privateint m_NumOfValues;
public T this[int index]
{
get
{
return m_InternalList[index];
}
set
{
m_InternalList[index] = value;
}
}
publicvoidAdd(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 ושאר הממשקים והטיפוסים שכבר למדנו לאהוב.