views:

3710

answers:

7

I have the following class:

class SampleClass
{
   private ArrayList mMyList;

   SampleClass()
   {
       // Initialize mMyList
   }

   public ArrayList MyList
   {
       get { return mMyList;}
   }
}

I want users to be able to get mMyList which is why i exposed the "get" via a property however i don't want changes they make to the object (ie. MyList.Add(new Class());) to make its way back into my class.

I guess i can return a copy of the object but that may be slow and i'm looking for a way that will provide a compile-time error informing the user that they shouldn't expect to be able to modify the returned value from the Property.

Is this possible?

+7  A: 

With an ArrayList you are fairly limited because there is no readonly non-generic collection class in the BCL. The quick and dirty solution is to return a type of IEnumerable.

   public IEnumerable MyList
   {
       get { return mMyList;}
   }

This won't actually prevent someone from casting to ArrayList but it won't allow edits by default either

You can return an effectively readonly list by calling ArrayList.ReadOnly. However it's return type is an ArrayList so the user would still be able to compile with .Add but it would produce a runtime error.

JaredPar
A: 

You should wrap the return value in a read only collection.

Mehrdad Afshari
+7  A: 

Use the ArrayList.ReadOnly() method to construct and return a read-only wrapper around the list. it won't copy the list, but simply make a read-only wrapper around it. In order to get compile-time checking, you probably want to return the read-only wrapper as IEnumerable as @Jared suggests.

public IEnumerable MyList
{
     get { return ArrayList.ReadOnly(mMyList); }
}

A collection that is read-only is simply a collection with a wrapper that prevents modifying the collection. If changes are made to the underlying collection, the read-only collection reflects those changes.

This method is an O(1) operation.

Reference

tvanfosson
I'm against this solution because it turns what should be a compile time error into a runtime error.
JaredPar
-1, OP: "I'm looking for a way that will provide a **compile-time** error"
Daniel LeCheminant
Without copying to a ReadOnlyCollection -- which he also said he wanted to avoid -- I choose enforcing read-only over pretending to be readonly. Doing both is actually probably better.
tvanfosson
A: 

Just make the property as Sealed (or NotInheritable in VB.NET) and readonly, like this:

   public sealed readonly ArrayList MyList
   {
       get { return mMyList;}
   }

Also don't forget to make the backing field as readonly:

   private readonly ArrayList mMyList;

But in order to initialize the value of mMyList, you must initialize it ONLY in the constructor, as in this example:

public SampleClass()
{
   // Initialize mMyList
   mMyList = new ArrayList();
}
eriawan
+3  A: 

Just to expand on JaredPar's answer. As he said, returning the actual backing field is not completely safe, since the user is still able to dynamically cast the IEnumerable back to an ArrayList.

I would write something like this to be sure no modifications to the original list are made:

public IEnumerable MyList
{
    get
    {
         foreach (object o in mMyList)
             yield return o;
    }
}

Then again, I would probabily also use a generic list (IEnumerable<T>) to be completely type safe.

Tom Lokhorst
*This* is the right one -- wish I could vote twice. Any problem with not calling `yield break;` after the loop?
Jay
@Jay: There's no need for an explicit `yield break;` at the after the loop. The end of the method implies the end of the `Enumerator` (and `MoveNext` will correctly return `false`).
Tom Lokhorst
+4  A: 

Use a ReadOnlyCollection.

return new ReadOnlyCollection<object>(mMyList).

You can also make the ReadOnlyCollection a field, and it will automatically reflect changes in the underlying list. This is the most efficient solution.

If you know that mMyList only contains one type of object (eg, it has nothing but DateTimes), you can return a ReadOnlyCollection<DateTime> (or some other type) for additional type safety. If you're using C# 3, you can simply write

return new ReadOnlyCollection<DateTime>(mMyList.OfType<DateTime>().ToArray())

However, this will not automatically update with the underlying list, and is also less efficient (It will copy the entire list). The best option is to make mMyList a generic List<T> (eg, a List<DateTime>)

SLaks
+2  A: 

List(T).AsReadOnly Method =)

Svish