Если у меня есть глубоко неизменяемый тип (все члены доступны только для чтения и если они являются элементами ссылочного типа, то они также ссылаются на объекты, которые являются глубоко неизменяемыми).
Я бы хотел реализовать ленивое инициализированное свойство для типа, например так:
private ReadOnlyCollection<SomeImmutableType> m_PropName = null;
public ReadOnlyCollection<SomeImmutableType> PropName
{
get
{
if(null == m_PropName)
{
ReadOnlyCollection<SomeImmutableType> temp = /* do lazy init */;
m_PropName = temp;
}
return m_PropName;
}
}
Из того, что я могу сказать:
m_PropName = temp;
... потокобезопасен. Меня не слишком беспокоит то, что два потока будут одновременно инициализироваться, потому что это будет редко, оба результата будут идентичны с логической точки зрения, и я бы не стал использовать блокировку, если у меня нет к.
Будет ли это работать? Какие плюсы и минусы?
Изменить:
Спасибо за ваши ответы. Я вероятно буду двигаться вперед с использованием блокировки. Однако я удивлен, что никто не поднял вопрос о том, что компилятор понимает, что переменная temp не нужна, и просто присваивает значение m_PropName. Если бы это было так, то поток чтения мог бы прочитать объект, который еще не закончен. Предотвращает ли компилятор такую ситуацию?
(Ответы, похоже, указывают на то, что среда выполнения этого не допустит.)
Edit:
Поэтому я решил использовать метод Interlocked CompareExchange, вдохновленный этой статьей Джо Даффи .
В основном:
private ReadOnlyCollection<SomeImmutableType> m_PropName = null;
public ReadOnlyCollection<SomeImmutableType> PropName
{
get
{
if(null == m_PropName)
{
ReadOnlyCollection<SomeImmutableType> temp = /* do lazy init */;
System.Threading.Interlocked(ref m_PropName, temp, null);
}
return m_PropName;
}
}
Предполагается, что все потоки, вызывающие этот метод в этом экземпляре объекта, получат ссылку на один и тот же объект, поэтому будет работать оператор ==. Вполне возможно, что вы потратили впустую работу, и это нормально - это просто делает этот алгоритм оптимистичным.
Как отмечено в некоторых комментариях ниже, это зависит от модели памяти .NET 2.0 для работы. В противном случае m_PropName должно быть объявлено как volatile.