Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unsubscribing from anonymous event handler inside an static method (extension method)

I have an extension method to subscribe a PropertyChanged event of an object that implements INotifyPropertyChanged.

I would like that the event fires just once. Not more.

This is my method.

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;

}

But it does not work. I cannnot remove the event handler so the event fires every time I call this method.

I have try a different approach. Instead of using annonymous methods, something more traditional, like this:

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
    }

And it just works fine. The event fires just once, but I also need the Action parameter. I cannot use it with this approach.

Any workaround or different aproach to solve this issue?Is there something strange with anonymous methods inside static methods?

Thanks in advance.

like image 683
Nadya Avatar asked Feb 11 '13 16:02

Nadya


2 Answers

That is a limitation of using anonymous methods as event handlers. They cannot be removed as you would a normal method (which is technically a delegate instance automatically create via a method group conversion) because anonymous methods get compiled into a compiler-generated container class and a new instance of the class is created each time.

In order to preserve the action parameter you could create a container class which would have the delegate for your event handler inside. The class can be declared private inside the of the other class you're working with - or made internal, maybe in a "Helpers" namespace. It would look something like this:

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

Then, create and store the reference to the container in your class. You might create a static member currentContainer and then set the handler like this:

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;
}
like image 183
Mike Dinescu Avatar answered Nov 08 '22 05:11

Mike Dinescu


You can get your first example to work if you unsubscribe from within the event handler itself.

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

The above code serves as a "one and done" event handler. You can register an unlimited number of these, and each one will only be executed once before unregistering itself.

Keep in mind that it's possible to have one of these handlers execute multiple times, if you raise the event across multiple threads in short succession. To prevent this, you might need to create a static Dictionary(T,T) mapping object instances to "lock objects," and add some sentry code to ensure that a handler is only executed once. Those implementation specifics seem to be a bit outside the scope of your question as currently written, however.

like image 3
BTownTKD Avatar answered Nov 08 '22 06:11

BTownTKD