1

Here is a scenario:

I have a class which subscribes to many different events:

ProviderOfFruit.Event += OnFruitHarvested;
ProviderOfCars.Event += OnCarBrokeDown;
ProviderOfPeople.Event += OnPersonAwoke;
...etc

Later I want to unsubscribe from all of these at the same time. Am I doomed to writing this out in full:

ProviderOfFruit.Event -= OnFruitHarvested;
ProviderOfCars.Event -= OnCarBrokeDown;
ProviderOfPeople.Event -= OnPersonAwoke;
...etc

Or is there a way to do something along the lines of:

ListOfEvents.Unsubscibe(); ?

NOTE: I do not want to clear an event of all its subscribers, many classes my be subscribed to an event. I just want one of those classes to unsubscribe from the events it is subscribing to. The aim here being that I never forget to unsubscribe from a particular event.

Mark Bamford
  • 113
  • 1
  • 5
  • can be helpful https://stackoverflow.com/questions/153573/how-can-i-clear-event-subscriptions-in-c – melya Nov 24 '17 at 16:05
  • at worst you could keep them in a list, then loop through them to unsubscribe each one, I think. – ADyson Nov 24 '17 at 16:06
  • @melya That is about removing all subscribers from 1 event, I want to remove all the subscriptions i have made to any number of different events. – Mark Bamford Nov 24 '17 at 16:41
  • @ADyson its the creation of the list I am struggling with, as delegates appear to be immutable? – Mark Bamford Nov 24 '17 at 16:42
  • I think what you really want is `weak events` - that is, events, in and of themselves, shouldn't keep objects alive and event handlers attached to a collected object should clean themselves rather than produce errors. There have been [efforts in that direction](https://msdn.microsoft.com/en-us/library/aa970850(v=vs.100).aspx) but unfortunately the "standard" event pattern got baked into .NET first. – Damien_The_Unbeliever Nov 24 '17 at 18:00
  • (Also, for full generality, you don't want *events* to be strong or weak but for *subscribers* to be so. One subscriber may want to subscribe to an event and hold no further references and trust that the subscription will be kept alive. Another subscriber may want to listen to (some) events but for those events to not affect that subscriber's lifetime. I hear MS Research are working on a time machine...) – Damien_The_Unbeliever Nov 24 '17 at 18:07

2 Answers2

0

Yes, you can keep all the events a class subscribes to in a collection and loop over them. This keeps you from having to list each event during subscription and then again when unsubscribing -- and, as a bonus, helps to prevent the possibility of missing an event in one place or another.

However, this is limited and doesn't solve the biggest problem of your class having a hard dependency on so many other classes. Your example doesn't provide enough detail to say for certain, but you may find the Event Aggregator pattern of use.

David Culp
  • 5,354
  • 3
  • 24
  • 32
  • "you can keep all the events a class subscribes to in a collection and loop over them." Can you give an example of how to do this? – Mark Bamford Nov 24 '17 at 16:52
  • This answer from another SO question details one method of doing so (but do look at Event Aggregator and determine if it fits your needs) - https://stackoverflow.com/a/16135376/413399 – David Culp Nov 24 '17 at 16:57
  • The Event Aggregator pattern doesn't look like what I need, and the collection example doesn't seem to be right either. I have edited the main post to hopefully clarify what I am aiming for. I don't have a specific case I am trying to solve, its more something I find myself doing frequently and it feels like I could be more DRY about it. – Mark Bamford Nov 24 '17 at 17:24
-1

You can use the following pattern. First create class which will run arbitrary function you pass on construction and another on dispose:

public class EventSubscription : IDisposable {
    private readonly Action _unsubscribe;

    private EventSubscription(Action subscribe, Action unsubscribe) {
        _unsubscribe = unsubscribe;
        subscribe();
    }

    public static IDisposable Create(Action subscribe, Action unsubscribe) {
        return new EventSubscription(subscribe, unsubscribe);
    }

    public void Dispose() {
        _unsubscribe();
    }
}

Then create field in your class where you subscribe to events:

private static readonly List<IDisposable> _subscriptions = new List<IDisposable>();

And subscribe like this:

_subscriptions.Add(EventSubscription.Create(
    () => Event1 += OnEvent1, 
    () => Event1 -= OnEvent1));
_subscriptions.Add(EventSubscription.Create(
    () => Event2 += OnEvent2, 
    () => Event2 -= OnEvent2));

When you need to unsubsribe, just do:

_subscriptions.ForEach(c => c.Dispose());

Advantages include unsubscribing from all at once, and much less chance to forget to unsubscribe, because you always pass += and -= handler in pair, in the same call.

Variation of this is this general purpose class:

public class Disposable : IDisposable {
    private readonly Action _action;
    private Disposable(Action action) {
        _action = action;
    }
    public static IDisposable FromAction(Action action) {
        return new Disposable(action);
    }
    public void Dispose() {
        _action();
    }
}

You can find it already in many places (like .NET reactive extensions), but if not - you can implement yourself. Then above code becomes:

Event1 += OnEvent1;
_subscriptions.Add(Disposable.FromAction(() => Event1 -= OnEvent1));

Using IDisposable is not necessary of course, you can just have list of Actions.

Evk
  • 98,527
  • 8
  • 141
  • 191
  • This is definitely a step in the right direction, but still has a lot of redundant code and potential for error, can we do better? (first thing I can see is making a custom collection taking subscribe and unsubscribe lamdas, are there other improvements?) – Mark Bamford Nov 24 '17 at 17:47
  • @MarkBamford I don't think so unfortunately (aside from minor improvements which do not change overall idea). You can use reflection to subscribe\unsubscribe, but then you will have to pass event target (object with target event) and event name as string, which introduces even more potential for error. Problem is you cannot "pass" event as argument to another function (in a way that function will be able to sub\unsub) or store it in array\field. – Evk Nov 24 '17 at 17:53