Question

I have an ICollection<T> called foos in my class which I want to expose as read-only (see this question). I see that the interface defines a property .IsReadOnly, which seems appropriate... My question is this: how do I make it obvious to the consumer of the class that foos is read-only?

I don't want to rely on them remembering to query .IsReadOnly before trying a not-implemented method such as .Add(). Ideally, I would like to expose foos as a ReadOnlyCollection<T>, but it does not implement IList<T>. Should I expose foo via a method called, for example, GetReadOnlyFooCollection rather than via a property? If so, would this not confuse someone who then expects a ReadOnlyCollection<T>?

This is C# 2.0, so extension methods like ToList() are not available...

Was it helpful?

Solution

I seem to have settled on returning IEnumerable with the objects cloned.

public IEnumerable<Foose> GetFooseList() {
   foreach(var foos in Collection) {
     yield return foos.Clone();
   }
}
  • requires a Clone method on Foos.

This allows no changes in the collection. Remember that ReadonlyCollection is "leaky" since the objects inside it can be changed as mentioned in a link in another post.

OTHER TIPS

You can make "foos" a ReadOnlyCollection like this:

ReadOnlyCollection<T> readOnlyCollection = foos.ToList<T>().AsReadOnly();

Then you can expose it as a property of your class.

EDIT:

    class FooContainer
    {
        private ICollection<Foo> foos;
        public ReadOnlyCollection<Foo> ReadOnlyFoos { get { return foos.ToList<Foo>().AsReadOnly();} }

    }

Note: You should remember that once you get the ReadOnlyFoos collection is no longer "synchronized" with your foos ICollection. See the thread you referenced.

Since the question was written, .NET 4.0 has added an IReadOnlyCollection<T> interface; it would probably be good to use that as the declared return type.

That does, however, leave open the question of what type of instance to return. One approach would be to clone all the items in the original collection. Another would be to always return a read-only wrapper. A third would be to return the original collection if it implements IReadOnlyCollection<T>. Each approach will be the best one in certain contexts, but will be less than ideal (or perhaps downright dreadful) in others. Unfortunately, Microsoft provides no standard means by which a question can be asked two very important questions:

  1. Do you promise to always and forevermore contain the same items as you do right now?

  2. Can you safely be exposed directly to code which is not supposed to modify your contents.

Which style of wrapping is appropriate would depend upon what the client code is expecting to do with the thing it receives. Some scenarios to be avoided:

  1. An object was supplied of a type that the client would recognize as immutable, but rather than being returned directly it is duplicated, using a type that the client doesn't recognize as immutable. Consequently, the client is compelled to duplicate the collection again.

  2. An object was supplied of a type that the client would recognize as immutable, but before being returned it is wrapped in such a fashion that the client can't tell whether the collection is immutable or not, and thus is compelled to duplicate.

  3. An object of mutable type which is not supposed to be mutated is supplied by a client (cast to a read-only interface). It is then exposed directly to another client which determines that it is a mutable type and proceeds to modify it.

  4. A reference to a mutable collection is received and is encapsulated in a read-only wrapper before being returned to a client that needs an immutable object. The method that returned the collection promised that it is immutable, and thus the client declined to make its own defensive copy. The client is then ill-prepared for the possibility that the collection might change.

There isn't really any particularly "safe" course of action an object can take with collections that it receives from some clients and needs to expose to others. Always duplicating everything is in many circumstances the safest course of action, but it can easily result in situations where a collection which shouldn't need to be duplicated at all ends up getting duplicated hundreds or thousands of times. Returning references as received can often be the most efficient approach, but it can also be semantically dangerous.

I wish Microsoft would add a standard means by which collections could be asked the above questions or, better yet, be asked to produce an immutable snapshot of their current state. An immutable collection could return an immutable snapshot of its current state very cheaply (just return itself) and even some mutable collection types could return an immutable snapshot at a cost far below the cost of a full enumeration (e.g. a List<T> might be backed by two T[][] arrays, one of which holds references to sharable immutable arrays of 256 items, and the other of which holds references to unsharable mutable arrays of 256 items. Making a snapshot of a list would require cloning only the inner arrays containing items that have been modified since the last snapshot--potentially much cheaper than cloning the whole list. Unfortunately, since there's no standard "make an immutable snapshot" interface [note that ICloneable doesn't count, since a clone of a mutable list would be mutable; while one could make an immutable snapshot by encapsulating a mutable clone in a read-only wrapper, that would only work for things which are cloneable, and even types which aren't cloneable should still support a "mutable snapshot" function.]

My recommendation is to return use a ReadOnlyCollection<T> for the scenario directly. This makes the usage explicit to the calling user.

Normally I would suggest using the appropriate interface. But given that the .NET Framework does not currently have a suitable IReadOnlyCollection, you must go with the ReadOnlyCollection type.

Also you must be aware when using ReadOnlyCollection, because it is not actually read-only: Immutability and ReadOnlyCollection

Sometimes you may want to use an interface, perhaps because you want to mock the collection during unit testing. Please see my blog entry for adding your own interface to ReadonlyCollection by using an adapter.

I typically return an IEnumerable<T>.

Once you make a collection readonly (so methods like Add, Remove and Clear no longer work) there's not much left that a collection supports that an enumerable doesn't - just Count and Contains, I believe.

If consumers of your class really need to treat elements like they're in a collection, it's easy enough to pass an IEnumerable to List<T>'s constructor.

Return a T[]:

private ICollection<T> items;

public T[] Items
{
    get { return new List<T>(items).ToArray(); }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top