#c#-4.0
#c #-4.0
Вопрос:
В бизнес-классе у меня есть :
class Employee{
public Employee() {
m_Manager = new Lazy<Manager>( () => return myRepository.GetManager(ManagerId); );
}
public int ManagerId { get; set;}
private Lazy<Manager> m_Manager;
public Manager Manager {
get {
return m_Manager.Value;
}
}
}
Это работает правильно, пользовательский репозиторий вызывается только при доступе к свойству Manager.
Теперь я хочу «сбросить» свое свойство manager, если ManagerID изменился. Как это сделать?
Я могу сделать :
class Employee{
public Employee() {
m_Manager = new Lazy<Manager>( () => return myRepository.GetManager(ManagerId); );
}
private int m_ManagerId;
public int ManagerId {
get { return m_ManagerId;}
set {
if(m_ManagerId != value)
{
m_ManagerId = value;
m_Manager = new Lazy<Manager>( () => return myRepository.GetManager(ManagerId); );
}
}
}
private Lazy<Manager> m_Manager;
public Manager Manager {
get {
return m_Manager.Value;
}
}
}
Есть ли более чистый способ сделать это? Разве не существует m_Manager.Reset()
или чего-то подобного этому?
Ответ №1:
Lazy<T>
не определяет метод Reset(). Я думаю, то, что вы реализовали, выглядит нормально.
Комментарии:
1. Я согласен, что общий подход хорош, но предлагаю извлечь дублированный код инициализации Lazy<Manager> в общий метод.
Ответ №2:
Если вас устраивает использование недокументированного поведения и закрытых полей, вот способ сделать это:
public static void ResetPublicationOnly<T>(this Lazy<T> lazy)
{
const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic;
LazyThreadSafetyMode mode = (LazyThreadSafetyMode)typeof(Lazy<T>).GetProperty("Mode", flags).GetValue(lazy, null);
if (mode != LazyThreadSafetyMode.PublicationOnly)
throw new InvalidOperationException("ResetPublicationOnly only works for Lazy<T> with LazyThreadSafetyMode.PublicationOnly");
typeof(Lazy<T>).GetField("m_boxed", flags).SetValue(lazy, null);
}
И какой-нибудь тест на использование:
Lazy<string> val = new Lazy<string>(() => "hola" DateTime.Now.Ticks, LazyThreadSafetyMode.PublicationOnly);
val.ResetPublicationOnly(); //reset before initialized
var str1 = val.Value;
val.ResetPublicationOnly(); //reset after initialized
var str2 = val.Value;
Assert.AreNotEqual(str1, str2);
РЕДАКТИРОВАТЬ: Устарело!Это решение больше не работает так, как указал Кит. Мы создали нашу собственную систему сброса в Signum Framework
public interface IResetLazy
{
void Reset();
void Load();
Type DeclaringType { get; }
}
[ComVisible(false)]
[HostProtection(Action = SecurityAction.LinkDemand, Resources = HostProtectionResource.Synchronization | HostProtectionResource.SharedState)]
public class ResetLazy<T>: IResetLazy
{
class Box
{
public Box(T value)
{
this.Value = value;
}
public readonly T Value;
}
public ResetLazy(Func<T> valueFactory, LazyThreadSafetyMode mode = LazyThreadSafetyMode.PublicationOnly, Type declaringType = null)
{
if (valueFactory == null)
throw new ArgumentNullException("valueFactory");
this.mode = mode;
this.valueFactory = valueFactory;
this.declaringType = declaringType ?? valueFactory.Method.DeclaringType;
}
LazyThreadSafetyMode mode;
Func<T> valueFactory;
object syncLock = new object();
Box box;
Type declaringType;
public Type DeclaringType
{
get { return declaringType; }
}
public T Value
{
get
{
var b1 = this.box;
if (b1 != null)
return b1.Value;
if (mode == LazyThreadSafetyMode.ExecutionAndPublication)
{
lock (syncLock)
{
var b2 = box;
if (b2 != null)
return b2.Value;
this.box = new Box(valueFactory());
return box.Value;
}
}
else if (mode == LazyThreadSafetyMode.PublicationOnly)
{
var newValue = valueFactory();
lock (syncLock)
{
var b2 = box;
if (b2 != null)
return b2.Value;
this.box = new Box(newValue);
return box.Value;
}
}
else
{
var b = new Box(valueFactory());
this.box = b;
return b.Value;
}
}
}
public void Load()
{
var a = Value;
}
public bool IsValueCreated
{
get { return box != null; }
}
public void Reset()
{
if (mode != LazyThreadSafetyMode.None)
{
lock (syncLock)
{
this.box = null;
}
}
else
{
this.box = null;
}
}
}
Комментарии:
1. надеюсь, вы не в замкнутом цикле
2. похоже, они внесли некоторые изменения в исходный код Lazy<T>. Этот метод больше не работает, и будет выдано исключение
3. Отличный пример того, почему не следует использовать недокументированное поведение и закрытые поля!
4. Гораздо лучше просто переназначить переменную в соответствии с кодом в вопросе… простой И надежный, легко сделать потокобезопасным.
5. Я думаю, что одному и тому же потоку разрешено дважды использовать одну и ту же блокировку, это то, что вы имеете в виду, но есть фундаментальные различия в том, как работает ResetLazy по сравнению с referencesource.microsoft.com/#mscorlib/system / … и это заставляет меня нервничать. Во всяком случае, я часто использую его без проблем.
Ответ №3:
Перед повторным созданием экземпляра Lazy вы также можете добавить условие для сброса, только если оно было инициализировано «if(m_Manager.IsValueCreated)».
Комментарии:
1. Это создало бы условие гонки — если бы другой поток начал создавать значение и уже прочитал (исходный) ManagerID, и в этот момент ManagerID изменен, тогда вы бы увидели, что IsValueCreated равен false , и не создавали бы повторно экземпляр Lazy<> . Несколько мгновений спустя другой поток успешно создает объект Manager, и все последующие вызывающие пользователи получают неправильный объект manager. В исходном коде только вызывающие, которые, по крайней мере, начали обращаться к объекту Employee до изменения идентификатора ManagerID, будут видеть старый объект Manager.