שאלה:
בנינו מחלקה שמכילה כמה סוגים של Collections ואנחנו חושפים אותם החוצה ב-Properties.
ה-Collections האלו הם Private למחלקה עצמה, והחוצה אנחנו רוצים לחשוף רק אפשרויות לעבור עליהם בקריאה בלבד.
ההוספה והמחיקה לאוספים הפנימיים הוא תוצאה של אלגוריתמים פנימיים, למשל אנחנו רוצים שיהיה אפשר להוסיף רק דרך Add שלנו כדי שנוכל לעלות Events.
הבעיה היא שב-Get של ה-Property אנחנו מחזירים Reference Type ואפשר לשנות מחוץ למחלקה את ה-Collections בלי לעבור דרך האלגורתימים.
יש לזה פתרון בדוט נט?
תשובה:
בואו נבנה מחלקה לדוגמה שחושפת שלושה סוגי אוספים: (גם באנגלית: Collections)
1. ArrayList - שהשתמנו בדוט נט 1.1
2. List ג'נארי של דוט נט 2.0
3. StringCollection - שזה Collection יעודי שעבר אופטיזמציה לטפל רק במחרוזות
ככה נראית המחלקה:
אפשר לראות שיש אוספים שהם Private בתוך המחלקה.
למשל, נוכל ליצור מתודות (גם באנגלית: Methods) שחושפות מחוץ למחלקה פונקציונליות של הוספה, מחיקה ועדכון ורק דרכן נרצה שיהיה אפשר לשנות את האוספים.
למשל ככה היינו ניגשים למחלקה:
ואם היינו רוצים למשל לעבור על ה-List הגנ'ארי היינו עושים:
אז מה הבעיה עם המחלקה בצורה הנוכחית?
אנחנו יכולים לשנות את האוסף כי קיבלנו אותו Reffernce Type.
בואו נביט על הקוד הבא:
אנחנו ניגשים ישירות למתודות שחושף כל אוסף ואוסף ומשנים אותו. למרות שרצינו לחשוף אותם כקריאה בלבד.
מה מסתבר? עם הפריימוורק מגיעים סוגי אוספים שהם קריאה בלבד.
האוספים האלו הם עותקים שרק אפשר לעבור להם על התוכן, אבל לא ניתן להוסיף, למחוק או בכלל לשנות אותם.
נשנה את המאפיינים שלנו שיחזירו אוספים לקריאה בלבד.
על List ג'נארי יושבת מתודה בשם List<T>.AsReadOnly שמחזירה אוסף לקריאה בלבד מסוג ReadOnlyCollection ג'נארי.
במקום להחזיר List ג'נארי נחזיר ReadOnlyCollection ג'נארי.
הופך ל:
ועכשיו נוכל להוסיף רק דרך המתודת Add שלנו.
ב-ArrayList יש מתודה סטטית בשם ArrayList.ReadOnly שמקבלת ArrayList ומחזירה ArrayList שהמימוש הפנימי שלו יזרוק שגיאה כאשר ינסו לשנות אותו. נדאג להחזיר רק ArrayList שהוא קריאה בלבד מהמאפיינים.
ואם ננסה להוסיף לאוסף ישירות בלי לעבור דרך מתודת ה-Add נקבל שגיאת זמן ריצה.
בנושא ה-StringCollection אנחנו קצת בבעיה כי אין לנו שום טיפוס מקביל ל-StringCollection שהוא קריאה בלבד.
יש לנו שתי אפשרויות: הראשונה היא להמיר אותו ל-List ג'נארי ולהחזיר ReadOnlyCollection ג'נארי.
האפשרות השנייה היא להשתמש ב-ArrayList.ReadOnly. מה מסתבר? ה-ArrayList.ReadOnly גם יכול לקבל סתם IList כללי ויחזיר IList שהוא קריאה בלבד.
הופך ל:
ואם ננסה להריץ את הקוד הבא נקבל שגיאת זמן ריצה.
An unhandled exception of type 'System.NotSupportedException' occurred in mscorlib.dll
Additional information: Collection is read-only.
בזמן ריצה נוכל לבדוק לכל אחד מהאוספים הללו IsReadOnly כדי לראות אם האוסף שבידינו הוא עותק לקריאה בלבד.