我有一个扩展方法来订阅实现PropertyChanged
的对象的INotifyPropertyChanged
事件。
我希望事件只发射一次。不多了。
这是我的方法。
public static void OnPropertyChanged<T>(this INotifyPropertyChanged target, string propertyName, Action action)
{
if (target == null)
{
return;
}
PropertyChangedEventHandler handler = (obj, e) =>
{
if (propertyName == e.PropertyName)
{
action();
}
};
target.PropertyChanged -= handler;
target.PropertyChanged += handler;
}
但它不起作用。我无法删除事件处理程序,因此每次调用此方法时都会触发事件。
我尝试了不同的方法。而不是使用匿名方法,更传统的东西,如:
public static void OnPropertyChanged<T>(this INotifyPropertyChanged target, string propertyName, Action action)
{
if (target == null)
{
return;
}
target.PropertyChanged -= target_PropertyChanged;
target.PropertyChanged += target_PropertyChanged;
}
static void target_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//do stuff here
}
它运作正常。该事件只触发一次,但我还需要Action参数。我不能用这种方法。
解决这个问题的任何解决方法或不同方法?静态方法中的匿名方法有什么奇怪之处吗?
提前致谢。
答案 0 :(得分:3)
这是使用匿名方法作为事件处理程序的限制。它们不能像普通方法(技术上是通过方法组转换自动创建委托实例)那样被删除,因为匿名方法被编译到编译器生成的容器类中,并且每次都会创建类的新实例。
为了保留action参数,你可以创建一个容器类,它将包含你的事件处理程序的委托。该类可以在您正在使用的其他类中声明为私有 - 或者在内部创建,也可以在“Helpers”命名空间中。它看起来像这样:
class DelegateContainer
{
public DelegateContainer(Action theAction, string propName)
{
TheAction = theAction;
PopertyName = propName;
}
public Action TheAction { get; private set; }
public string PropertyName { get; private set; }
public void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)
{
if(PropertyName == e.PropertyName)
TheAction();
}
}
然后,在类中创建并存储对容器的引用。您可以创建一个静态成员currentContainer
,然后像这样设置处理程序:
private static DelegateContainer currentContainer;
public static void OnPropertyChanged<T>(this INotifyPropertyChanged target, string propertyName, Action action)
{
if (target == null)
{
return;
}
if(currentContainer != null)
target.PropertyChanged -= currentContainer.PropertyChangedHandler;
currentContainer = new DelegateContainer(action, propertyName);
target.PropertyChanged += currentContainer.PropertyChangedHandler;
}
答案 1 :(得分:3)
如果您取消订阅事件处理程序本身中的,您可以获得第一个示例。
public static void OnPropertyChanged<T>(this INotifyPropertyChanged target, string propertyName, Action action)
{
if (target == null)
{
return;
}
// Declare the handler first, in order to create
// a concrete reference that you can use from within
// the delegate
PropertyChangedEventHandler handler = null;
handler = (obj, e) =>
{
if (propertyName == e.PropertyName)
{
obj.PropertyChanged -= handler; //un-register yourself
action();
}
};
target.PropertyChanged += handler;
}
上面的代码用作“one and done”事件处理程序。您可以注册不限数量的这些,并且每个只能在取消注册之前执行一次。
请记住,如果您在短时间内跨多个线程引发事件,则可以让其中一个处理程序执行多次。为了防止这种情况,您可能需要创建静态Dictionary(T,T)映射对象实例以“锁定对象”,并添加一些哨兵代码以确保处理程序仅执行一次。然而,这些实现细节似乎有点超出了您当前编写的问题的范围。
答案 2 :(得分:0)
从技术上讲,这与您尝试取消订阅的匿名方法不同。每次调用OnPropertyChanged
时,.NET都会创建该方法的新实例。这就是取消订阅无效的原因。