views:

1250

answers:

8

I have an object in a multi-threaded environment that maintains a collection of information, e.g.:

public IList<string> Data {
    get {
        return data;
    }
}

I currently have return data; wrapped by a ReaderWriterLockSlim to protect the collection from sharing violations. However, to be doubly sure, I'd like to return the collection as read-only, so that the calling code is unable to make changes to the collection, only view what's already there. Is this at all possible?

+10  A: 

If your underlying data is stored as list you can use List(T).AsReadOnly method.
If your data can be enumerated, you can use Enumerable.ToList method to cast your collection to List and call AsReadOnly on it.

aku
+2  A: 

One should note that aku's answer will only protect the list as being read only. Elements in the list are still very writable. I don't know if there is any way of protecting non-atomic elements without cloning them before placing them in the read only list.

Kris Erickson
A: 

@alastairs,

As @Kris Erickson mentioned, if you care of list items thread safety, consider using immutable types.

aku
+2  A: 

If your only intent is to get calling code to not make a mistake, and modify the collection when it should only be reading all that is necessary is to return an interface which doesn't support Add, Remove, etc.. Why not return IEnumerable? Calling code would have to cast, which they are unlikely to do without knowing the internals of the property they are accessing.

If however your intent is to prevent the calling code from observing updates from other threads you'll have to fall back to solutions already mentioned, to perform a deep or shallow copy depending on your need.

nedruod
IEnumerable sounds like a good way forward actually. As you suggest, my intention is only to protect the collection from direct modification; the collection members are not going to be modified (or needed to be modified).
alastairs
+3  A: 

I think you're confusing concepts here.

The ReadOnlyCollection provides a read-only wrapper for an existing collection, allowing you (Class A) to pass out a reference to the collection safe in the knowledge that the caller (Class B) cannot modify the collection (i.e. cannot add or remove any elements from the collection.)

There are absolutely no thread-safety guarantees.

  • If you (Class A) continue to modify the underlying collection after you hand it out as a ReadOnlyCollection then class B will see these changes, have any iterators invalidated, etc. and generally be open to any of the usual concurrency issues with collections.
  • Additionally, if the elements within the collection are mutable, both you (Class A) and the caller (Class B) will be able to change any mutable state of the objects within the collection.

Your implementation depends on your needs: - If you don't care about the caller (Class B) from seeing any further changes to the collection then you can just clone the collection, hand it out, and stop caring. - If you definitely need the caller (Class B) to see changes that are made to the collection, and you want this to be thread-safe, then you have more of a problem on your hands. One possibility is to implement your own thread-safe variant of the ReadOnlyCollection to allow locked access, though this will be non-trivial and non-performant if you want to support IEnumerable, and it still won't protect you against mutable elements in the collection.

Daniel Fortunov
+1  A: 

You can use a copy of the collection instead.

public IList<string> Data {
get {
    return new List<T>(data);
}}

That way it doesn't matter if it gets updated.

Dror Helper
+2  A: 

You want to use the yield keyword. You loop through the IEnumerable list and return the results with yeild. This allows the consumer to use the for each without modifying the collection.

It would look something like this:

List<string> _Data;
public IEnumerable<string> Data
{
  get
  {
    foreach(string item in _Data)
    {
      return yield item;
    }
  }
}
Charles Graham
+2  A: 

I voted for your accepted answer and agree with it--however might I give you something to consider?

Don't return a collection directly. Make an accurately named business logic class that reflects the purpose of the collection.

The main advantage of this comes in the fact that you can't add code to collections so whenever you have a native "collection" in your object model, you ALWAYS have non-OO support code spread throughout your project to access it.

For instance, if your collection was invoices, you'd probably have 3 or 4 places in your code where you iterated over unpaid invoices. you could have a getUnpaidInvoices method. However, the real power comes in when you start to think of methods like "payUnpaidInvoices(payer, account);".

When you pass around collections instead of writing an object model, entire classes of refactorings will never occur to you.

Note also that this makes your problem particularly nice. If you don't want people changing the collections, your container need contain no mutators. If you decide later that in just one case you actually HAVE to modify it, you can create a safe mechanism to do so.

How do you solve that problem when you are passing around a native collection?

Also, native collections can't be enhanced with extra data. You'll recognize this next time you find that you pass in (Collection, Extra) to more than one or two methods. It indicates that "Extra" belongs with the object containing your collection.

Bill K
Great answer, wish I could up-vote more than once!
alastairs