301. IReadOnlyList{T}, IReadOnlyDictionary{TKey,TValue}

[מבוסס על הכתבה הזאת]

הפעם יהיה טיפ מהעתיד – אנחנו נדבר על יכולות שהוסיפו בFramework 4.5.

עוד בFramework 1.0, התלוננו מתכנתים רבים על כך שאין ממשקים של ICollection או IDictionary שהם Readonly, כלומר חסרי הפונקציות Add, Remove ופעולות שמשנות את הCollection.

התגובה של מיקרוסופט הייתה פשוטה: מבחינתנו כל Collection דיפולטית הוא Readonly. כלומר, אם אתם רוצים לקרוא לאחת הפונקציות שמשנות את הCollection (למשל Add או Remove) – תתכוננו לחטוף Exception, אלא אם כן אתם יודעים שהCollection הוא לא ReadOnly. (ותוכלו לבדוק אם הוא ReadOnly לפי הProperty של ICollection בשם IsReadOnly)

אם נסתכל בהערות של ICollection, למשל, נראה את הדבר הבא:

Throws:
System.NotSupportedException: The System.Collections.Generic.ICollection is read-only.

כמובן, הדבר הציק למתכנתים, אבל הם הושקטו ע”י מיקרוסופט, כשהוציאו את הטיפוס ReadOnlyCollection, שמקל על המימוש של Collection שהוא ReadOnly, אבל יותר מזאת: הוא Type-safed! יחי הGenerics. (ראו גם טיפ מספר 4)

הנושא קיבל תשומת לב מחדש, כשבFramework 4.0 הציגו את הCovariance ואת הContravariance. (ראו גם טיפים מספר 36-40)

במילים פשוטות, היכולת הזאת נתנה אפשרות לכתוב את הקוד הבא:

1
2
IEnumerable<Triangle> triangles = new List<Triangle>();
IEnumerable<Shape> shapes = triangles;

הכל טוב ויפה, אבל לפעמים יש לנו מבנה שהוא טיפה יותר מורכב מIEnumerable, למשל IList שיש לו Indexer לפי אינדקס, או IDictionary, שמאפשר לנו לקבל ערך לפי מפתח. (ולבדוק מי המפתחות/הערכים)

עם זאת, מיקרוסופט עדיין לא הוסיפו ממשקים כאלה לFramework 4.0.

כפי שוודאי יכולתם לנחש, לFramework 4.5, כן נוספו ממשקים בשמות IReadOnlyDictionary<TKey, TValue> ו IReadOnlyList<T>.


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

הרי ראינו שמיקרוסופט לא היו להוטים להוסיף אותם אי פעם לFramework.

ובכן, מה שקרה זה שהחל מFramework 4.5, יש תמיכה של מיקרוסופט במשהו שנקרא Windows RT (Realtime). בגדול זהו API שהולך להחליף את הWindows 32 API הוותיק. בין השאר מה שהולך לקרות זה שהולכת להיות תמיכה שקופה בתקשורת בין שפות, למשל C# יוכל לדבר עם JavaScript ועם COM בצורה מאוד אלגנטית. הWindows RT ידע להמיר למשל את הממשקים המתאימים מCOM לממשקים המתאימים ב.net.

(אם למישהו בא לתקן/לדייק, הוא מוזמן)

מסתבר שבCOM יש ממשקים בשם IVectorView<V> וIMapView<S,U>, שמייצגים למעשה Dictionary וList שהם ReadOnly. לכן, בשביל ה Interoperability, מיקרסופט היו חייבים להוסיף ממשקים שקולים גם ב.net.

אוקיי, עכשיו בואו נסתכל על הממשקים:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface IReadOnlyList<out T> : IEnumerable<T>
{
int Count { get; }
T this[int index] { get; }
}
public interface IReadOnlyDictionary<TKey, TValue> :
IEnumerable<KeyValuePair<TKey, TValue>>
{
int Count { get; }
TValue this[TKey key] { get; }
IEnumerable<TKey> Keys { get; }
IEnumerable<TValue> Values { get; }
bool ContainsKey(TKey key);
bool TryGetValue(TKey key, out TValue value);
}

יש כמה דברים מעניינים להגיד:

דבר ראשון: היה אפשר לחשוב ש IList<T>וIDictionary<TKey, TValue> ירשו מממשקים אלה בהתאמה, אך לא כך המצב. הסיבה לכך היא שאם זה היה ככה – לא הייתה תאימות לאחור, מאחר וכשמממשים ממשק באופן Explicitly (ראו טיפ מספר 82-83), מציינים עבור כל מתודה לאיזה ממשק היא שייכת. והמתודות שנמצאות בממשקים שהם ReadOnly כבר לא תהינה שייכות לממשקים שהם לא ReadOnly. כלומר, ההזדמנות היחידה לדאוג לכך שהממשקים ירשו מהממשקים שהם ReadOnly, הייתה בFramework 2.0, אבל הם פספסו את ההזדמנות.

דבר שני: היה אפשר לחשוב שיהיה ממשק בשם IReadOnlyCollection<out T> שיש לו… Property בשם Count. הדבר הוא נשקל, אבל לבסוף ויתרו עליו. הסיבה היא כזאת: מבחינתם, ברגע שאתה יוצר ממשק חדש, אתה מתחיל עם מינוס אלף נקודות. כעת, מכאן צריך לתת כמה שיותר נימוקים לקיום הממשק כדי לצבור יותר ויותר נקודות.

אם אין לך מספיק נקודות, כנראה אין סיבה מספיק טובה לממשק. ואכן זה כך. סביר להניח שאם נרצה Property בשם Count, זה מאחר והוא יכול להשתנות, כלומר אפשר להוסיף איברים לCollection. מאחר וזה כך, אפשר לממש כבר את הממשק ICollection. ואנחנו כבר יודעים (ראו גם טיפ מספר 15) שהExtension Method של Count() יודע לגשת לProperty הזה כאשר אנחנו מממשים ICollection.

אפשר להתווכח על זה, אבל כנראה שממשק עם Property בשם Count לא היה מועיל לנו יותר מדי.

המשך יום מהעתיד טוב.

שתף