views:

1653

answers:

5

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

A: 

If you're simply looking to expose a collection on your instance, then using a getter/setter to a private member variable seems like the most sensible solution to me (your first proposed option).

Ryan Duffield
+3  A: 

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.

Quibblesome
This creates new ReadOnlycollection on each read of Collection property, that can be very resource intensive
Ivan
Aye, Emperor XLII improves the premise in the above example he posted.
Quibblesome
A: 

I'm a java developer but i think this is the same for c#.

I never expose a private collection property because other parts of the program can change it without parent noticing, so that in the getter method i return an array whith the objects of the collection and in the setter method i call a clearAll() over the collection and then an addAll()

Telcontar
+17  A: 

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 Quarrelsome 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 :)

Emperor XLII
Please note that arrays cause boxing and unboxing of objects which is very resource intensive
Brettski
If the array is strongly typed (i.e. int[], long[], etc), accessing elements will not cause boxing. This would only happen if you were using an object[] to store value types (i.e. object[] a = new { 1, 2, 3 }).
Emperor XLII
Ah, fair point, you've improved my suggestion. Nice one! :)
Quibblesome
Implementing the first method you give in solution 2 will cause an "A field initializer cannot reference the non-static field, method, or property" Error, Explained by http://stackoverflow.com/questions/923343/c-error-class-delegate-field-initializer-cannot-reference-the-non-static. By NOT declaring myPrivateCollectionView_ as readonly, it can be set the first time the MyCollection property is used. This is also more efficient, as myPrivateCollectionView_ is only created and maintained if it is actually needed.
jphofmann
@jphofmann: Yes, avoiding obvious compilation errors, even in purely illustrative code, is a good idea :) I've updated the first example code for #2 with your point about lazy initialization of the view.
Emperor XLII
+1  A: 

Why do you suggest using ReadOnlyCollection(T) is a compromise? If you still need to get change notifications made on the original wrapped IList you could also use a ReadOnlyObservableCollection(T) to wrap your collection. Would this be less of a compromise in your scenario?

jpierson