正在取消订阅静态方法(扩展方法)内部的匿名事件处理程序

本文关键字:内部 程序 事件处理 方法 扩展 取消 静态方法 | 更新日期: 2023-09-27 17:58:46

我有一个扩展方法来订阅实现INotifyPropertyChanged的对象的PropertyChanged事件。

我希望活动只启动一次。没有更多。

这是我的方法。

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参数。我不能用这种方法。

有解决这个问题的变通方法或不同的方法吗?静态方法中的匿名方法有什么奇怪的地方吗?

提前谢谢。

正在取消订阅静态方法(扩展方法)内部的匿名事件处理程序

这是使用匿名方法作为事件处理程序的限制。它们不能像普通方法那样被删除(从技术上讲,普通方法是通过方法组转换自动创建的委托实例),因为匿名方法被编译到编译器生成的容器类中,每次都会创建该类的新实例。

为了保留操作参数,您可以创建一个容器类,其中包含事件处理程序的委托。该类可以在您正在处理的另一个类的内部声明为private,也可以在"Helpers"命名空间中声明为internal。它看起来像这样:

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;
}

如果您取消订阅事件处理程序本身内的,您就可以使用第一个示例。

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;
}

上面的代码充当"一次性完成"事件处理程序。您可以注册无限数量的这些,每一个在注销之前只执行一次。

请记住,如果在短时间内跨多个线程引发事件,那么其中一个处理程序可能会多次执行。为了防止这种情况,您可能需要创建一个静态Dictionary(T,T),将对象实例映射到"锁定对象",并添加一些哨兵代码,以确保处理程序只执行一次。然而,这些实现细节似乎有点超出了您目前所写问题的范围。

从技术上讲,这与您试图取消订阅的匿名方法不同。NET在每次调用OnPropertyChanged时都会创建该方法的新实例。这就是为什么取消订阅无效的原因。