views:

1717

answers:

10

My understanding is that if you are using a generic list (List) in C#, that it can support multiple concurrent readers but only one writer. And when you introduce a writer into the mix, you must also provide synchronization constructs to make the operations thread safe.

Is List.Contains considered a read operation? In other words, if I am calling this method, do I need to worry that a writer may be writing to this List at the same time?

+1  A: 

List<T>.Contains is most certainly a read operation. It is possible that some other thread is writing to the collection as you read it.

Andrew Hare
And if the collection changes while Contains is running?
Matt Olenik
Nevermind, good now
Matt Olenik
+1  A: 

According to the doc ...

Enumerating through a collection is intrinsically not a thread-safe procedure.

So, I would say no it's not thread-safe. You should lock it.

JP Alioto
A: 

If a writer may be writing at the same time, List.Contains is definitely not thread safe. You'll need to wrap it and any other reads and writes with a lock.

Michael
+11  A: 

Yes, you should. Basically I would synchronize for any operation if the list might be used for writing at the same time.

Generally I find collections fall into two categories - ones which are created, initialized and then never changed again (thread-safe), and ones which are mutated over time (not thread-safe, lock for all access).

Jon Skeet
In the case where you instantiate one, fill it, and never use it again, I find it's helpful to wrap it in a ReadOnlyCollection<T>(). It doesn't stop a consumer from mutating the underlying items in the collection, but if you throw away the reference to the original list, then iterating is safe.
Scott Whitlock
Scott: Agreed - that makes it a lot clearer. Personally I like using collection types which guarantee immutability immediately after construction (using a builder type if necessary) but those aren't available in the core framework yet :(
Jon Skeet
+2  A: 

Yes, you do have to worry! List.Contains just gets an EqualityComparer and then loops through all items the list contains comparing the item passed as parameter with the item at the index of the current iteration, so if the list gets modified while being iterated, results may be unpredictable.

emaster70
+1  A: 

It is safe to assume this is NOT a threadsafe operation. The MSDN description sums it up:

...this method uses the collection’s objects’ Equals and CompareTo methods on item to determine whether item exists.

So, after the read operation is a compare operation.

spoulson
+1  A: 

In a multi-thread environment, you do need to make sure there is no writing to the collection at the same time. Here is the code from reflector, the collection itself didn't provide any lock for you, so it's on your won.

public bool Contains(T item)
{
    if (item == null)
    {
        for (int j = 0; j < this._size; j++)
        {
            if (this._items[j] == null)
            {
                return true;
            }
        }
        return false;
    }
    EqualityComparer<T> comparer = EqualityComparer<T>.Default;
    for (int i = 0; i < this._size; i++)
    {
        if (comparer.Equals(this._items[i], item))
        {
            return true;
        }
    }
    return false;
}
J.W.
+1 for great minds thinking alike.
Erich Mirabal
A: 

It's considered a read operation. You won't run into any race conditions but if you're concerned about getting the latest you could make the List volatile.

Alexander Kahoun
A: 

According to the MSDN documentation:

Public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.

The ReaderWriterLock class seems to be built for the synchronization you're looking for.

Greg
+1  A: 

If you use Reflector to inspect at the code, you get something like this:

public bool Contains(T item)
    {
        if (item == null)
        {
            for (int j = 0; j < this._size; j++)
            {
                if (this._items[j] == null)
                {
                    return true;
                }
            }
            return false;
        }
        EqualityComparer<T> comparer = EqualityComparer<T>.Default;
        for (int i = 0; i < this._size; i++)
        {
            if (comparer.Equals(this._items[i], item))
            {
                return true;
            }
        }
        return false;
    }

As you can see, it is a simple iteration over the items which is definitely a 'read' operation. If you are using it only for reads (and nothing mutates the items), then no need to lock. If you start modifying the list in a separate thread, then you most decidedly need to synchronize access.

Erich Mirabal