So, to start with, no the specific solution that you have provided is not safe, due to the specifics of how you're accessing the field.
Getting a working solution is easy enough. Just don't create a new list; instead, clear it out and add the new items:
class Foo
{
private List<string> lockedList = new List<string>();
public void ReplaceList(IEnumerable<string> items)
{
lock (lockedList)
{
lockedList.Clear();
lockedList.AddRange(items);
}
}
public void Add(string newItem)
{
lock (lockedList)
{
lockedList.Add(newItem);
}
}
public void Contains(string item)
{
lock (lockedList)
{
lockedList.Contains(item);
}
}
}
Now your field isn't actually changing, and you don't need to worry about all of the problems that that can cause.
As for how the code in the question can break, all it takes is a call to Add
or Contains
to read the field, get the list, lock the list, and then have another thread replace the field. The when you read the field for a second time, after already getting the value to lock on, the value may have changed, so you'll end up mutating or reading from a list that another caller won't be restricted from accessing.
All that said, while mutating the lockedList
variable is a really bad idea, and you should unquestionably avoid mutating it, as shown above, you can also ensure that you only actually read the field once, rather than reading from it repeatedly, and you'll still ensure that each list is only ever accessed from a single thread at any one time:
class Foo
{
private volatile List<string> lockedList = new List<string>();
public void ReplaceList(IEnumerable<string> items)
{
lockedList = new List<string>(items);
}
public void Add(string newItem)
{
var localList = lockedList;
lock (localList)
{
localList.Add(newItem);
}
}
public void Contains(string item)
{
var localList = lockedList;
lock (localList)
{
localList.Contains(item);
}
}
}
Notice here that the problem that this fixes isn't mutating the field that the object to lock on was fetched from (that's not inherently the problem, although is a very bad practice), but rather constantly getting new values from the field in all usages of it from inside of the lock
statements and expecting that value to never change, when it can.
This is going to be much harder to maintain, is very fragile, and is much more difficult to understand or ensure the correctness of though, so again, do do things like this.
lockedList
initially points to. You're not transferring the lock tonewList
, you never have a lock on that object. So as long as your code doesn't assume otherwise, it's fine. – Blorgbeard