knocte
knocte

Reputation: 17949

Clear all event handlers in F#?

If I remember correctly from my C# days, there was a way to unsubscribe all event handlers from an event without the need to do a -= operation for each of them: assigning null to the event.

However, seems that in F# this is not possible? I find the methods Trigger, Publish, Subscribe, Add, and Remove. Would be handy to have a Clear one or something alike. Maybe it's easy to implement a custom event that can achieve this?

Upvotes: 3

Views: 224

Answers (2)

Scott Hutchinson
Scott Hutchinson

Reputation: 1721

This quote really just confirms the answer by Tomas.

From Concurrency in .NET by Riccardo Terrell, Published by Manning Publications, 2018, Chapter 6 "It’s possible to choose (and use) either Observable or Event when using F# to build reactive systems; but to avoid memory leaks, the preferred choice is Observable. When using the F# Event module, composed events are attached to the original event, and they don’t have an unsubscribe mechanism that can lead to memory leaks. Instead, the Observable module provides the subscribe operator to register a callback function. This operator returns an IDisposable object that can be used to stop event-stream processing and to de-register all subscribed observable (or event) handlers in the pipeline with one call of the Dispose method."

Upvotes: 0

Tomas Petricek
Tomas Petricek

Reputation: 243061

As far as I can see, there is no way to do this with standard F# Event<'T> type. Even when you define an event and mark it as CLIEvent, the generated code defines a custom type of event with add and remove methods and so there does not seem to be a way to remove all handlers.

However, if you need to define your own F# event that supports this, you can actually implement this yourself. You can implement something like Event<'T> which keeps track of event handlers using a ResizeArray and removes all handlers when asked to do that. All you need to do is to implement the IObservable (or if you want more methods, IEvent interface).

The following does not handle concurrency correctly and might break, but it shows the idea:

open System

type RemovableEvent<'T> () = 
  let handlers = ResizeArray<Handler<_>>()
  member x.Trigger(v) = 
    for h in handlers do h.Invoke(x, v)
  member x.Clear() = 
    handlers.Clear()
  member x.Publish = 
    { new IEvent<'T> with
        member x.AddHandler(h) = handlers.Add(h)
        member x.RemoveHandler(h) = handlers.Remove(h) |> ignore
        member x.Subscribe(h) =
          let h = Handler<_>(fun _ v -> h.OnNext v)
          handlers.Add(h)
          { new IDisposable with
            member x.Dispose() = handlers.Remove(h) |> ignore } }

let re = RemovableEvent<_>()
re.Publish.Add(printfn "Hello %s")
re.Publish.Add(printfn "Hello again %s")
re.Trigger("world")
re.Clear()

Upvotes: 3

Related Questions