Отписка от анонимного обработчика событий внутри статического метода (метод расширения )

у меня есть метод расширения, чтобы подписаться 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
    }

и все работает нормально. Событие срабатывает только один раз, но мне также нужен параметр действия. Я не могу использовать этот подход.

любой обходной путь или другой aproach для решения этой проблемы?Есть что-то странное с анонимными методами внутри статических методов?

спасибо заранее.

3 ответов


это ограничение использования анонимных методов в качестве обработчиков событий. Их нельзя удалить, как обычный метод (который технически является экземпляром делегата, автоматически созданным с помощью преобразования группы методов), поскольку анонимные методы компилируются в класс контейнера, созданный компилятором, и каждый раз создается новый экземпляр класса.

для сохранения параметра action вы можете создать класс контейнера, который будет иметь делегат для вашего события обработчик внутри. Класс может быть объявлен закрытым внутри другого класса, с которым вы работаете, или сделан внутренним, возможно, в пространстве имен "помощники". Это выглядело бы примерно так:--4-->

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

затем создайте и сохраните ссылку на контейнер в своем классе. Вы можете создать статический член!--2--> а затем установите обработчик следующим образом:

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

приведенный выше код служит обработчиком событий "one and done". Вы можете зарегистрировать неограниченное количество из них, и каждый из них будет выполнен только один раз, прежде чем отменить регистрацию.

имейте в виду, что один из этих обработчиков может выполняться несколько раз, если вы поднимаете событие по нескольким потокам в короткой последовательности. Чтобы предотвратить это, вам может потребоваться создать static Словарь (T,T) сопоставление экземпляров объектов с "заблокировать объекты" и добавить код sentry, чтобы убедиться, что обработчик выполняется только один раз. Однако эти особенности реализации, похоже, немного выходят за рамки вашего вопроса, как он написан в настоящее время.


технически, это не тот же самый анонимный метод, который вы пытаетесь отказаться. .NET создает новый экземпляр этого метода каждый раз, когда ваш OnPropertyChanged называется. Вот почему отписка не будет работать.