سؤال

في كل مرة أقوم فيها بإنشاء كائن يحتوي على خاصية مجموعة، أعود ذهابًا وإيابًا لمعرفة أفضل طريقة للقيام بذلك؟

  1. الممتلكات العامة مع getter التي تعيد إشارة إلى متغير خاص
  2. طرق GET_OBJLIST صريحة و set_objlist التي تعود وإنشاء كائنات جديدة أو مستنسخة في كل مرة
  3. get_objlist صريح الذي يعيد ienumerator و set_objlist الذي يأخذ ienumerator

هل يحدث فرقًا إذا كانت المجموعة عبارة عن مصفوفة (على سبيل المثال، objList.Clone()) مقابل قائمة؟

إذا كان إرجاع المجموعة الفعلية كمرجع أمرًا سيئًا للغاية لأنه ينشئ تبعيات، فلماذا يتم إرجاع أي خاصية كمرجع؟في أي وقت تقوم فيه بكشف كائن فرعي كمرجع، يمكن تغيير الأجزاء الداخلية لهذا الطفل دون "معرفة" الوالدين ما لم يكن لدى الطفل حدث تغيير في الخاصية.هل هناك خطر تسرب الذاكرة؟

ألا يؤدي الخياران 2 و3 إلى كسر التسلسل؟هل هذا صيد 22 أم أنه يتعين عليك تنفيذ تسلسل مخصص في أي وقت لديك خاصية مجموعة؟

يبدو أن ReadOnlyCollection العام يمثل حلاً وسطًا لطيفًا للاستخدام العام.فهو يلتف على قائمة Ilist ويقيد الوصول إليها.ربما يساعد هذا في حالات تسرب الذاكرة والتسلسل.ومع ذلك لا يزال لديه مخاوف التعداد

ربما يعتمد الأمر فقط.إذا كنت لا تهتم بتعديل المجموعة، فما عليك سوى عرضها كمدخل عام عبر متغير خاص لكل رقم 1.إذا كنت لا تريد أن تقوم البرامج الأخرى بتعديل المجموعة، فالرقم 2 و/أو رقم 3 هو الأفضل.

ضمنيًا في السؤال لماذا يجب استخدام طريقة واحدة على طريقة أخرى وما هي التداعيات على الأمان والذاكرة والتسلسل وما إلى ذلك؟

هل كانت مفيدة؟

المحلول

تعتمد كيفية عرض المجموعة بشكل كامل على كيفية تفاعل المستخدمين معها.

1) إذا كان المستخدمون سيقومون بإضافة وإزالة عناصر من مجموعة كائن ما، فإن خاصية الحصول على المجموعة البسيطة هي الأفضل (الخيار رقم 1 من السؤال الأصلي):

private readonly Collection<T> myCollection_ = new ...;
public Collection<T> MyCollection {
  get { return this.myCollection_; }
}

يتم استخدام هذه الاستراتيجية ل Items المجموعات على WindowsForms وWPF ItemsControl عناصر التحكم، حيث يقوم المستخدمون بإضافة وإزالة العناصر التي يريدون عرضها في عنصر التحكم.تنشر عناصر التحكم هذه المجموعة الفعلية وتستخدم عمليات الاسترجاعات أو مستمعي الأحداث لتتبع العناصر.

يعرض WPF أيضًا بعض المجموعات القابلة للتعيين للسماح للمستخدمين بعرض مجموعة من العناصر التي يتحكمون فيها، مثل ItemsSource الملكية على ItemsControl (الخيار رقم 3 من السؤال الأصلي).ومع ذلك، هذه ليست حالة الاستخدام الشائع.


2) إذا كان المستخدمون سيقرأون فقط البيانات التي يحتفظ بها الكائن، فيمكنك استخدام مجموعة للقراءة فقط، مثل مراوغ مقترح:

private readonly List<T> myPrivateCollection_ = new ...;
private ReadOnlyCollection<T> myPrivateCollectionView_;
public ReadOnlyCollection<T> MyCollection {
  get {
    if( this.myPrivateCollectionView_ == null ) { /* lazily initialize view */ }
    return this.myPrivateCollectionView_;
  }
}

لاحظ أن ReadOnlyCollection<T> يوفر عرضًا مباشرًا للمجموعة الأساسية، لذلك تحتاج إلى إنشاء العرض مرة واحدة فقط.

إذا لم يتم تنفيذ المجموعة الداخلية IList<T>, ، أو إذا كنت تريد تقييد الوصول إلى مستخدمين أكثر تقدمًا، فيمكنك بدلاً من ذلك التفاف الوصول إلى المجموعة من خلال العداد:

public IEnumerable<T> MyCollection {
  get {
    foreach( T item in this.myPrivateCollection_ )
      yield return item;
  }
}

هذا الأسلوب سهل التنفيذ ويوفر أيضًا الوصول إلى جميع الأعضاء دون الكشف عن المجموعة الداخلية.ومع ذلك، فإنه يتطلب أن تظل المجموعة غير معدلة، حيث أن فئات مجموعة BCL ستطرح استثناءً إذا حاولت تعداد مجموعة بعد تعديلها.إذا كان من المحتمل أن تتغير المجموعة الأساسية، فيمكنك إما إنشاء غلاف خفيف يقوم بتعداد المجموعة بأمان، أو إرجاع نسخة من المجموعة.


3) أخيرًا، إذا كنت بحاجة إلى كشف المصفوفات بدلاً من المجموعات ذات المستوى الأعلى، فيجب عليك إرجاع نسخة من المصفوفة لمنع المستخدمين من تعديلها (الخيار رقم 2 من السؤال الأصلي):

private T[] myArray_;
public T[] GetMyArray( ) {
  T[] copy = new T[this.myArray_.Length];
  this.myArray_.CopyTo( copy, 0 );
  return copy;
  // Note: if you are using LINQ, calling the 'ToArray( )' 
  //  extension method will create a copy for you.
}

يجب ألا تعرض المصفوفة الأساسية من خلال إحدى الخصائص، حيث لن تتمكن من معرفة متى يقوم المستخدمون بتعديلها.للسماح بتعديل المصفوفة، يمكنك إما إضافة ما يتوافق معها SetMyArray( T[] array ) الطريقة، أو استخدم مفهرسًا مخصصًا:

public T this[int index] {
  get { return this.myArray_[index]; }
  set {
    // TODO: validate new value; raise change event; etc.
    this.myArray_[index] = value;
  }
}

(وبطبيعة الحال، من خلال تنفيذ مفهرس مخصص، سوف تقوم بتكرار عمل فئات BCL :)

نصائح أخرى

عادةً ما أختار هذا، وهو أداة عامة تُرجع System.Collections.ObjectModel.ReadOnlyCollection:

public ReadOnlyCollection<SomeClass> Collection
{
    get
    {
         return new ReadOnlyCollection<SomeClass>(myList);
    }
}

والأساليب العامة على الكائن لتعديل المجموعة.

Clear();
Add(SomeClass class);

إذا كان من المفترض أن يكون الفصل مستودعًا لأشخاص آخرين ليعبثوا به، فأنا فقط أعرض المتغير الخاص وفقًا للطريقة رقم 1 لأنه يحفظ كتابة واجهة برمجة التطبيقات الخاصة بك، لكنني أميل إلى الابتعاد عن ذلك في كود الإنتاج.

إذا كنت تتطلع ببساطة إلى كشف مجموعة على المثيل الخاص بك، فإن استخدام getter/setter لمتغير عضو خاص يبدو أنه الحل الأكثر منطقية بالنسبة لي (الخيار الأول المقترح).

لماذا تقترح أن استخدام ReadOnlyCollection(T) هو حل وسط؟إذا كنت لا تزال بحاجة إلى الحصول على إشعارات التغيير التي تم إجراؤها على قائمة Ilist الأصلية المغلفة، فيمكنك أيضًا استخدام ملف ReadOnlyObservableCollection(T) لتغليف مجموعتك.هل سيكون هذا أقل من حل وسط في السيناريو الخاص بك؟

أنا مطور جافا ولكن أعتقد أن هذا هو نفسه بالنسبة لـ C#.

لا أكشف أبدًا عن خاصية مجموعة خاصة لأن أجزاء أخرى من البرنامج يمكن أن تغيرها دون ملاحظة الوالدين، لذلك في طريقة getter أقوم بإرجاع مصفوفة تحتوي على كائنات المجموعة وفي طريقة الضبط أقوم باستدعاء clearAll() على المجموعة ثم addAll()

لا يزال لدى ReadOnlyCollection عيب وهو أن المستهلك لا يستطيع التأكد من أن المجموعة الأصلية لن يتم تغييرها في وقت غير مناسب.بدلا من ذلك يمكنك استخدام مجموعات غير قابلة للتغيير.إذا كنت بحاجة إلى إجراء تغيير، فبدلاً من تغيير النسخة الأصلية، سيتم إعطاؤك نسخة معدلة.طريقة تنفيذه تنافسية مع أداء المجموعات القابلة للتغيير.أو من الأفضل أن لا تضطر إلى نسخ النسخة الأصلية عدة مرات لإجراء عدد من التغييرات المختلفة (غير المتوافقة) بعد ذلك على كل نسخة.

أوصي باستخدام الجديد IReadOnlyList<T> و IReadOnlyCollection<T> واجهات لعرض مجموعة (يتطلب .NET 4.5).

مثال:

public class AddressBook
{
    private readonly List<Contact> contacts;

    public AddressBook()
    {
        this.contacts = new List<Contact>();
    }

    public IReadOnlyList<Contact> Contacts { get { return contacts; } }

    public void AddContact(Contact contact)
    {
        contacts.Add(contact);
    }

    public void RemoveContact(Contact contact)
    {
        contacts.Remove(contact);
    }
}

إذا كنت بحاجة إلى ضمان عدم إمكانية التلاعب بالمجموعة من الخارج، ففكر في الأمر ReadOnlyCollection<T> أو المجموعات غير القابلة للتغيير الجديدة.

يتجنب باستخدام الواجهة IEnumerable<T> لفضح جمع.لا تحدد هذه الواجهة أي ضمان بأن التعدادات المتعددة تعمل بشكل جيد.إذا كان IEnumerable يمثل استعلامًا، فسيقوم كل تعداد بتنفيذ الاستعلام مرة أخرى.لا يعرف المطورون الذين يحصلون على مثيل IEnumerable ما إذا كان يمثل مجموعة أو استعلامًا.

المزيد حول هذا الموضوع يمكن قراءته على هذا صفحة ويكي.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top