Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to expose a collection property? [closed]

Every time I create an object that has a collection property I go back and forth on the best way to do it?

  1. public property with a getter that returns a reference to private variable
  2. explicit get_ObjList and set_ObjList methods that return and create new or cloned objects every time
  3. explicit get_ObjList that returns an IEnumerator and a set_ObjList that takes IEnumerator

Does it make a difference if the collection is an array (i.e., objList.Clone()) versus a List?

If returning the actual collection as a reference is so bad because it creates dependencies, then why return any property as a reference? Anytime you expose an child object as a reference the internals of that child can be changed without the parent "knowing" unless the child has a property changed event. Is there a risk for memory leaks?

And, don't options 2 and 3 break serialization? Is this a catch 22 or do you have to implement custom serialization anytime you have a collection property?

The generic ReadOnlyCollection seems like a nice compromise for general use. It wraps an IList and restricts access to it. Maybe this helps with memory leaks and serialization. However it still has enumeration concerns

Maybe it just depends. If you don't care that the collection is modified, then just expose it as a public accessor over a private variable per #1. If you don't want other programs to modify the collection then #2 and/or #3 is better.

Implicit in the question is why should one method be used over another and what are the ramifications on security, memory, serialization, etc.?

like image 832
jedatu Avatar asked Aug 29 '08 18:08

jedatu


3 Answers

How you expose a collection depends entirely on how users are intended to interact with it.

1) If users will be adding and removing items from an object's collection, then a simple get-only collection property is best (option #1 from the original question):

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

This strategy is used for the Items collections on the WindowsForms and WPF ItemsControl controls, where users add and remove items they want the control to display. These controls publish the actual collection and use callbacks or event listeners to keep track of items.

WPF also exposes some settable collections to allow users to display a collection of items they control, such as the ItemsSource property on ItemsControl (option #3 from the original question). However, this is not a common use case.


2) If users will only be reading data maintained by the object, then you can use a readonly collection, as Quibblesome suggested:

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_;
  }
}

Note that ReadOnlyCollection<T> provides a live view of the underlying collection, so you only need to create the view once.

If the internal collection does not implement IList<T>, or if you want to restrict access to more advanced users, you can instead wrap access to the collection through an enumerator:

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

This approach is simple to implement and also provides access to all the members without exposing the internal collection. However, it does require that the collection remain unmodfied, as the BCL collection classes will throw an exception if you try to enumerate a collection after it has been modified. If the underlying collection is likely to change, you can either create a light wrapper that will enumerate the collection safely, or return a copy of the collection.


3) Finally, if you need to expose arrays rather than higher-level collections, then you should return a copy of the array to prevent users from modifying it (option #2 from the orginal question):

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.
}

You should not expose the underlying array through a property, as you will not be able to tell when users modify it. To allow modifying the array, you can either add a corresponding SetMyArray( T[] array ) method, or use a custom indexer:

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

(of course, by implementing a custom indexer, you will be duplicating the work of the BCL classes :)

like image 135
Emperor XLII Avatar answered Nov 01 '22 19:11

Emperor XLII


I usually go for this, a public getter that returns System.Collections.ObjectModel.ReadOnlyCollection:

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

And public methods on the object to modify the collection.

Clear();
Add(SomeClass class);

If the class is supposed to be a repository for other people to mess with then I just expose the private variable as per method #1 as it saves writing your own API, but I tend to shy away from that in production code.

like image 30
Quibblesome Avatar answered Nov 01 '22 20:11

Quibblesome


ReadOnlyCollection still has the disadvantage that the consumer can't be sure that the original collection won't be changed at an inopportune time. Instead you can use Immutable Collections. If you need to do a change then instead changing the original you are being given a modified copy. The way it is implemented it is competitive with the performance of the mutable collections. Or even better if you don't have to copy the original several times to make a number of different (incompatible) changes afterwards to each copy.

like image 1
Johannes Avatar answered Nov 01 '22 19:11

Johannes