tags:

views:

376

answers:

3

I have a custom class that implements ICollection, and this class is readonly, ie. IsReadOnly returns true (as opposed to using the readonly keyword), and all functions that would normally modify the data in the collection throw InvalidOperationException's.

Now, given such a construct, and a quick skim over the thread-safety issues when implementing ICollection (specifically ICollection.IsSynchronized and friends), I came up with this quick and dirty solution.

bool ICollection.IsSynchronised { get{ return true; } }
object ICollection.SyncRoot { get{ return new Object(); } }

Now, given the examples in MSDN, this won't cause different threads to lock properly, because they are getting different objects from SyncRoot. Given that this is a readonly collection though, is this an issue? Are there memory/GC issues with returning new Object()? Any other issues you can see with this implementation?

A: 

I guess the issue would be if clients used your sync root to achieve locking of not only your collection, but something else. Supposed they cached the size of the collection - or maybe "what subset of this collection matches a predicate" - they would reasonably assume that they could use your SyncRoot to guard both your collection and their other member.

Personally I hardly use SyncRoot at all, but I think it would be sensible to always return the same thing.

Jon Skeet
The collection is in effect immutable, there is no way of modifying it (save for digging around in reflection of course), so all these sorts of things should be guaranteed shouldn't they?
Matthew Scharley
Consider that your clients may not know that they're getting an immutable collection. Obviously they're not trying to modify it (or they'll already get an error) but they may well be locking to make sure they wouldn't fail if they were provided a mutable collection.
Jon Skeet
Yes, ok, they call Syncroot, get an object to lock against, an object that noone else will ever get, so the lock is useless. But should we really be holding up other threads unnecessarily?
Matthew Scharley
You're *assuming* it will be unnecessary. It may be necessary, if they're using that one lock to guard multiple things.
Jon Skeet
+1  A: 

It seems very odd to return a different object each time... actually, I very rarely (if ever) use the SyncRoot approach, as often I need to synchronize between multiple objects etc, so a separate object is more meaningful.

But if the data is truly immutable (readonly), why not just return false from IsSynchronized?

Re GC - any such object would typically be short lived and be collected in GEN0. If you have a field with an object (for the lock), it would last as long as the collection, but most likely won't hurt anyway...

If you need a lock, I'd be tempted to just have a:

private readonly object lockObj = new object();

You could also use a lazy approach to only "new" it when needed, which makes a certain amount of sense if you don't actually expect anyone to ask for the sync-lock (by returns false to IsSynchronized).

Another common approach is to return "this"; it keeps things simple, but risks conflicts with some other code using your object as a lock for an unrelated purpose. Rare, but possible. This is actually the approach that [MethodImpl] uses to synchronize.

Marc Gravell
Wouldn't that be saying that the object isn't thread safe, when infact, it is?
Matthew Scharley
Perhaps; IMO, the whole logic around synchronized collections is a little bit borked - it is rare that you want thread-safety for a *single* operation - you normally want to check a value and then do something else. I suspect that most common code will never check this property anyway...
Marc Gravell
I think the main reason for this is to put a locked statement around foreach loops, so the loop doesn't mess up if someone modifies the collection while you're enumerating over it.
Matthew Scharley
Oh, I know the theory - it just doesn't always pan out in reality... threading/locking is tricky, and the collection SyncRoot is often an over-simplification that offers very little, and is more often simply ignored (making it virtually useless; synchronization is all-or-nothing).
Marc Gravell
+2  A: 

Yes this is an issue in some cases. Even though the collection is read only and cannot be changed, the objects the collection references are not read only. Thus if the clients use the SyncRoot to perform locking they will not be thread safe when modifying the objects referenced by the collection.

I would recommend adding:

private readonly object syncRoot = new object();

to your class. Return this as the SyncRoot and you're good to go.

Kimoz