开发者

How to check if an event is being handled in F#

What is the F# e开发者_运维技巧quivalent of the following C# code? Specifically, I need to check if an event is being handled.

protected virtual void OnClicked(ClickEventArgs e) {
    if (this.Clicked != null) //how can I perform this check in F#
        this.Clicked(this, e);
}


Okay, I think I figured this thing out. Taking a cue from Don Syme's blog, specifically the section "The Implementation of the IEvent Module."

Instead of the following:

let validationFailedEvent = new Event<DataValidationEventHandler, DataValidationEventArgs>()

I had to implement IEvent myself and create a variable to hold the invocation list:

let mutable listeners: Delegate = null

let validationFailedEvent = { new IEvent<DataValidationEventHandler, DataValidationEventArgs> with 
                                    member x.AddHandler(d) = 
                                        listeners <- Delegate.Combine(listeners, d)
                                    member x.RemoveHandler(d) =
                                        listeners <- Delegate.Remove(listeners, d)
                                    member x.Subscribe(observer) =
                                        let h = new Handler<_>(fun sender args -> observer.OnNext(args))
                                        (x :?> IEvent<_,_>).AddHandler(h)
                                        { new System.IDisposable with 
                                             member x.Dispose() = (x :?> IEvent<_,_>).RemoveHandler(h) } }

Then, to check if there are listeners, and, if not, raise an exception:

member private x.fireValidationFailedEvent(e:DataValidationEventArgs) =
    match listeners with
    | null -> failwith "No listeners"
    | d -> d.DynamicInvoke([| box x; box e |])


An alternative way to implement RequiresSubscriptionEvent is to build on top of the existing Event functionality (using composition) and just add a counter that counts the number of registered handlers and add a property HasListeners (or even publish the number of listeners if you wanted...)

This makes the code a bit easier to use and hopefuly also safer, because if you don't check whether it has any listneres, it will still work as the usual F# code. And if you want to perform the check, you can...

type RequiresSubscriptionEvent<_>() = 
  let evt = new Event<_>()
  let mutable counter = 0
  let published = 
    { new IEvent<_> with
      member x.AddHandler(h) = 
        evt.Publish.AddHandler(h)
        counter <- counter + 1; 
      member x.RemoveHandler(h) = 
        evt.Publish.RemoveHandler(h)
        counter <- counter - 1; 
      member x.Subscribe(s) = 
        let h = new Handler<_>(fun _ -> s.OnNext)
        x.AddHandler(h)
        { new System.IDisposable with 
            member y.Dispose() = x.RemoveHandler(h) } }
  member x.Trigger(v) = evt.Trigger(v)
  member x.Publish = published
  member x.HasListeners = counter > 0

Sample usage:

type Demo() =
  let evt = new RequiresSubscriptionEvent<_>()
  [<CLIEvent>]
  member x.OnSomething = evt.Publish
  member x.FooThatFiresSomething() = 
    if evt.HasListeners then
      evt.Trigger("foo!")
    else
      printfn "No handlers!"

Even though this isn't a part of standard F# libraries, it shows the great advantage of F# first class events. If there is some missing functionality, you can simply implement it yourself!


Typically, you don't need to do that check in F# (the event infrastructure checks for you):

type T() =
  let ev = new Event<_>()
  [<CLIEvent>]
  member x.Event = ev.Publish
  member x.OnClicked() =
    ev.Trigger()


I followed kvb's suggestion and put this logic in a class. I copied Event from the F# sources and added a Handled property, which checks if the Delegate is null. I tried adding to, then removing handlers from the event to make sure it gets set back to null, and indeed it does.

    type EventEx<'Delegate,'Args when 'Delegate : delegate<'Args,unit> and 'Delegate :> System.Delegate >() = 
        let mutable multicast : System.Delegate = null
        static let argTypes = 
            let instanceBindingFlags = BindingFlags.Instance ||| BindingFlags.Public ||| BindingFlags.NonPublic ||| BindingFlags.DeclaredOnly
            let mi = typeof<'Delegate>.GetMethod("Invoke",instanceBindingFlags)
            mi.GetParameters() |> (fun arr -> arr.[1..]) |> Array.map (fun p -> p.ParameterType)

        member x.Handled = (multicast <> null)

        member x.Trigger(sender:obj,args:'Args) = 
            match multicast with 
            | null -> ()
            | d -> 
                if argTypes.Length = 1 then 
                    d.DynamicInvoke([| sender; box args |]) |> ignore
                else
                    d.DynamicInvoke(Array.append [| sender |] (Microsoft.FSharp.Reflection.FSharpValue.GetTupleFields(box args))) |> ignore
        member x.Publish = 
            { new IEvent<'Delegate,'Args> with 
                member x.AddHandler(d) =
                    multicast <- System.Delegate.Combine(multicast, d)
                member x.RemoveHandler(d) = 
                    multicast <- System.Delegate.Remove(multicast, d) 
                member e.Subscribe(observer) = 
                   let h = new Handler<_>(fun sender args -> observer.OnNext(args))
                   (e :?> IEvent<_,_>).AddHandler(h)
                   { new System.IDisposable with 
                        member x.Dispose() = (e :?> IEvent<_,_>).RemoveHandler(h) } }


This article here http://geekswithblogs.net/Erik/archive/2008/05/22/122302.aspx says you do not need to check for null events in F#, though I don't know what his reference is.

This article http://blogs.msdn.com/dsyme/articles/FSharpCompositionalEvents.aspx by Don Symes goes into F# events in quite a bit of detail. It looks like events are not owned by the class in F#

From the above,

it is that events are now first-class values in the F# langauge. Indeed, events are not a separate notion at all in the language design, rather, events are just values of type Microsoft.FSharp.Idioms.IEvent<_>, and .NET events are effectively just properties of this type.

And

One of the restrictions of C# is that events can only exist as members within classes. With the F# model, new event values can be created just as values as part of any expression.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜