DCSIMG
Question from Tapuz .Net forum: Exposing a Read-only collection from a class - Justin myJustin = new Justin( Expriences.Current );

Question from Tapuz .Net forum: Exposing a Read-only collection from a class

שאלה:

בנינו מחלקה שמכילה כמה סוגים של Collections ואנחנו חושפים אותם החוצה ב-Properties.

ה-Collections האלו הם Private למחלקה עצמה, והחוצה אנחנו רוצים לחשוף רק אפשרויות לעבור עליהם בקריאה בלבד.

ההוספה והמחיקה לאוספים הפנימיים הוא תוצאה של אלגוריתמים פנימיים, למשל אנחנו רוצים שיהיה אפשר להוסיף רק דרך Add שלנו כדי שנוכל לעלות Events.

 הבעיה היא שב-Get של ה-Property אנחנו מחזירים Reference Type ואפשר לשנות מחוץ למחלקה את ה-Collections בלי לעבור דרך האלגורתימים.

יש לזה פתרון בדוט נט?

 

תשובה:

בואו נבנה מחלקה לדוגמה שחושפת שלושה סוגי אוספים: (גם באנגלית: Collections)

1. ArrayList - שהשתמנו בדוט נט 1.1

2. List ג'נארי של דוט נט 2.0

3. StringCollection  - שזה Collection יעודי שעבר אופטיזמציה לטפל רק במחרוזות

ככה נראית המחלקה:

    public class myClassHasCollections

    {

        private ArrayList _dotNet11Collection = new ArrayList();

        public ArrayList DotNet11Collection

        {

            get { return _dotNet11Collection; }

        }

 

        private List<SomeClass> _dotNet20Collection = new List<SomeClass>();

        public List<SomeClass> DotNet20Collection

        {

            get { return _dotNet20Collection; }

        }

 

        private StringCollection _dotNet20SpeaclizedStringCollection = new StringCollection();

        public StringCollection DotNet20SpeaclizedStringCollection

        {

            get { return _dotNet20SpeaclizedStringCollection; }

        }

    }

 אפשר לראות שיש אוספים שהם Private בתוך המחלקה.

למשל, נוכל ליצור מתודות (גם באנגלית: Methods) שחושפות מחוץ למחלקה פונקציונליות של הוספה, מחיקה ועדכון ורק דרכן נרצה שיהיה אפשר לשנות את האוספים.

    public class myClassHasCollections

    {

        private ArrayList _dotNet11Collection = new ArrayList();

        public ArrayList DotNet11Collection

        {

            get { return _dotNet11Collection; }

        }

 

        public void AddToDotNet11Collection(object objToAdd)

        {

            _dotNet11Collection.Add(objToAdd);

        }

 

 

        private List<SomeClass> _dotNet20Collection = new List<SomeClass>();

        public List<SomeClass> DotNet20Collection

        {

            get { return _dotNet20Collection; }

        }

 

        public void AddToDotNet20Collection(SomeClass someClassToAdd)

        {

            _dotNet20Collection.Add(someClassToAdd);

        }

 

 

        private StringCollection _dotNet20SpeaclizedStringCollection = new StringCollection();

        public StringCollection DotNet20SpeaclizedStringCollection

        {

            get { return _dotNet20SpeaclizedStringCollection; }

        }

 

        public void AddToDotNet20SpeaclizedStringCollection(string StringToAdd)

        {

            _dotNet20SpeaclizedStringCollection.Add(StringToAdd);

        }

    }

  

למשל ככה היינו ניגשים למחלקה:

            myClassHasCollections collections = new myClassHasCollections();

 

            collections.AddToDotNet11Collection(new object());

            collections.AddToDotNet20Collection(new SomeClass());

            collections.AddToDotNet20SpeaclizedStringCollection(www.JustinAngel.Net);

 

ואם היינו רוצים למשל לעבור על ה-List הגנ'ארי היינו עושים:

            foreach (SomeClass someClass in collections.DotNet20Collection)

            {

                Console.WriteLine(someClass.Key);

            }

 

אז מה הבעיה עם המחלקה בצורה הנוכחית? 
אנחנו יכולים לשנות את האוסף כי קיבלנו אותו Reffernce Type.

בואו נביט על הקוד הבא:

            collections.DotNet11Collection.Add(new object());

            collections.DotNet20Collection.Add(new SomeClass());

            collections.DotNet20SpeaclizedStringCollection.Add("www.JustinAngel.Net");

אנחנו ניגשים ישירות למתודות שחושף כל אוסף ואוסף ומשנים אותו. למרות שרצינו לחשוף אותם כקריאה בלבד.

 

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

נשנה את המאפיינים שלנו שיחזירו אוספים לקריאה בלבד.

 

על List ג'נארי יושבת מתודה בשם List<T>.AsReadOnly שמחזירה אוסף לקריאה בלבד מסוג ReadOnlyCollection ג'נארי.

במקום להחזיר List ג'נארי נחזיר ReadOnlyCollection ג'נארי.

        private List<SomeClass> _dotNet20Collection = new List<SomeClass>();

        public List<SomeClass> DotNet20Collection

        {

            get { return _dotNet20Collection; }

        }

הופך ל:

        private List<SomeClass> _dotNet20Collection = new List<SomeClass>();

        public ReadOnlyCollection<SomeClass> DotNet20Collection

        {

            get { return _dotNet20Collection.AsReadOnly(); }

        }

 

        public void AddToDotNet20Collection(SomeClass someClassToAdd)

        {

            _dotNet20Collection.Add(someClassToAdd);

        }

 

ועכשיו נוכל להוסיף רק דרך המתודת Add שלנו.

 

ב-ArrayList יש מתודה סטטית בשם ArrayList.ReadOnly שמקבלת ArrayList ומחזירה ArrayList שהמימוש הפנימי שלו יזרוק שגיאה כאשר ינסו לשנות אותו. נדאג להחזיר רק ArrayList שהוא קריאה בלבד מהמאפיינים.

        private ArrayList _dotNet11Collection = new ArrayList();

        public ArrayList DotNet11Collection

        {

            get

            {

                return ArrayList.ReadOnly(_dotNet11Collection);

            }

        }

ואם ננסה להוסיף לאוסף ישירות בלי לעבור דרך מתודת ה-Add נקבל שגיאת זמן ריצה.

            collections.DotNet11Collection.Add(new object());

 

בנושא ה-StringCollection אנחנו קצת בבעיה כי אין לנו שום טיפוס מקביל ל-StringCollection שהוא קריאה בלבד.
יש לנו שתי אפשרויות: הראשונה היא להמיר אותו ל-List ג'נארי ולהחזיר ReadOnlyCollection ג'נארי.
האפשרות השנייה היא להשתמש ב-ArrayList.ReadOnly. מה מסתבר? ה-ArrayList.ReadOnly גם יכול לקבל סתם IList כללי ויחזיר IList שהוא קריאה בלבד.

        private StringCollection _dotNet20SpeaclizedStringCollection = new StringCollection();

        public StringCollection DotNet20SpeaclizedStringCollection

        {

            get { return _dotNet20SpeaclizedStringCollection; }

        }

הופך ל:

        private StringCollection _dotNet20SpeaclizedStringCollection = new StringCollection();

        public IList DotNet20SpeaclizedStringCollection

        {

            get

            {

                return ArrayList.ReadOnly(_dotNet20SpeaclizedStringCollection);

            }

        }

 

        public void AddToDotNet20SpeaclizedStringCollection(string StringToAdd)

        {

            _dotNet20SpeaclizedStringCollection.Add(StringToAdd);

        }

ואם ננסה להריץ את הקוד הבא נקבל שגיאת זמן ריצה.

            collections.DotNet20SpeaclizedStringCollection.Add(www.JustinAngel.Net);

An unhandled exception of type 'System.NotSupportedException' occurred in mscorlib.dll

Additional information: Collection is read-only.

 

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

 

Published Monday, June 04, 2007 12:19 PM by Justin-Josef Angel [MVP]

Comments

No Comments