我有一个扩展方法,用于触发看起来像这样的PropertyChanged通知:
public static TRet RaiseIfChanged<TObj, TRet>(this TObj target, Expression<Func<TObj, TRet>> property, TRet newValue)
where TObj : AlantaViewModelBase
{
var propertyInfo = (property.Body as MemberExpression).Member as PropertyInfo;
var propertyValue = propertyInfo.GetValue(target, null);
if (!EqualityComparer<TRet>.Default.Equals((TRet)propertyValue, (TRet)newValue))
{
// Marshal this to the dispatcher so that the RaisePropertyChanged() notification happens after
// the value is actually set.
Deployment.Current.Dispatcher.BeginInvoke(() => target.RaisePropertyChanged(propertyInfo.Name));
}
return newValue;
}
它被这样使用:
private bool isBusy;
public bool IsBusy
{
get { return isBusy; }
set { isBusy = this.RaiseIfChanged(p => p.IsBusy, value); }
}
我的问题是我用Dispatcher.BeginInvoke()
实际调用target.RaisePropertyChanged()
。鉴于在扩展方法完成之前属性实际上不会发生变化,我需要确信在设置属性值之前,事件实际上不会被提升。我能否相信这种情况总是如此?或者是否有事件在属性发生变化之前被解雇?或者有更好的方法来解决这个问题吗?
答案 0 :(得分:3)
你没有这样的保证。 RaisePropertyChanged()方法运行的确切时间取决于UI线程的状态。如果恰好准备好查看调用队列,以便调用目标在工作线程调用RaiseIfChanged的时间执行,并且工作线程失去处理器量,那么,是的,该调用将在工作者之前进行可以返回并设置属性值。不太可能,但良好的线程意图的道路充满了这样的道路杀伤力。
您必须在提升事件之前分配属性。干燥的测试和设置非常简单。通过使用PropertyInfo.SetValue()或通过引用传递支持变量,保持干燥但丑陋。就个人而言,我不会,使用反射来读取属性值比直接编码它要容易三个数量级。
答案 1 :(得分:2)
您的代码不保证执行顺序。也就是说,可以在设置属性之前发出事件信号。例如,在单处理器机器上,看到调度程序只是同步执行代码就不足为奇了。在这种情况下,事件将在从方法返回之前发出信号。
在多核或多处理器计算机上,所有投注均已关闭。我怀疑你会发现有时候事件会在房产改变之前发出信号,有时却不会发出信号。
安全的方法是:
set
{
var oldValue = isBusy;
isBusy = value;
this.RaiseIfChanged(oldValue, value);
}
当然,这需要更改您的扩展方法。 。
答案 2 :(得分:0)
Dispatcher.BeginInvoke()计划一个新线程来运行lambda。一旦有处理器时间,该线程就会运行。我假设,RaisePropertyChanged()方法手动触发一个事件,在.NET中通常使用同步MulticastDelegate实现。如果是这种情况,事件处理程序可以在实际属性更改之前运行;它是否会取决于您无法控制的硬件细节,但缺点是,您无法保证。
你应该这样做的方法是存储原始值,执行赋值,然后如果值确实发生了变化则引发事件。 KISS:您可以重新定义RaiseIfChanged以取旧值而不是投影,或者您可以在触发事件之前在RaiseIfChanged内执行赋值。
答案 3 :(得分:0)
没有假设你可以决定何时执行。它将立即执行(在您从UI线程运行的情况下)或将来在任何点执行。