ראינו לפני פעמיים מימוש למיפוי חד חד ערכי בין מפתחות לערכים.
למרבה הצער, רוב המיפויים בעולם הם דווקא לא חח”ע. במקום זאת אפשר להשתמש במיפוי קצת אחר:
|
|
איך נממש את זה?
נחזיק שני Dictionaryים – אחד מהKey לValue, השני מהValueלאוסף של Key, ונדאג שכל הפונקציות של IDictionaryיעברו דרך שני הDictionaryים:
|
|
כמה דברים שאפשר לראות מכאן:
- יש הרבה מאוד מתודות לממשק IDictionary. לא הייתי ממליץ לאף אחד לממש אותו בעצמו (למרות שרוב המימושים הם שורה אחת-שתיים בעטיפה שכתובה פה)
- המימושים הכבדים, באופן לא מפתיע, הם של הפונקציות Add, Remove והIndexer, שמבצעים מניפולציות על הDictionaryים הפנימיים
- זה גם משהו שעלה בפעם הקודמת שכתבתי על המפה החד חד ערכית – מאחר ויש פה שני Indexerים, יש שתי אפשרויות:
- הראשונה היא לממש את הממשקים Implicitly, ואז אם שני הטיפוסים שווים, אי אפשר לקרוא לIndexer ישירות (אלא רק דרך הכנסה למשתנה מסוג הממשק המתאים, ראו גם טיפ מספר 82)
- השנייה, היא לממש לפחות את אחד הממשקיםExplicitly, ואז תמיד אפשר לקרוא לIndexerהנותר ישירות. לIndexer שמימשנו Explicitlyניתן יהיה לקרוא רק דרך הכנס למשתנה מהסוג המתאים. כאן בחרתי בדרך זו, כי נראה לי שיותר חשוב לגשת לערך לפי מפתח, לעומת הכיוון השני
- שימו לב שיש פה כמה מימושים לGetEnumerator, למעשה יש את המימוש הלא גנרי של IEnumerable שמפנה למימוש של הDictionary, יש מימוש של הLookup שמפנה לDictionary ההפוך. היה אפשר לחשוב שאם נבצע מעבר על הCollection עם הטיפוס המתאים, הקומפיילר יפנה אותנו לפרמטר הגנרי, למשל שהקוד הבא יעבוד:1234foreach (IGrouping<string, int> group in invertible){// ...}
למעשה, זה לא קורה, והקוד לא עובד, מאחר ונקראת הפונקציה הלא גנרית של IEnumerable. הדרך לפתור את הבעיה היא לעשות משהו כזה:123456IEnumerable<IGrouping<string,int>> invertibleEnumerable = invertible;foreach (IGrouping<string, int> group in invertibleEnumerable){ // ...}
- שימו לב שכדי לממשIEnumerable<IGrouping<TValue, TKey>>, נאלצנו לכתוב מחלקת IGrouping משלנו, מאחר ואין כזאת שהיא public בFramework. בנוסף, היינו צריכים לעשות Select במימוש של GetEnumerator כדי שיחזרו טיפוסים מהסוג המתאים.
כעקרון זה לא הכי שימושי, אבל לדעתי זה מלמד כמה דברים.
המשך יום הפיך טוב!