155. OfType and Cast

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

בFramework 2.0 נוספו טיפוסים גנריים, ונוסף גם הממשק IEnumerable. (ראו גם טיפים מספר 51-55)

לצרכי תאימות לאחור ושיקולים נוספים (טיפ מספר 150 למשל), רוב הטיפוסים בFramework שממשים IEnumerable מממשים גם IEnumerable.

אלא, שלמרבה הצער, לא ניתן להשתמש ברוב הפונקציות של LINQ (כולל הQuery syntax) על הטיפוס IEnumerable, אלא רק על הגרסה הגנרית שלו.

דבר זה כואב מאוד למי שיוצא לו לעבוד עם חלקים מהFramework שהגיחו בFramework 1.0 ולא שודרגו מאז, לפחות מבחינת הAPI, ביניהם ADO.net (עבודה עםDataSet/DataTableים) וWindows Forms.

קיימים הרבה מאוד טיפוסים מתקופה זו שמממשים IEnumerable ולא IEnumerable. דוגמאות בולטות לכך הן DataRowCollection וControlCollection.

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

קיימות שתי Extension Methods של IEnumerable המסייעות לנו בעניינים אלה:

הראשונה היא Cast המאפשרת לנו להמיר את הIEnumerable שלנו לIEnumerable עבור T שנציין:

1
2
3
4
5
6
7
IEnumerable theNumbersAreEvil =
new object[] {4, 8, 15, 16, 23, 42};
IEnumerable<int> theNumbers =
theNumbersAreEvil.Cast<int>();
Console.WriteLine(theNumbers.Sum()); // 108

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

כמובן, כפי שבוודאי יכולתם לנחש, פונקציה זו היא Lazy (כמו כל דבר בLINQ), ולכן גם אם האוסף המקורי שלנו משתנה, האוסף שנוצר ממנו יושפע מכך. (ההסבות תתבצענה רק כאשר נקרא לפונקציה MoveNext של הEnumerator, או לחלופין כשנרוץ על האוסף)

נוכל להשתמש בפונקציה זו גם לצרכים אחרים. נניח שאנחנו עובדים בFramework 3.5, וקיימת פונקציה שמצפה לקבל IEnumerable<Shape>, אבל יש לנו IEnumerable<Circle>. לא נוכל להעביר אוסף זה ישירות (ראו טיפים 36-40), אבל נוכל להשתמש בCast כדי לבצע את המשימה:

1
2
IEnumerable<Circle> circles;
IEnumerable<Shape> shapes = circles.Cast<Shape>();

בנוסף לפונקציה Cast, קיימת גם הפונקציה OfType. פונקציה זו דומה לCast, אלא שהיא מסננת עבורנו רק את האיברים שהם מהטיפוס הגנרי שנעביר לה (בניגוד לCast שזורקת Exception ברגע שהיא לא מצליחה להסב לטיפוס הגנרי):

1
2
3
4
5
IEnumerable homogenousEnumerable =
new object[] {"This is a string", 42, DateTime.Now, "This is another string", 108};
IEnumerable<string> theStrings = homogenousEnumerable.OfType<string>();
// "This is a string", "This is another string".

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

1
2
IEnumerable<Shape> shapes;
IEnumerable<Circle> circles = shapes.OfType<Circle>();

חג פסח ב’ שמח!

שתף